├── .ghci ├── .github └── workflows │ └── haskell-ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README-nix.md ├── README.md ├── Setup.hs ├── appveyor.yml ├── bench ├── bench.hs └── reports │ ├── 7.1.1.html │ ├── 7.1.1.txt │ ├── 7.1.2.html │ └── 7.1.2.txt ├── default.nix ├── formatting.cabal ├── haskell-package-overrides.nix ├── nixpkgs.json ├── pinned-nixpkgs.nix ├── pkgname.nix ├── shell.nix ├── src ├── Data │ └── Text │ │ ├── Format.hs │ │ └── Format │ │ ├── Functions.hs │ │ └── Types.hs ├── Formatting.hs └── Formatting │ ├── Buildable.hs │ ├── Clock.hs │ ├── Combinators.hs │ ├── Examples.hs │ ├── Formatters.hs │ ├── FromBuilder.hs │ ├── Internal.hs │ ├── Internal │ └── Raw.hs │ ├── ShortFormatters.hs │ └── Time.hs ├── stack.yaml ├── test-ghcs └── test └── Spec.hs /.ghci: -------------------------------------------------------------------------------- 1 | :set -isrc 2 | -------------------------------------------------------------------------------- /.github/workflows/haskell-ci.yml: -------------------------------------------------------------------------------- 1 | # This GitHub workflow config has been generated by a script via 2 | # 3 | # haskell-ci 'github' 'formatting.cabal' 4 | # 5 | # To regenerate the script (for example after adjusting tested-with) run 6 | # 7 | # haskell-ci regenerate 8 | # 9 | # For more information, see https://github.com/haskell-CI/haskell-ci 10 | # 11 | # version: 0.14.3 12 | # 13 | # REGENDATA ("0.14.3",["github","formatting.cabal"]) 14 | # 15 | name: Haskell-CI 16 | on: 17 | - push 18 | - pull_request 19 | jobs: 20 | linux: 21 | name: Haskell-CI - Linux - ${{ matrix.compiler }} 22 | runs-on: ubuntu-18.04 23 | timeout-minutes: 24 | 60 25 | container: 26 | image: buildpack-deps:bionic 27 | continue-on-error: ${{ matrix.allow-failure }} 28 | strategy: 29 | matrix: 30 | include: 31 | - compiler: ghc-9.2.2 32 | compilerKind: ghc 33 | compilerVersion: 9.2.2 34 | setup-method: ghcup 35 | allow-failure: false 36 | - compiler: ghc-9.0.2 37 | compilerKind: ghc 38 | compilerVersion: 9.0.2 39 | setup-method: ghcup 40 | allow-failure: false 41 | - compiler: ghc-8.10.7 42 | compilerKind: ghc 43 | compilerVersion: 8.10.7 44 | setup-method: ghcup 45 | allow-failure: false 46 | - compiler: ghc-8.8.4 47 | compilerKind: ghc 48 | compilerVersion: 8.8.4 49 | setup-method: hvr-ppa 50 | allow-failure: false 51 | - compiler: ghc-8.6.5 52 | compilerKind: ghc 53 | compilerVersion: 8.6.5 54 | setup-method: hvr-ppa 55 | allow-failure: false 56 | - compiler: ghc-8.4.4 57 | compilerKind: ghc 58 | compilerVersion: 8.4.4 59 | setup-method: hvr-ppa 60 | allow-failure: false 61 | fail-fast: false 62 | steps: 63 | - name: apt 64 | run: | 65 | apt-get update 66 | apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 67 | if [ "${{ matrix.setup-method }}" = ghcup ]; then 68 | mkdir -p "$HOME/.ghcup/bin" 69 | curl -sL https://downloads.haskell.org/ghcup/0.1.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup" 70 | chmod a+x "$HOME/.ghcup/bin/ghcup" 71 | "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" 72 | "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0 73 | else 74 | apt-add-repository -y 'ppa:hvr/ghc' 75 | apt-get update 76 | apt-get install -y "$HCNAME" 77 | mkdir -p "$HOME/.ghcup/bin" 78 | curl -sL https://downloads.haskell.org/ghcup/0.1.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup" 79 | chmod a+x "$HOME/.ghcup/bin/ghcup" 80 | "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0 81 | fi 82 | env: 83 | HCKIND: ${{ matrix.compilerKind }} 84 | HCNAME: ${{ matrix.compiler }} 85 | HCVER: ${{ matrix.compilerVersion }} 86 | - name: Set PATH and environment variables 87 | run: | 88 | echo "$HOME/.cabal/bin" >> $GITHUB_PATH 89 | echo "LANG=C.UTF-8" >> "$GITHUB_ENV" 90 | echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" 91 | echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" 92 | HCDIR=/opt/$HCKIND/$HCVER 93 | if [ "${{ matrix.setup-method }}" = ghcup ]; then 94 | HC=$HOME/.ghcup/bin/$HCKIND-$HCVER 95 | echo "HC=$HC" >> "$GITHUB_ENV" 96 | echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV" 97 | echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV" 98 | echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" 99 | else 100 | HC=$HCDIR/bin/$HCKIND 101 | echo "HC=$HC" >> "$GITHUB_ENV" 102 | echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV" 103 | echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV" 104 | echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" 105 | fi 106 | 107 | HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') 108 | echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" 109 | echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" 110 | echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" 111 | echo "HEADHACKAGE=false" >> "$GITHUB_ENV" 112 | echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" 113 | echo "GHCJSARITH=0" >> "$GITHUB_ENV" 114 | env: 115 | HCKIND: ${{ matrix.compilerKind }} 116 | HCNAME: ${{ matrix.compiler }} 117 | HCVER: ${{ matrix.compilerVersion }} 118 | - name: env 119 | run: | 120 | env 121 | - name: write cabal config 122 | run: | 123 | mkdir -p $CABAL_DIR 124 | cat >> $CABAL_CONFIG <> $CABAL_CONFIG < cabal-plan.xz 157 | echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c - 158 | xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan 159 | rm -f cabal-plan.xz 160 | chmod a+x $HOME/.cabal/bin/cabal-plan 161 | cabal-plan --version 162 | - name: checkout 163 | uses: actions/checkout@v2 164 | with: 165 | path: source 166 | - name: initial cabal.project for sdist 167 | run: | 168 | touch cabal.project 169 | echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project 170 | cat cabal.project 171 | - name: sdist 172 | run: | 173 | mkdir -p sdist 174 | $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist 175 | - name: unpack 176 | run: | 177 | mkdir -p unpacked 178 | find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \; 179 | - name: generate cabal.project 180 | run: | 181 | PKGDIR_formatting="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/formatting-[0-9.]*')" 182 | echo "PKGDIR_formatting=${PKGDIR_formatting}" >> "$GITHUB_ENV" 183 | rm -f cabal.project cabal.project.local 184 | touch cabal.project 185 | touch cabal.project.local 186 | echo "packages: ${PKGDIR_formatting}" >> cabal.project 187 | echo "package formatting" >> cabal.project 188 | echo " ghc-options: -Werror=missing-methods" >> cabal.project 189 | cat >> cabal.project <> cabal.project.local 192 | cat cabal.project 193 | cat cabal.project.local 194 | - name: dump install plan 195 | run: | 196 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all 197 | cabal-plan 198 | - name: cache 199 | uses: actions/cache@v2 200 | with: 201 | key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} 202 | path: ~/.cabal/store 203 | restore-keys: ${{ runner.os }}-${{ matrix.compiler }}- 204 | - name: install dependencies 205 | run: | 206 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all 207 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all 208 | - name: build w/o tests 209 | run: | 210 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 211 | - name: build 212 | run: | 213 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always 214 | - name: tests 215 | run: | 216 | $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct 217 | - name: cabal check 218 | run: | 219 | cd ${PKGDIR_formatting} || false 220 | ${CABAL} -vnormal check 221 | - name: haddock 222 | run: | 223 | $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all 224 | - name: unconstrained build 225 | run: | 226 | rm -f cabal.project.local 227 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 228 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | dist/ 3 | TAGS 4 | dist-newstyle/ 5 | tags 6 | .hkgr 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 7.2.0 2 | 3 | * Added `FromBuilder` and `formatted` to simplify using formatting with other APIs (thanks Kyle Butt). 4 | * Updated examples in comments to pass cabal-docspec (thanks Kyle Butt). 5 | * Fixed haddock parsing in ghc-8.8.4 (thanks Oleg Grenrus). 6 | * Generalised IO printing functions to use MonadIO (thanks Oleg Grenrus). 7 | * Added `(%+)` and `(<%+>)` for appending formatters with a space between them, the latter also behaving like `(<>)` (thanks Oleg Grenrus). 8 | * Allow building with Cabal 2.2 (thanks Jens Petersen). 9 | * Removed unused dependency on `ghc-prim` 10 | * Add a `no-double-conversion` build flag to optionally remove the dependency on `double-conversion` (Thanks Janus Troelsen) 11 | * The `no-double-conversion` flag also fixes the build on GHC 9.4 on which `double-conversion` is apparently broken 12 | 13 | 7.1.3 14 | 15 | * Fix the GHCJS build by not using `double-conversion`, as it relies on a native C library which obviously isn't available in GHCJS (it is still used in native builds). 16 | 17 | 7.1.2 18 | 19 | * Removed direct dependency on integer-gmp, instead using very similar code from the `text` package. This changed the implementation of `build` for `Integer`, which results in better performance in some cases, and no performance degradation. See the benchmarking reports in bench/reports for more details. 20 | * formatting now compiles on GHCJS (due to the change above). 21 | * Added some benchmarking, starting based on code from the `string-interpolate` package. 22 | * Added INLINE pragmas to many very short functions. Results in better performance in the benchmarks. 23 | 24 | 7.1.1 25 | 26 | * Added `charsKeptIf` and `charsRemovedIf`. 27 | 28 | 7.1.0 29 | 30 | * Added common container formatter combinators: `maybed`, `optioned`, `eithered`, `lefted`, and `righted`. 31 | 32 | 7.0.0.2 33 | 34 | * Removed unnecessary dependencies on array and bytestring 35 | * Actually removed code to support GHC < 8.4 36 | 37 | 7.0.0.1 38 | 39 | * Added README.md to extra-source-files so it shows up on Hackage 40 | 41 | 7.0.0 42 | 43 | * Introduced `Formatting.Combinators`. 44 | * Fixed: #62 and #60: incorrect formatting of Integral types that do not have negative values (e.g. Word) 45 | * Fixed: #59 rendering of floats e.g. 0.01 as "0.01" rather than "1.0e-2" 46 | * Added dependency of double-conversion to provide fast and correct rendering of floating-point numbers (including the fix for #59). 47 | * Make compatible with bytestring-0.11.0.0 48 | * Removed -O2 ghc flag 49 | * Updated .cabal file version from 1.8 to 2.4 50 | * Drop support for GHC < 8.4 51 | 52 | 6.3.7 53 | 54 | * Introduced instance `Buildable a => Buildable [a]`. 55 | 56 | 6.3.6 57 | 58 | * Bring back `int :: Integral a => Format r (a -> r)` 59 | 60 | 6.3.5 61 | 62 | * Avoid pointless conversions on Float/Double. 63 | 64 | 6.3.3 65 | 66 | * The `Data.Text.Format` hierarchy was reexported as 67 | `Formatting.Internal.Raw`. 68 | 69 | 6.3.1 70 | 71 | * Proper GHC 7.10 -> GHC 8.4 support 72 | 73 | 6.3.0 74 | 75 | * Folded the `text-format` package into this package, removed the 76 | `double-conversion` dependency. Lost the following functions in 77 | this: 78 | * `prec` 79 | * `expt` 80 | * Added a test suite with regression tests: 81 | * Fixed: #31 82 | * Fixed: #28 83 | * Fixed: https://github.com/bos/text-format/issues/18 84 | 85 | 6.2.5 86 | 87 | * Changed microseconds to display as "us" to avoid unicode issues. 88 | 89 | 6.2.1 90 | 91 | * Added bytesDecimal 92 | 93 | 6.2.0 94 | 95 | * Dropped Holey/HoleyT in favour of simpler Format type. 96 | * Added Monoid instance. 97 | * Added back Category instance. 98 | * Dropped Functor instance. 99 | 100 | 6.1.1 101 | 102 | * Add support for GHC 7.10 (time update). 103 | 104 | 6.1.0 105 | 106 | * Add formatter for TimeSpec. 107 | 108 | 6.0.0 109 | 110 | * Changed the type of `Format`. Now you write `Format r (a -> r)` instead 111 | of `Format a`. 112 | * Add `formatToString` function. 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Chris Done 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Chris Done nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README-nix.md: -------------------------------------------------------------------------------- 1 | # Nix Usage 2 | 3 | This project prefers `nix` for building. 4 | Below are instructions for how to use it. 5 | 6 | ## Requirements 7 | 8 | You will need `nix` installed to test and build. 9 | 10 | ## Building 11 | 12 | To run a build with `nix`, simply: 13 | 14 | nix-build 15 | 16 | ## Nix Shell 17 | 18 | When developing though, you may want to work in a `nix-shell`: 19 | 20 | nix-shell 21 | cabal build 22 | 23 | You can also specify one or more of several options: 24 | 25 | - *pkgs*: Override the nixpkgs used for the shell (defaults to those pinned by `pinned-nixpkgs.nix`), 26 | - *compiler*: Override the default compiler, 27 | - *doBenchmark*: Whether to enable benchmarks listed in the .cabal file (defaults to false), 28 | - *doTest*: Whether to enable tests listed in the .cabal file (defaults to false). 29 | 30 | Examples: 31 | 32 | nix-shell --arg pkgs 'import /path/to/nixpkgs/default.nix {}' 33 | nix-shell --arg doTest true 34 | nix-shell --arg compiler '"ghc865"' 35 | 36 | ## Running 37 | 38 | To run from within a `nix-shell`: 39 | 40 | nix-shell 41 | cabal exec -- 42 | 43 | Where is the name of the executable you want to run. 44 | 45 | ## Getting a REPL 46 | 47 | To run a repl with the package and its dependencies: 48 | 49 | nix-shell 50 | cabal repl 51 | 52 | or 53 | 54 | nix-shell --run 'cabal repl' 55 | 56 | ## GHCID 57 | 58 | Run `ghcid` from within `nix-shell`, like this: 59 | 60 | nix-shell 61 | ghcid -c 'cabal repl' 62 | 63 | or 64 | 65 | nix-shell --run 'ghcid -c "cabal repl"' 66 | 67 | ## Tags 68 | 69 | To generate project tags use `hasktags`, e.g: 70 | 71 | ```bash 72 | grep -i hs-source-dir *.cabal | sed 's/^.*:\s*//' | sort | uniq | xargs hasktags 73 | ``` 74 | 75 | This will create `tags` and `TAGS`, for use with various editors. 76 | 77 | To generate tags for all project code and all of the haskell dependencies of your project, so that you can jump straight to library source code: 78 | 79 | ```bash 80 | cp $(nix-build --no-link -A projectTags)/tags ./ 81 | ``` 82 | 83 | ## Updating the Pinned nixpkgs 84 | 85 | For reproducible builds we have pinned to a specific version of nixpkgs. 86 | If you ever need to update to a different version of nixpkgs then run: 87 | 88 | nix-prefetch-git --rev --url https://github.com/NixOS/nixpkgs.git > nixpkgs.json 89 | 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # formatting [![Build Status](https://travis-ci.org/AJChapman/formatting.png)](https://travis-ci.org/AJChapman/formatting) [![Hackage](https://img.shields.io/hackage/v/formatting.svg?style=flat)](https://hackage.haskell.org/package/formatting) 2 | 3 | Formatting is a type-safe and flexible library for formatting text from built-in or custom data types. 4 | 5 | - [Hackage Documentation](https://hackage.haskell.org/package/formatting) 6 | - [The original blog post introducing the library](https://chrisdone.com/posts/formatting/), but note that some of the types have changed: `Holey` is no longer used, and `Format`'s type has changed to `newtype Format r a = Format {runFormat :: (Builder -> r) -> a}` 7 | 8 | ## Usage 9 | 10 | You will probably need the `OverloadedStrings` language extension, and to import `Formatting`: 11 | 12 | ```haskell 13 | {-# LANGUAGE OverloadedStrings #-} 14 | 15 | import Formatting 16 | ``` 17 | 18 | You may also need some or all of these: 19 | 20 | ```haskell 21 | import qualified Data.Text as T 22 | import qualified Data.Text.Lazy as TL 23 | import qualified Data.Text.Lazy.Builder as TLB 24 | ``` 25 | 26 | Now a simple example: 27 | 28 | ```haskell 29 | > format ("Person's name is " % text % " and age is " % int) "Dave" 54 30 | "Person's name is Dave and age is 54" 31 | ``` 32 | 33 | In this example, the formatters are two string literals (which take no arguments), and two formatters which take arguments: `text`, which takes a lazy `Text`, and `int` which takes any `Integral`, such as `Int`. 34 | They are all joined together using the `%` operator, producing a formatter which takes two arguments: a lazy `Text` and an `Integral`. 35 | It produces a lazy `Text`, because we used `format`. 36 | To produce other string types, or print the result instead, refer to this table: 37 | 38 | | To produce a | use | 39 | | ------------- | ------------------ | 40 | | `TL.Text` | [`format`] | 41 | | `T.Text` | [`sformat`] | 42 | | `Builder` | [`bformat`] | 43 | | `String` | [`formatToString`] | 44 | 45 | To print the values instead, refer to this table: 46 | 47 | | To print to | use | 48 | | ----------------------------- | ------------- | 49 | | `stdout` | [`fprint`] | 50 | | `stdout`, appending a newline | [`fprintLn`] | 51 | | a handle | [`hprint`] | 52 | | a handle, appending a newline | [`hprintLn`] | 53 | 54 | Apart from the `%` operator, formatters can also be joined using the monoid append operator (`<>`) to avoid repeating the same argument, they can be chained using `%.`, and there are also formatter combinators for composing more advanced combinators. 55 | More on this below. 56 | 57 | ### Formatter Quick Reference 58 | 59 | Built-in formatters: 60 | 61 | | To format a | e.g. | as | use | short form | 62 | | --------------------------------------:| ------------------------ | ---------------- | ---------------------------------- | ---------- | 63 | | lazy `Text` | `"Hello"` | `"Hello"` | [`text`] | [`t`] | 64 | | strict `Text` | `"World!"` | `"World!"` | [`stext`] | [`st`] | 65 | | `String` | `"Goodbye"` | `"Goodbye"` | [`string`] | [`s`] | 66 | | `Builder` | `"Bathtub"` | `"Bathtub"` | [`builder`] | | 67 | | `Show a => a` | `[1, 2, 3]` | `"[1, 2, 3]"` | [`shown`] | [`sh`] | 68 | | `Char` | `'!'` | `"!"` | [`char`] | [`c`] | 69 | | `Integral a => a` | `23` | `"23"` | [`int`] | [`d`] | 70 | | `Real a => a` | `123.32` | `"123.32"` | [`float`] | [`sf`] | 71 | | `Real a => a` | `123.32` | `"123.320"` | [`fixed`] `3` | [`f`] | 72 | | `Scientific` | `scientific 60221409 16` | `"6.0221409e23"` | [`sci`] | | 73 | | `Scientific` | `scientific 60221409 16` | `"6.022e23"` | [`scifmt`] `Exponent (Just 3)` | | 74 | | `Buildable n, Integral n => n` | `123456` | `"12.34.56"` | [`groupInt`] `2 '.'` | | 75 | | `Buildable n, Integral n => n` | `12000` | `"12,000"` | [`commas`] | | 76 | | `Integral n => n` | `32` | `"32nd"` | [`ords`] | | 77 | | `Num a, Eq a => a` | `1` | `"1 ant"` | `int <>` [`plural`] `"ant" "ants"` | | 78 | | `Num a, Eq a => a` | `2` | `"2 ants"` | `int <>` [`plural`] `"ant" "ants"` | | 79 | | `Enum a => a` | `a` | `"97"` | [`asInt`] | | 80 | | `Integral a => a` | `23` | `"10111"` | [`bin`] | [`b`] | 81 | | `Integral a => a` | `23` | `"0b10111"` | [`prefixBin`] | | 82 | | `Integral a => a` | `23` | `"27"` | [`oct`] | [`o`] | 83 | | `Integral a => a` | `23` | `"0o27"` | [`prefixOct`] | | 84 | | `Integral a => a` | `23` | `"17"` | [`hex`] | [`x`] | 85 | | `Integral a => a` | `23` | `"0x17"` | [`prefixHex`] | | 86 | | `Integral a => a` | `23` | `"13"` | [`base`] `20` | | 87 | | `Buildable a => a` | `10` | `" 10"` | [`left`] `4 ' '` | [`l`] | 88 | | `Buildable a => a` | `10` | `"10 "` | [`right`] `4 ' '` | [`r`] | 89 | | `Buildable a => a` | `10` | `" 10 "` | [`center`] `4 ' '` | | 90 | | `Buildable a => a` | `123456` | `"123"` | [`fitLeft`] `3` | | 91 | | `Buildable a => a` | `123456` | `"456"` | [`fitRight`] `3` | | 92 | | `Buildable a => a` | `True` | `"True"` | [`build`] | | 93 | | `a` | `undefined` | `"gronk!"` | [`fconst`] `"gronk!"` | | 94 | 95 | ### Formatter Combinator Quick Reference 96 | 97 | Formatter combinators take a formatter and modify it somehow, e.g. by using it to format elements of a list, or changing its output. 98 | 99 | Built-in formatter combinators: 100 | 101 | 102 | | To format a | e.g. | as | use | 103 | | ----------------------------------------:| ------------------------ | ----------------------------------- | ---------------------------------------- | 104 | | `Maybe a` | `Nothing` | `"Goodbye"` | [`maybed`] `"Goodbye" text` | 105 | | `Maybe a` | `Just "Hello"` | `"Hello"` | [`maybed`] `"Goodbye" text` | 106 | | `Maybe a` | `Nothing` | `""` | [`optioned`] `text` | 107 | | `Maybe a` | `Just "Hello"` | `"Hello"` | [`optioned`] `text` | 108 | | `Either a b` | `Left "Error!"` | `"Error!"` | [`eithered`] `text int` | 109 | | `Either a b` | `Right 69` | `"69"` | [`eithered`] `text int` | 110 | | `Either a x` | `Left "bingo"` | `"bingo"` | [`lefted`] `text` | 111 | | `Either a x` | `Right 16` | `""` | [`lefted`] `text` | 112 | | `Either x a` | `Right "bingo"` | `"bingo"` | [`righted`] `text` | 113 | | `Either x a` | `Left 16` | `""` | [`righted`] `text` | 114 | | `Foldable t => t a` | `[1, 2, 3]` | `"1st2nd3rd"` | [`concatenated`] `ords` | 115 | | `Foldable t => t a` | `[123, 456, 789]` | `"789456123"` | [`joinedWith`] `(mconcat . reverse) int` | 116 | | `Foldable t => t a` | `[1, 2, 3]` | `"1\|\|2\|\|3"` | [`intercalated`] `"\|\|" int` | 117 | | `Foldable t => t a` | `[1, 2, 3]` | `"1 2 3"` | [`unworded`] `int` | 118 | | `Foldable t => t a` | `[1, 2, 3]` | `"1\n2\n3"` | [`unlined`] `d` | 119 | | `Foldable t => t a` | `[1, 2, 3]` | `"1 2 3"` | [`spaced`] `int` | 120 | | `Foldable t => t a` | `[1, 2, 3]` | `"1,2,3"` | [`commaSep`] `int` | 121 | | `Foldable t => t a` | `[1, 2, 3]` | `"1st, 2nd, 3rd"` | [`commaSpaceSep`] `ords` | 122 | | `Foldable t => t a` | `["one", "two", "three"]` | `"[one, two, three]"` | [`list`] `t` | 123 | | `Foldable t => t a` | `["one", "two", "three"]` | `"[\"one\", \"two\", \"three\"]"` | [`qlist`] `t` | 124 | | `[a]` | `[1..]` | `"[1, 10, 11, 100]"` | [`took`] `4 (list bin)` | 125 | | `[a]` | `[1..6]` | `"[4, 5, 6]"` | [`dropped`] `3 (list int)` | 126 | | `a` | `"one two\tthree\nfour` | `"one, two, three, four"` | [`splat`] `isSpace commaSpaceSep stext` | 127 | | `a` | `1234567890` | `"[123, 456, 789, 0]"` | [`splatWith`] `(chunksOf 3) list int` | 128 | | `a` | `"one,two,three"` | `"one\ntwo\nthree\n"` | [`splatOn`] `"," unlined t` | 129 | | `a` | `"one two three "` | `"[one, two, three]"` | [`worded`] `list text` | 130 | | `a` | `"one\n\ntwo\nthree\n\n` | `"["one", "", "two", "three", ""]"` | [`lined`] `qlist text` | 131 | | `a` | `123456` | `"654321"` | [`alteredWith`] `TL.reverse int` | 132 | | `a` | `"Data.Char.isUpper` | `"DCU"` | [`charsKeptIf`] `isUpper string` | 133 | | `a` | `"Data.Char.isUpper` | `"ata.har.ispper"` | [`charsRemovedIf`] `isUpper string` | 134 | | `a` | `"look and boot"` | `"leek and beet"` | [`replaced`] `"oo" "ee" text` | 135 | | `a` | `"look and boot"` | `"LOOK AND BOOT"` | [`uppercased`] | 136 | | `a` | `"Look and Boot"` | `"look and boot"` | [`lowercased`] | 137 | | `a` | `"look and boot"` | `"Look And Boot"` | [`titlecased`] | 138 | | `a` | `"hellos"` | `"he..."` | [`ltruncated`] `5 text` | 139 | | `a` | `"hellos"` | `"h...s"` | [`ctruncated`] | 140 | | `a` | `"hellos"` | `"...os"` | [`rtruncated`] `5 text` | 141 | | `a` | `1` | `" 1"` | [`lpadded`] `3 int` | 142 | | `a` | `1` | `"1 "` | [`rpadded`] `3 int` | 143 | | `a` | `1` | `" 1 "` | [`cpadded`] `3 int` | 144 | | `a` | `123` | `"123 "` | [`lfixed`] `4 int` | 145 | | `a` | `123456` | `"1..."` | [`lfixed`] `4 int` | 146 | | `a` | `123` | `" 123"` | [`rfixed`] `4 int` | 147 | | `a` | `123456` | `"...6"` | [`rfixed`] `4 int` | 148 | | `a` | `123` | `" 123 "` | [`cfixed`] `2 1 ' ' int` | 149 | | `a` | `1234567` | `"12...7"` | [`cfixed`] `2 1 ' ' int` | 150 | | `a` | `"Goo"` | `"McGoo"` | [`prefixed`] `"Mc" t` | 151 | | `a` | `"Goo"` | `"Goosen"` | [`suffixed`] `"sen" t` | 152 | | `a` | `"Goo"` | `"McGooMc"` | [`surrounded`] `"Mc" t` | 153 | | `a` | `"Goo"` | `"McGoosen"` | [`enclosed`] `"Mc" "sen" t` | 154 | | `a` | `"Goo"` | `"'Goo'"` | [`squoted`] `t` | 155 | | `a` | `"Goo"` | `"\"Goo\""` | [`dquoted`] `t` | 156 | | `a` | `"Goo"` | `"(Goo)"` | [`parenthesised`] `t` | 157 | | `a` | `"Goo"` | `"[Goo]"` | [`squared`] `t` | 158 | | `a` | `"Goo"` | `"{Goo}"` | [`braced`] `t` | 159 | | `a` | `"Goo"` | `""` | [`angled`] `t` | 160 | | `a` | `"Goo"` | ``"`Goo`"`` | [`backticked`] `t` | 161 | | `a` | `"Goo"` | `" Goo"` | [`indented`] `3 t` | 162 | | `Foldable t => t a` | `[1, 2, 3]` | `" 1\n 2\n 3"` | [`indentedLines`] `2 d` | 163 | | `a` | `"1\n2\n3"` | `" 1\n 2\n 3"` | [`reindented`] `2 t` | 164 | | `Integral i, RealFrac d => d` | `6.66` | `"7"` | [`roundedTo`] `int` | 165 | | `Integral i, RealFrac d => d` | `6.66` | `"6"` | [`truncatedTo`] `int` | 166 | | `Integral i, RealFrac d => d` | `6.66` | `"7"` | [`ceilingedTo`] `int` | 167 | | `Integral i, RealFrac d => d` | `6.66` | `"6"` | [`flooredTo`] `int` | 168 | | field through a `Lens' s a` | `(1, "goo")` | `"goo"` | [`viewed`] `_2 t` | 169 | | field through a record accessor `s -> a` | `(1, "goo")` | `"1"` | [`accessed`] `fst d` | 170 | | `Integral a => a` | `4097` | `"0b0001000000000001"` | [`binPrefix`] `16` | 171 | | `Integral a => a` | `4097` | `"0o0000000000010001"` | [`octPrefix`] `16` | 172 | | `Integral a => a` | `4097` | `"0x0000000000001001"` | [`hexPrefix`] `16` | 173 | | `Ord f, Integral a, Fractional f => a` | `1024` | `"1KB"` | [`bytes`] `shortest` | 174 | | `Ord f, Integral a, Fractional f => a` | `1234567890` | `"1.15GB"` | [`bytes`] `(fixed 2)` | 175 | 176 | ## Composing formatters 177 | 178 | `%.` is like `%` but feeds one formatter into another: 179 | 180 | ``` haskell 181 | λ> format (left 2 '0' %. hex) 10 182 | "0a" 183 | ``` 184 | 185 | ## Using more than one formatter on the same argument 186 | 187 | ``` haskell 188 | λ> now <- getCurrentTime 189 | λ> format (year % "/" <> month <> "/" % dayOfMonth) now 190 | "2015/01/27" 191 | ``` 192 | 193 | ## The Buildable Typeclass 194 | 195 | One of the great things about `formatting` is that it doesn't rely on typeclasses: you can define one or more formatters for each of your types. 196 | But you also have the option of defining a 'default' formatter for a type, by implementing the `Buildable` typeclass, which has one method: `build :: p -> Builder`. 197 | Once this is defined for a type, you can use the `build` formatter (which is distinct from the `build` method of `Buildable`!): 198 | 199 | ```haskell 200 | > format ("Int: " % build % ", Text: " % build) 23 "hello" 201 | "Int: 23, Text: hello" 202 | ``` 203 | 204 | Note that while this can be convenient, it also sacrifices some type-safety: there's nothing preventing you from putting the arguments in the wrong order, because both `Int` and `Text` have a `Buildable` instance. 205 | Note also that if a type already has a `Show` instance then you can use this instead, by using the `shown` formatter. 206 | 207 | ## Understanding the Types 208 | 209 | Formatters generally have a type like this: 210 | 211 | ```haskell 212 | Format r (a -> r) 213 | ``` 214 | 215 | This describes a formatter that will eventually produce some string type `r`, and takes an `a` as an argument. 216 | For example: 217 | 218 | ```haskell 219 | int :: Integral a => Format r (a -> r) 220 | ``` 221 | 222 | This takes an `Integral a` argument, and eventually produces an `r`. 223 | Let's work through using this with `format`: 224 | 225 | ```haskell 226 | -- format has this type: 227 | format :: Format TL.Text a -> a 228 | 229 | -- so in 'format int', called with an 'Int', 'int's type specialises to: 230 | int :: Format TL.Text (Int -> TL.Text) 231 | 232 | -- and 'format's 'a' parameter specialises to 'Int -> TL.Text': 233 | format :: Format TL.Text (Int -> TL.Text) -> Int -> TL.Text 234 | 235 | -- so 'format int' takes an Int and produces text: 236 | format int :: Int -> TL.Text 237 | ``` 238 | 239 | What can be confusing in the above is that `int`'s `a` parameter expands to `Int`, but `format`'s `a` parameter expands to `Int -> TL.Text`. 240 | 241 | Now let's look at what happens when we use the `%` operator to append formatters: 242 | 243 | ```haskell 244 | -- Here are the types of the functions we will use: 245 | (%) :: Format r a -> Format r' r -> Format r' a 246 | int :: Format r (Int -> r) -- simplified for this use 247 | stext :: Format r (T.Text -> r) 248 | 249 | -- Within the call to '%', in the expression 'int % stext', the type parameters expand like this: 250 | -- r = T.Text -> r' 251 | -- a = Int -> T.Text -> r' 252 | -- and so we have these types: 253 | int :: Format (T.Text -> r') (Int -> T.Text -> r') 254 | stext :: Format r' (T.Text -> r') 255 | int % stext :: Format r' (Int -> T.Text -> r') 256 | 257 | -- And so when we use 'format' we get a function that takes two arguments and produces text: 258 | format (int % stext) :: Int -> T.Text -> TL.Text 259 | ``` 260 | 261 | ## Comparison with Other Languages 262 | 263 | Example: 264 | 265 | ``` haskell 266 | format ("Person's name is " % text % ", age is " % hex) "Dave" 54 267 | ``` 268 | 269 | or with short-names: 270 | 271 | ``` haskell 272 | format ("Person's name is " % t % ", age is " % x) "Dave" 54 273 | ``` 274 | 275 | Similar to C's `printf`: 276 | 277 | ``` c 278 | printf("Person's name is %s, age is %x","Dave",54); 279 | ``` 280 | 281 | and Common Lisp's `FORMAT`: 282 | 283 | ``` lisp 284 | (format nil "Person's name is ~a, age is ~x" "Dave" 54) 285 | ``` 286 | 287 | ## Formatter Examples 288 | 289 | ### "Hello, World!": Texts 290 | 291 | ``` haskell 292 | > format (text % "!") "Hi!" 293 | "Hi!!" 294 | > format (string % "!") "Hi!" 295 | "Hi!!" 296 | ``` 297 | 298 | ### 123: Integers 299 | 300 | ``` haskell 301 | > format int 23 302 | "23" 303 | ``` 304 | 305 | ### 23.4: Decimals 306 | 307 | ``` haskell 308 | > format (fixed 0) 23.3 309 | "23" 310 | > format (fixed 2) 23.3333 311 | "23.33" 312 | > format shortest 23.3333 313 | "23.3333" 314 | > format shortest 0.0 315 | "0.0" 316 | > format sci 2.3 317 | "2.3" 318 | > format (scifmt Fixed (Just 0)) 2.3 319 | "2" 320 | ``` 321 | 322 | ### 1,242: Commas 323 | 324 | ``` haskell 325 | > format commas 123456778 326 | "123,456,778" 327 | > format commas 1234 328 | "1,234" 329 | ``` 330 | 331 | ### 1st: Ordinals 332 | 333 | ``` haskell 334 | > format ords 1 335 | "1st" 336 | > format ords 2 337 | "2nd" 338 | > format ords 3 339 | "3rd" 340 | > format ords 4 341 | "4th" 342 | ``` 343 | 344 | ### 3F: Hex 345 | 346 | ``` haskell 347 | > format hex 15 348 | "f" 349 | > format hex 25 350 | "19" 351 | ``` 352 | 353 | ### Monday 1st June: Dates & times 354 | 355 | ``` haskell 356 | > now <- getCurrentTime 357 | > later <- getCurrentTime 358 | > format (dayOfMonth % "/" % month % "/" % year) now now now 359 | "16/06/2014" 360 | > format day now 361 | "167" 362 | > format hms now 363 | "08:24:41" 364 | > format tz now 365 | "+0000" 366 | > format datetime now 367 | "Mon Jun 16 08:24:41 UTC 2014" 368 | > format century now 369 | "20" 370 | > format (dayOfMonthOrd % " of " % monthName) now now 371 | "16th of June" 372 | ``` 373 | 374 | ### 3 years ago: Time spans 375 | 376 | ``` haskell 377 | > format (diff False) (diffUTCTime later now) 378 | "2 seconds" 379 | > format (diff True) (diffUTCTime later now) 380 | "in 2 seconds" 381 | > format (diff True) (diffUTCTime now later) 382 | "2 seconds ago" 383 | > format (seconds 0 % " secs") (diffUTCTime now later) 384 | "2 secs" 385 | ``` 386 | 387 | ``` haskell 388 | > let Just old = parseTime defaultTimeLocale "%Y" "1980" :: Maybe UTCTime 389 | > format (years 0) (diffUTCTime now old) 390 | "34" 391 | > format (diff True) (diffUTCTime now old) 392 | "in 35 years" 393 | > format (diff True) (diffUTCTime old now) 394 | "35 years ago" 395 | > format (days 0) (diffUTCTime old now) 396 | "12585" 397 | > format (days 0 % " days") (diffUTCTime old now) 398 | "12585 days" 399 | ``` 400 | 401 | ### File sizes 402 | 403 | ``` haskell 404 | > format (bytes shortest) 1024 405 | "1KB" 406 | > format (bytes (fixed 2 % " ")) (1024*1024*5) 407 | "5.00 MB" 408 | ``` 409 | 410 | ### Scientific 411 | 412 | If you're using a type which provides its own builder, like the 413 | `Scientific` type: 414 | 415 | ``` haskell 416 | import Data.Text.Lazy.Builder.Scientific 417 | scientificBuilder :: Scientific -> Builder 418 | formatScientificBuilder :: FPFormat -> Maybe Int -> Scientific -> Builder 419 | ``` 420 | 421 | Then you can use `later` easily: 422 | 423 | ``` haskell 424 | > format (later scientificBuilder) 23.4 425 | "23.4" 426 | ``` 427 | 428 | Actually, there are now already two handy combinators (`sci` and 429 | `scifmt`) for the `Scientific` type as shown above in the Decimals 430 | section. 431 | 432 | ## Writing your own Formatters 433 | 434 | You can include things verbatim in the formatter: 435 | 436 | ``` haskell 437 | > format (now "This is printed now.") 438 | "This is printed now." 439 | ``` 440 | 441 | Although with `OverloadedStrings` you can just use string literals: 442 | 443 | ``` haskell 444 | > format "This is printed now." 445 | "This is printed now." 446 | ``` 447 | 448 | You can handle things later which makes the formatter accept arguments: 449 | 450 | ``` haskell 451 | > format (later (const "This is printed later.")) () 452 | "This is printed later." 453 | ``` 454 | 455 | The type of the function passed to `later` should return an instance 456 | of `Monoid`. 457 | 458 | ``` haskell 459 | later :: (a -> Builder) -> Format r (a -> r) 460 | ``` 461 | 462 | The function you format with (`format`, `bprint`, etc.) 463 | will determine the monoid of choice. In the case of this library, the 464 | top-level formating functions expect you to build a text `Builder`: 465 | 466 | ``` haskell 467 | format :: Format Text a -> a 468 | ``` 469 | 470 | Because builders are efficient generators. 471 | 472 | So in this case we will be expected to produce Builders from arguments: 473 | 474 | ``` haskell 475 | format . later :: (a -> Builder) -> a -> Text 476 | ``` 477 | 478 | To do that for common types you can just re-use the formatting library 479 | and use bprint: 480 | 481 | ``` haskell 482 | λ> :t bprint 483 | bprint :: Format Builder a -> a 484 | > :t bprint int 23 485 | bprint int 23 :: Builder 486 | ``` 487 | 488 | Coming back to `later`, we can now use it to build our own printer 489 | combinators: 490 | 491 | ``` haskell 492 | > let mint = later (maybe "" (bprint int)) 493 | > :t mint 494 | mint :: Integral a => Format r (Maybe a -> r) 495 | ``` 496 | 497 | Now `mint` is a formatter to show `Maybe Integer`: 498 | 499 | ``` haskell 500 | > format mint (readMaybe "23") 501 | "23" 502 | > format mint (readMaybe "foo") 503 | "" 504 | ``` 505 | 506 | Although a better, more general combinator might be: 507 | 508 | ``` haskell 509 | > let mfmt x f = later (maybe x (bprint f)) 510 | ``` 511 | 512 | Now you can use it to maybe format things: 513 | 514 | ``` haskell 515 | > format (mfmt "Nope!" int) (readMaybe "foo") 516 | "Nope!" 517 | ``` 518 | 519 | ## Using it with other APIs 520 | 521 | As a convenience, we provide the `FromBuilder` typeclass and the `formatted` 522 | combinator. `formatted` makes it simple to add formatting to any API that is 523 | expecting a `Builder`, a strict or lazy `Text`, or a `String`. For example if 524 | you have functions `logDebug`, `logWarning` and `logInfo` all of type 525 | `Text -> IO ()` you can do the following: 526 | 527 | ``` haskell 528 | > formatted logDebug ("x is: " % int) x 529 | > formatted logInfo ("y is: " % squared int) y 530 | > formatted logWarning ("z is: " % braced int) z 531 | ``` 532 | 533 | The above example will work for either strict or lazy `Text` 534 | 535 | ## Hacking 536 | 537 | ### Building with Nix 538 | 539 | See [README-nix.md](./README-nix.md). 540 | 541 | ### Running the Tests 542 | 543 | From within your `nix-shell`, run `cabal test`. 544 | 545 | The tests are in `test/Spec.hs`. 546 | 547 | ### Running the Benchmarks 548 | 549 | Start `nix-shell` like this: `nix-shell --arg doBenchmark true`. 550 | From within your `nix-shell`, run `cabal bench`. 551 | 552 | To build the html benchmarking reports, run `cabal bench --benchmark-option=-obench/reports/7.2.0.html > bench/reports/7.2.0.txt`, replacing '7.2.0' with the current version. 553 | This will output the file `bench/reports/7.2.0.html` which you can open in a browser, and bench/reports/7.2.0.txt which you can view in a terminal or text editor. 554 | 555 | The benchmarks are in `bench/bench.hs`. 556 | 557 | [`format`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:format 558 | [`sformat`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:sformat 559 | [`bformat`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:bformat 560 | [`formatToString`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:formatToString 561 | [`fprint`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:fprint 562 | [`fprintLn`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:fprintLn 563 | [`hprint`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:hprint 564 | [`hprintLn`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:hprintLn 565 | 566 | [`text`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:text 567 | [`stext`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:stext 568 | [`string`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:string 569 | [`builder`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:builder 570 | [`shown`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:shown 571 | [`char`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:char 572 | [`int`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:int 573 | [`float`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:float 574 | [`fixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fixed 575 | [`sci`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:sci 576 | [`scifmt`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:scifmt 577 | [`groupInt`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:groupInt 578 | [`commas`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:commas 579 | [`ords`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:ords 580 | [`plural`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:plural 581 | [`asInt`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:asInt 582 | [`bin`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:bin 583 | [`prefixBin`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:prefixBin 584 | [`oct`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:oct 585 | [`prefixOct`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:prefixOct 586 | [`hex`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:hex 587 | [`prefixHex`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:prefixHex 588 | [`base`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:base 589 | [`left`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:left 590 | [`right`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:right 591 | [`center`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:center 592 | [`fitLeft`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fitLeft 593 | [`fitRight`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fitRight 594 | [`build`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:build 595 | [`fconst`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fconst 596 | [`bytes`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:bytes 597 | 598 | [`t`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:t 599 | [`st`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:st 600 | [`s`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:s 601 | [`sh`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:sh 602 | [`c`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:c 603 | [`d`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:d 604 | [`sf`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:sf 605 | [`f`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:f 606 | [`b`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:b 607 | [`o`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:o 608 | [`x`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:x 609 | [`l`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:l 610 | [`r`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:r 611 | 612 | [`maybed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:maybed 613 | [`optioned`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:optioned 614 | [`eithered`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:eithered 615 | [`lefted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:lefted 616 | [`righted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:righted 617 | [`concatenated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:concatenated 618 | [`joinedWith`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:joinedWith 619 | [`intercalated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:intercalated 620 | [`unworded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:unworded 621 | [`unlined`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:unlined 622 | [`spaced`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:spaced 623 | [`commaSep`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:commaSep 624 | [`commaSpaceSep`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:commaSpaceSep 625 | [`list`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:list 626 | [`qlist`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:qlist 627 | [`took`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:took 628 | [`dropped`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:dropped 629 | [`splat`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:splat 630 | [`splatWith`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:splatWith 631 | [`splatOn`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:splatOn 632 | [`worded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:worded 633 | [`lined`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lined 634 | [`alteredWith`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:alteredWith 635 | [`charsKeptIf`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:charsKeptIf 636 | [`charsRemovedIf`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#vata.har.isppercharsRemovedIf 637 | [`replaced`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:replaced 638 | [`uppercased`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:uppercased 639 | [`lowercased`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lowercased 640 | [`titlecased`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:titlecased 641 | [`ltruncated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:ltruncated 642 | [`ctruncated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:ctruncated 643 | [`rtruncated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:rtruncated 644 | [`lpadded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lpadded 645 | [`rpadded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:rpadded 646 | [`cpadded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:cpadded 647 | [`lfixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lfixed 648 | [`rfixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:rfixed 649 | [`cfixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:cfixed 650 | [`prefixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:prefixed 651 | [`suffixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:suffixed 652 | [`surrounded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:surrounded 653 | [`enclosed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:enclosed 654 | [`squoted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:squoted 655 | [`dquoted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:dquoted 656 | [`parenthesised`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:parenthesised 657 | [`squared`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:squared 658 | [`braced`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:braced 659 | [`angled`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:angled 660 | [`backticked`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:backticked 661 | [`indented`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:indented 662 | [`indentedLines`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:indentedLines 663 | [`reindented`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:reindented 664 | [`roundedTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:roundedTo 665 | [`truncatedTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:truncatedTo 666 | [`ceilingedTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:ceilingedTo 667 | [`flooredTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:flooredTo 668 | [`viewed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:viewed 669 | [`accessed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:accessed 670 | [`binPrefix`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:binPrefix 671 | [`octPrefix`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:octPrefix 672 | [`hexPrefix`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:hexPrefix 673 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | build: off 2 | 3 | image: 4 | - Visual Studio 2019 5 | 6 | environment: 7 | global: 8 | STACK_ROOT: "c:\\sr" 9 | 10 | matrix: 11 | # - ARGS: "--resolver lts-12" # We support ghc-8.4, but this particular build fails due to cabal compatibility issues 12 | - ARGS: "--resolver lts-13" # ghc-8.6.4 13 | - ARGS: "--resolver lts-14" # ghc-8.6.5 14 | - ARGS: "--resolver lts-15" # ghc-8.8.2 15 | - ARGS: "--resolver lts-16" # ghc-8.8.4 16 | - ARGS: "--resolver nightly" 17 | 18 | clone_folder: "c:\\stack" 19 | 20 | before_test: 21 | # http://help.appveyor.com/discussions/problems/6312-curl-command-not-found 22 | - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% 23 | - curl -sS -ostack.zip -L --insecure https://get.haskellstack.org/stable/windows-x86_64.zip 24 | - 7z x stack.zip stack.exe 25 | 26 | test_script: 27 | # Install toolchain, but do it silently due to lots of output 28 | - stack %ARGS% setup > nul 29 | # The ugly echo "" hack is to avoid complaints about 0 being an invalid file 30 | # descriptor 31 | - echo "" | stack %ARGS% --no-terminal test 32 | -------------------------------------------------------------------------------- /bench/bench.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE RankNTypes #-} 3 | {-# LANGUAGE TypeApplications #-} 4 | 5 | import Criterion (bench, bgroup, env, nf, whnf) 6 | import Criterion.Main (defaultMain) 7 | import Test.QuickCheck 8 | 9 | import qualified Data.Text as T 10 | import qualified Data.Text.Lazy as LT 11 | 12 | import Formatting ((%)) 13 | import qualified Formatting as F 14 | import qualified Formatting.ShortFormatters as F 15 | 16 | -- From string-interpolate's benchmarks 17 | 18 | stringF :: String -> String 19 | stringF = F.formatToString ("A fine day to die, " % F.s % ".") 20 | 21 | multiStringF :: (Int, String, Bool) -> String 22 | multiStringF (x, y, z) = 23 | F.formatToString (" foo " % F.d % " bar " % F.s % " baz " % F.sh % " quux ") x y z 24 | 25 | textF :: T.Text -> T.Text 26 | textF = F.sformat ("A fine day to die, " % F.st % ".") 27 | 28 | multiTextF :: (Int, T.Text, Bool) -> T.Text 29 | multiTextF (x, y, z) = 30 | F.sformat (" foo " % F.d % " bar " % F.st % " baz " % F.sh % " quux ") x y z 31 | 32 | lazyTextF :: LT.Text -> LT.Text 33 | lazyTextF = F.format ("A find day to die, " % F.t % ".") 34 | 35 | multiLazyTextF :: (Int, LT.Text, Bool) -> LT.Text 36 | multiLazyTextF (x, y, z) = 37 | F.format (" foo " % F.d % " bar " % F.t % " baz " % F.sh % " quux ") x y z 38 | 39 | integerF :: Integer -> LT.Text 40 | integerF = F.format F.int 41 | 42 | buildF :: F.Buildable a => a -> LT.Text 43 | buildF = F.format F.build 44 | 45 | main :: IO () 46 | main = defaultMain 47 | [ bench "Small Strings" $ nf stringF "William" 48 | , bench "Small Text" $ nf textF "William" 49 | , bench "Small Lazy Text" $ nf lazyTextF "William" 50 | , bench "Multiple Interpolations String" $ nf multiStringF (42, "CATALLAXY", True) 51 | , bench "Multiple Interpolations Text" $ nf multiTextF (42, "CATALLAXY", True) 52 | , bench "Multiple Interpolations Lazy Text" $ nf multiLazyTextF (42, "CATALLAXY", True) 53 | , env largeishText $ \ ~t -> 54 | bench "Largeish Text" $ nf textF t 55 | , env largeishLazyText $ \ ~lt -> 56 | bench "Largeish Lazy Text" $ nf lazyTextF lt 57 | , bgroup "Integers" $ 58 | (\n -> bench (show n) $ whnf integerF n) <$> integersToTest 59 | , bgroup "Buildable (Integer)" $ 60 | (\n -> bench (show n) $ whnf buildF n) <$> integersToTest 61 | ] 62 | where 63 | integersToTest :: [Integer] 64 | integersToTest = [0, 1, -1, 10, -10, 99, -99, 100, 123, 12345678, maxIntInteger, -maxIntInteger, maxIntInteger * 2] 65 | 66 | maxIntInteger :: Integer 67 | maxIntInteger = fromIntegral (maxBound @Int) 68 | 69 | largeishText :: IO T.Text 70 | largeishText = 71 | generate $ T.pack . Prelude.take 100000 <$> infiniteListOf arbitrary 72 | 73 | largeishLazyText :: IO LT.Text 74 | largeishLazyText = 75 | generate $ LT.pack . Prelude.take 100000 <$> infiniteListOf arbitrary 76 | -------------------------------------------------------------------------------- /bench/reports/7.1.1.txt: -------------------------------------------------------------------------------- 1 | Resolving dependencies... 2 | Build profile: -w ghc-8.10.4 -O1 3 | In order, the following will be built (use -v for more details): 4 | - formatting-7.1.1 (lib) (configuration changed) 5 | - formatting-7.1.1 (bench:bench) (configuration changed) 6 | Configuring library for formatting-7.1.1.. 7 | Preprocessing library for formatting-7.1.1.. 8 | Building library for formatting-7.1.1.. 9 | [ 1 of 14] Compiling Data.Text.Format.Functions ( src/Data/Text/Format/Functions.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format/Functions.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format/Functions.dyn_o ) [flags changed] 10 | [ 2 of 14] Compiling Data.Text.Format.Types ( src/Data/Text/Format/Types.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format/Types.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format/Types.dyn_o ) [flags changed] 11 | [ 3 of 14] Compiling Formatting.Internal ( src/Formatting/Internal.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Internal.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Internal.dyn_o ) [flags changed] 12 | [ 4 of 14] Compiling Data.Text.Format.Int ( src/Data/Text/Format/Int.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format/Int.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format/Int.dyn_o ) 13 | [ 5 of 14] Compiling Formatting.Buildable ( src/Formatting/Buildable.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Buildable.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Buildable.dyn_o ) 14 | [ 6 of 14] Compiling Data.Text.Format ( src/Data/Text/Format.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Data/Text/Format.dyn_o ) [flags changed] 15 | [ 7 of 14] Compiling Formatting.Formatters ( src/Formatting/Formatters.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Formatters.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Formatters.dyn_o ) [flags changed] 16 | [ 8 of 14] Compiling Formatting.Combinators ( src/Formatting/Combinators.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Combinators.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Combinators.dyn_o ) [flags changed] 17 | [ 9 of 14] Compiling Formatting ( src/Formatting.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting.dyn_o ) [flags changed] 18 | [10 of 14] Compiling Formatting.Examples ( src/Formatting/Examples.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Examples.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Examples.dyn_o ) [flags changed] 19 | [11 of 14] Compiling Formatting.Clock ( src/Formatting/Clock.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Clock.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Clock.dyn_o ) [flags changed] 20 | [12 of 14] Compiling Formatting.Internal.Raw ( src/Formatting/Internal/Raw.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Internal/Raw.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Internal/Raw.dyn_o ) 21 | [13 of 14] Compiling Formatting.ShortFormatters ( src/Formatting/ShortFormatters.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/ShortFormatters.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/ShortFormatters.dyn_o ) [flags changed] 22 | [14 of 14] Compiling Formatting.Time ( src/Formatting/Time.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Time.o, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/build/Formatting/Time.dyn_o ) [flags changed] 23 | Configuring benchmark 'bench' for formatting-7.1.1.. 24 | Preprocessing benchmark 'bench' for formatting-7.1.1.. 25 | Building benchmark 'bench' for formatting-7.1.1.. 26 | [1 of 1] Compiling Main ( bench/bench.hs, /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/b/bench/build/bench/bench-tmp/Main.o ) 27 | Linking /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.1/b/bench/build/bench/bench ... 28 | Running 1 benchmarks... 29 | Benchmark bench: RUNNING... 30 | benchmarking Small Strings 31 | time 559.0 ns (554.5 ns .. 565.4 ns) 32 | 0.997 R² (0.994 R² .. 0.999 R²) 33 | mean 593.1 ns (571.9 ns .. 632.2 ns) 34 | std dev 95.97 ns (58.13 ns .. 150.6 ns) 35 | variance introduced by outliers: 96% (severely inflated) 36 | 37 | benchmarking Small Text 38 | time 194.7 ns (190.8 ns .. 199.6 ns) 39 | 0.995 R² (0.990 R² .. 0.998 R²) 40 | mean 194.2 ns (190.7 ns .. 201.7 ns) 41 | std dev 16.27 ns (9.994 ns .. 24.65 ns) 42 | variance introduced by outliers: 87% (severely inflated) 43 | 44 | benchmarking Small Lazy Text 45 | time 194.1 ns (190.5 ns .. 198.8 ns) 46 | 0.997 R² (0.995 R² .. 0.999 R²) 47 | mean 196.4 ns (192.7 ns .. 207.1 ns) 48 | std dev 19.91 ns (9.402 ns .. 37.63 ns) 49 | variance introduced by outliers: 91% (severely inflated) 50 | 51 | benchmarking Multiple Interpolations String 52 | time 1.087 μs (1.071 μs .. 1.106 μs) 53 | 0.997 R² (0.995 R² .. 0.999 R²) 54 | mean 1.095 μs (1.078 μs .. 1.128 μs) 55 | std dev 75.03 ns (41.59 ns .. 118.4 ns) 56 | variance introduced by outliers: 79% (severely inflated) 57 | 58 | benchmarking Multiple Interpolations Text 59 | time 592.7 ns (584.7 ns .. 602.7 ns) 60 | 0.998 R² (0.997 R² .. 1.000 R²) 61 | mean 590.8 ns (585.7 ns .. 599.5 ns) 62 | std dev 22.35 ns (15.85 ns .. 33.23 ns) 63 | variance introduced by outliers: 54% (severely inflated) 64 | 65 | benchmarking Multiple Interpolations Lazy Text 66 | time 569.1 ns (556.6 ns .. 585.2 ns) 67 | 0.997 R² (0.995 R² .. 0.999 R²) 68 | mean 571.7 ns (562.3 ns .. 594.1 ns) 69 | std dev 44.80 ns (22.80 ns .. 83.64 ns) 70 | variance introduced by outliers: 84% (severely inflated) 71 | 72 | benchmarking Largeish Text 73 | time 9.989 μs (9.886 μs .. 10.09 μs) 74 | 0.999 R² (0.997 R² .. 0.999 R²) 75 | mean 10.01 μs (9.914 μs .. 10.28 μs) 76 | std dev 495.3 ns (269.3 ns .. 902.9 ns) 77 | variance introduced by outliers: 60% (severely inflated) 78 | 79 | benchmarking Largeish Lazy Text 80 | time 339.0 ns (333.7 ns .. 346.3 ns) 81 | 0.997 R² (0.995 R² .. 0.999 R²) 82 | mean 343.9 ns (336.9 ns .. 364.9 ns) 83 | std dev 38.26 ns (15.12 ns .. 78.75 ns) 84 | variance introduced by outliers: 92% (severely inflated) 85 | 86 | benchmarking Integers/0 87 | time 132.5 ns (130.8 ns .. 134.4 ns) 88 | 0.998 R² (0.997 R² .. 1.000 R²) 89 | mean 133.2 ns (131.4 ns .. 136.9 ns) 90 | std dev 7.775 ns (3.254 ns .. 15.40 ns) 91 | variance introduced by outliers: 76% (severely inflated) 92 | 93 | benchmarking Integers/1 94 | time 149.4 ns (141.8 ns .. 157.7 ns) 95 | 0.989 R² (0.982 R² .. 0.998 R²) 96 | mean 143.7 ns (141.7 ns .. 148.0 ns) 97 | std dev 9.356 ns (6.044 ns .. 15.41 ns) 98 | variance introduced by outliers: 80% (severely inflated) 99 | 100 | benchmarking Integers/-1 101 | time 154.6 ns (152.9 ns .. 156.8 ns) 102 | 0.998 R² (0.995 R² .. 1.000 R²) 103 | mean 156.5 ns (154.0 ns .. 165.3 ns) 104 | std dev 12.60 ns (6.086 ns .. 25.66 ns) 105 | variance introduced by outliers: 86% (severely inflated) 106 | 107 | benchmarking Integers/10 108 | time 233.0 ns (230.1 ns .. 237.1 ns) 109 | 0.998 R² (0.995 R² .. 1.000 R²) 110 | mean 233.6 ns (231.0 ns .. 237.8 ns) 111 | std dev 11.68 ns (7.192 ns .. 17.98 ns) 112 | variance introduced by outliers: 69% (severely inflated) 113 | 114 | benchmarking Integers/-10 115 | time 236.7 ns (229.2 ns .. 247.6 ns) 116 | 0.994 R² (0.988 R² .. 0.999 R²) 117 | mean 236.1 ns (231.7 ns .. 242.7 ns) 118 | std dev 17.95 ns (10.90 ns .. 26.79 ns) 119 | variance introduced by outliers: 84% (severely inflated) 120 | 121 | benchmarking Integers/99 122 | time 233.4 ns (230.6 ns .. 237.3 ns) 123 | 0.998 R² (0.996 R² .. 1.000 R²) 124 | mean 233.4 ns (230.9 ns .. 239.0 ns) 125 | std dev 11.82 ns (6.563 ns .. 20.65 ns) 126 | variance introduced by outliers: 70% (severely inflated) 127 | 128 | benchmarking Integers/-99 129 | time 232.4 ns (228.4 ns .. 237.4 ns) 130 | 0.997 R² (0.995 R² .. 0.999 R²) 131 | mean 231.7 ns (229.2 ns .. 236.3 ns) 132 | std dev 11.35 ns (7.936 ns .. 16.78 ns) 133 | variance introduced by outliers: 68% (severely inflated) 134 | 135 | benchmarking Integers/100 136 | time 271.2 ns (268.5 ns .. 275.1 ns) 137 | 0.998 R² (0.996 R² .. 1.000 R²) 138 | mean 273.5 ns (269.9 ns .. 280.9 ns) 139 | std dev 16.36 ns (8.007 ns .. 30.05 ns) 140 | variance introduced by outliers: 76% (severely inflated) 141 | 142 | benchmarking Integers/123 143 | time 272.6 ns (269.0 ns .. 278.3 ns) 144 | 0.998 R² (0.996 R² .. 1.000 R²) 145 | mean 271.8 ns (269.1 ns .. 277.2 ns) 146 | std dev 12.07 ns (7.392 ns .. 20.49 ns) 147 | variance introduced by outliers: 64% (severely inflated) 148 | 149 | benchmarking Integers/12345678 150 | time 560.9 ns (551.1 ns .. 571.7 ns) 151 | 0.998 R² (0.997 R² .. 0.999 R²) 152 | mean 565.0 ns (556.4 ns .. 584.0 ns) 153 | std dev 39.99 ns (23.46 ns .. 67.76 ns) 154 | variance introduced by outliers: 81% (severely inflated) 155 | 156 | benchmarking Integers/9223372036854775807 157 | time 1.169 μs (1.154 μs .. 1.189 μs) 158 | 0.998 R² (0.997 R² .. 0.999 R²) 159 | mean 1.174 μs (1.160 μs .. 1.193 μs) 160 | std dev 53.83 ns (40.32 ns .. 72.03 ns) 161 | variance introduced by outliers: 62% (severely inflated) 162 | 163 | benchmarking Integers/-9223372036854775807 164 | time 1.186 μs (1.172 μs .. 1.203 μs) 165 | 0.998 R² (0.997 R² .. 0.999 R²) 166 | mean 1.204 μs (1.182 μs .. 1.318 μs) 167 | std dev 122.7 ns (38.68 ns .. 302.0 ns) 168 | variance introduced by outliers: 89% (severely inflated) 169 | 170 | benchmarking Integers/18446744073709551614 171 | time 1.259 μs (1.247 μs .. 1.272 μs) 172 | 0.999 R² (0.997 R² .. 1.000 R²) 173 | mean 1.257 μs (1.244 μs .. 1.288 μs) 174 | std dev 65.65 ns (28.74 ns .. 125.0 ns) 175 | variance introduced by outliers: 68% (severely inflated) 176 | 177 | benchmarking Buildable (Integer)/0 178 | time 30.68 ns (30.33 ns .. 31.06 ns) 179 | 0.999 R² (0.998 R² .. 1.000 R²) 180 | mean 30.64 ns (30.42 ns .. 31.12 ns) 181 | std dev 1.035 ns (642.6 ps .. 2.008 ns) 182 | variance introduced by outliers: 54% (severely inflated) 183 | 184 | benchmarking Buildable (Integer)/1 185 | time 30.91 ns (30.52 ns .. 31.38 ns) 186 | 0.998 R² (0.997 R² .. 1.000 R²) 187 | mean 31.14 ns (30.69 ns .. 32.25 ns) 188 | std dev 2.298 ns (1.067 ns .. 4.315 ns) 189 | variance introduced by outliers: 85% (severely inflated) 190 | 191 | benchmarking Buildable (Integer)/-1 192 | time 57.05 ns (56.22 ns .. 58.19 ns) 193 | 0.998 R² (0.995 R² .. 0.999 R²) 194 | mean 57.19 ns (56.48 ns .. 58.88 ns) 195 | std dev 3.287 ns (2.017 ns .. 5.957 ns) 196 | variance introduced by outliers: 77% (severely inflated) 197 | 198 | benchmarking Buildable (Integer)/10 199 | time 74.56 ns (73.65 ns .. 75.86 ns) 200 | 0.997 R² (0.993 R² .. 1.000 R²) 201 | mean 74.55 ns (73.62 ns .. 76.39 ns) 202 | std dev 4.306 ns (1.339 ns .. 6.915 ns) 203 | variance introduced by outliers: 77% (severely inflated) 204 | 205 | benchmarking Buildable (Integer)/-10 206 | time 92.51 ns (91.50 ns .. 93.84 ns) 207 | 0.998 R² (0.996 R² .. 1.000 R²) 208 | mean 92.74 ns (91.68 ns .. 95.89 ns) 209 | std dev 5.421 ns (2.845 ns .. 10.09 ns) 210 | variance introduced by outliers: 77% (severely inflated) 211 | 212 | benchmarking Buildable (Integer)/99 213 | time 74.27 ns (73.44 ns .. 75.54 ns) 214 | 0.998 R² (0.997 R² .. 1.000 R²) 215 | mean 74.65 ns (73.80 ns .. 76.24 ns) 216 | std dev 3.635 ns (2.332 ns .. 5.806 ns) 217 | variance introduced by outliers: 70% (severely inflated) 218 | 219 | benchmarking Buildable (Integer)/-99 220 | time 92.44 ns (91.18 ns .. 93.94 ns) 221 | 0.998 R² (0.997 R² .. 0.999 R²) 222 | mean 93.56 ns (92.03 ns .. 96.69 ns) 223 | std dev 6.996 ns (3.089 ns .. 11.68 ns) 224 | variance introduced by outliers: 85% (severely inflated) 225 | 226 | benchmarking Buildable (Integer)/100 227 | time 108.6 ns (107.6 ns .. 110.2 ns) 228 | 0.998 R² (0.996 R² .. 1.000 R²) 229 | mean 109.2 ns (108.0 ns .. 112.1 ns) 230 | std dev 5.557 ns (3.105 ns .. 10.77 ns) 231 | variance introduced by outliers: 71% (severely inflated) 232 | 233 | benchmarking Buildable (Integer)/123 234 | time 108.6 ns (107.2 ns .. 110.6 ns) 235 | 0.998 R² (0.997 R² .. 1.000 R²) 236 | mean 108.1 ns (107.3 ns .. 109.5 ns) 237 | std dev 3.614 ns (2.089 ns .. 6.067 ns) 238 | variance introduced by outliers: 51% (severely inflated) 239 | 240 | benchmarking Buildable (Integer)/12345678 241 | time 291.4 ns (286.3 ns .. 297.3 ns) 242 | 0.998 R² (0.997 R² .. 1.000 R²) 243 | mean 291.1 ns (288.0 ns .. 298.2 ns) 244 | std dev 15.30 ns (7.961 ns .. 28.29 ns) 245 | variance introduced by outliers: 71% (severely inflated) 246 | 247 | benchmarking Buildable (Integer)/9223372036854775807 248 | time 681.5 ns (675.0 ns .. 692.1 ns) 249 | 0.999 R² (0.998 R² .. 1.000 R²) 250 | mean 682.5 ns (677.1 ns .. 691.1 ns) 251 | std dev 21.70 ns (15.40 ns .. 30.10 ns) 252 | variance introduced by outliers: 45% (moderately inflated) 253 | 254 | benchmarking Buildable (Integer)/-9223372036854775807 255 | time 710.3 ns (700.6 ns .. 722.6 ns) 256 | 0.998 R² (0.997 R² .. 1.000 R²) 257 | mean 715.2 ns (704.2 ns .. 738.8 ns) 258 | std dev 53.08 ns (30.72 ns .. 88.99 ns) 259 | variance introduced by outliers: 82% (severely inflated) 260 | 261 | benchmarking Buildable (Integer)/18446744073709551614 262 | time 1.216 μs (1.200 μs .. 1.241 μs) 263 | 0.992 R² (0.976 R² .. 0.999 R²) 264 | mean 1.247 μs (1.214 μs .. 1.372 μs) 265 | std dev 190.4 ns (48.38 ns .. 387.9 ns) 266 | variance introduced by outliers: 95% (severely inflated) 267 | 268 | Benchmark bench: FINISH 269 | -------------------------------------------------------------------------------- /bench/reports/7.1.2.txt: -------------------------------------------------------------------------------- 1 | Build profile: -w ghc-8.10.4 -O1 2 | In order, the following will be built (use -v for more details): 3 | - formatting-7.1.2 (lib) (configuration changed) 4 | - formatting-7.1.2 (bench:bench) (configuration changed) 5 | Configuring library for formatting-7.1.2.. 6 | Preprocessing library for formatting-7.1.2.. 7 | Building library for formatting-7.1.2.. 8 | Configuring benchmark 'bench' for formatting-7.1.2.. 9 | Preprocessing benchmark 'bench' for formatting-7.1.2.. 10 | Building benchmark 'bench' for formatting-7.1.2.. 11 | Linking /home/cha748/src/formatting/dist-newstyle/build/x86_64-linux/ghc-8.10.4/formatting-7.1.2/b/bench/build/bench/bench ... 12 | Running 1 benchmarks... 13 | Benchmark bench: RUNNING... 14 | benchmarking Small Strings 15 | time 583.5 ns (576.4 ns .. 592.8 ns) 16 | 0.998 R² (0.996 R² .. 1.000 R²) 17 | mean 585.3 ns (579.3 ns .. 600.3 ns) 18 | std dev 28.93 ns (14.84 ns .. 54.49 ns) 19 | variance introduced by outliers: 67% (severely inflated) 20 | 21 | benchmarking Small Text 22 | time 204.2 ns (201.5 ns .. 207.4 ns) 23 | 0.998 R² (0.997 R² .. 0.999 R²) 24 | mean 203.8 ns (201.5 ns .. 207.5 ns) 25 | std dev 9.990 ns (7.055 ns .. 13.26 ns) 26 | variance introduced by outliers: 69% (severely inflated) 27 | 28 | benchmarking Small Lazy Text 29 | time 202.9 ns (200.6 ns .. 205.5 ns) 30 | 0.998 R² (0.996 R² .. 0.999 R²) 31 | mean 204.7 ns (201.9 ns .. 211.8 ns) 32 | std dev 14.06 ns (6.717 ns .. 26.61 ns) 33 | variance introduced by outliers: 81% (severely inflated) 34 | 35 | benchmarking Multiple Interpolations String 36 | time 1.036 μs (1.018 μs .. 1.058 μs) 37 | 0.998 R² (0.996 R² .. 1.000 R²) 38 | mean 1.032 μs (1.019 μs .. 1.054 μs) 39 | std dev 57.70 ns (32.33 ns .. 90.40 ns) 40 | variance introduced by outliers: 71% (severely inflated) 41 | 42 | benchmarking Multiple Interpolations Text 43 | time 527.4 ns (520.3 ns .. 537.1 ns) 44 | 0.992 R² (0.980 R² .. 0.999 R²) 45 | mean 554.6 ns (530.7 ns .. 608.8 ns) 46 | std dev 120.1 ns (48.43 ns .. 199.1 ns) 47 | variance introduced by outliers: 98% (severely inflated) 48 | 49 | benchmarking Multiple Interpolations Lazy Text 50 | time 533.6 ns (526.9 ns .. 540.6 ns) 51 | 0.998 R² (0.997 R² .. 0.999 R²) 52 | mean 531.0 ns (525.0 ns .. 540.1 ns) 53 | std dev 24.53 ns (18.20 ns .. 32.31 ns) 54 | variance introduced by outliers: 64% (severely inflated) 55 | 56 | benchmarking Largeish Text 57 | time 10.53 μs (10.38 μs .. 10.70 μs) 58 | 0.997 R² (0.995 R² .. 0.999 R²) 59 | mean 10.37 μs (10.23 μs .. 10.57 μs) 60 | std dev 552.9 ns (426.4 ns .. 779.7 ns) 61 | variance introduced by outliers: 64% (severely inflated) 62 | 63 | benchmarking Largeish Lazy Text 64 | time 347.9 ns (342.8 ns .. 354.1 ns) 65 | 0.998 R² (0.996 R² .. 0.999 R²) 66 | mean 350.1 ns (345.4 ns .. 360.4 ns) 67 | std dev 22.70 ns (13.91 ns .. 39.16 ns) 68 | variance introduced by outliers: 79% (severely inflated) 69 | 70 | benchmarking Integers/0 71 | time 110.6 ns (108.9 ns .. 112.4 ns) 72 | 0.998 R² (0.997 R² .. 0.999 R²) 73 | mean 110.4 ns (108.8 ns .. 112.7 ns) 74 | std dev 6.458 ns (4.718 ns .. 8.752 ns) 75 | variance introduced by outliers: 77% (severely inflated) 76 | 77 | benchmarking Integers/1 78 | time 119.7 ns (117.3 ns .. 121.9 ns) 79 | 0.998 R² (0.996 R² .. 0.999 R²) 80 | mean 119.3 ns (117.4 ns .. 124.4 ns) 81 | std dev 9.782 ns (4.609 ns .. 18.77 ns) 82 | variance introduced by outliers: 87% (severely inflated) 83 | 84 | benchmarking Integers/-1 85 | time 123.9 ns (122.4 ns .. 125.7 ns) 86 | 0.998 R² (0.995 R² .. 0.999 R²) 87 | mean 124.2 ns (122.8 ns .. 127.4 ns) 88 | std dev 6.763 ns (3.197 ns .. 12.50 ns) 89 | variance introduced by outliers: 74% (severely inflated) 90 | 91 | benchmarking Integers/10 92 | time 196.6 ns (192.4 ns .. 201.2 ns) 93 | 0.997 R² (0.996 R² .. 0.998 R²) 94 | mean 196.0 ns (194.0 ns .. 199.0 ns) 95 | std dev 8.231 ns (6.320 ns .. 11.79 ns) 96 | variance introduced by outliers: 61% (severely inflated) 97 | 98 | benchmarking Integers/-10 99 | time 204.0 ns (201.6 ns .. 206.4 ns) 100 | 0.999 R² (0.997 R² .. 0.999 R²) 101 | mean 204.5 ns (202.0 ns .. 209.4 ns) 102 | std dev 11.37 ns (6.571 ns .. 21.04 ns) 103 | variance introduced by outliers: 74% (severely inflated) 104 | 105 | benchmarking Integers/99 106 | time 190.2 ns (187.9 ns .. 193.2 ns) 107 | 0.998 R² (0.997 R² .. 0.999 R²) 108 | mean 192.4 ns (190.3 ns .. 195.5 ns) 109 | std dev 8.868 ns (5.711 ns .. 14.17 ns) 110 | variance introduced by outliers: 66% (severely inflated) 111 | 112 | benchmarking Integers/-99 113 | time 201.6 ns (197.4 ns .. 206.0 ns) 114 | 0.997 R² (0.996 R² .. 0.999 R²) 115 | mean 201.1 ns (198.0 ns .. 207.1 ns) 116 | std dev 13.28 ns (8.275 ns .. 21.97 ns) 117 | variance introduced by outliers: 80% (severely inflated) 118 | 119 | benchmarking Integers/100 120 | time 242.4 ns (238.8 ns .. 246.4 ns) 121 | 0.998 R² (0.997 R² .. 0.999 R²) 122 | mean 241.4 ns (238.8 ns .. 245.0 ns) 123 | std dev 10.27 ns (7.828 ns .. 12.68 ns) 124 | variance introduced by outliers: 62% (severely inflated) 125 | 126 | benchmarking Integers/123 127 | time 235.5 ns (232.0 ns .. 240.1 ns) 128 | 0.998 R² (0.996 R² .. 0.999 R²) 129 | mean 240.3 ns (237.3 ns .. 244.0 ns) 130 | std dev 11.14 ns (8.676 ns .. 14.40 ns) 131 | variance introduced by outliers: 66% (severely inflated) 132 | 133 | benchmarking Integers/12345678 134 | time 522.9 ns (514.7 ns .. 529.1 ns) 135 | 0.998 R² (0.997 R² .. 0.999 R²) 136 | mean 517.6 ns (511.5 ns .. 529.3 ns) 137 | std dev 27.41 ns (15.22 ns .. 49.14 ns) 138 | variance introduced by outliers: 70% (severely inflated) 139 | 140 | benchmarking Integers/9223372036854775807 141 | time 1.117 μs (1.098 μs .. 1.139 μs) 142 | 0.998 R² (0.996 R² .. 0.999 R²) 143 | mean 1.118 μs (1.104 μs .. 1.135 μs) 144 | std dev 52.89 ns (38.83 ns .. 70.49 ns) 145 | variance introduced by outliers: 64% (severely inflated) 146 | 147 | benchmarking Integers/-9223372036854775807 148 | time 1.110 μs (1.095 μs .. 1.130 μs) 149 | 0.998 R² (0.997 R² .. 0.999 R²) 150 | mean 1.125 μs (1.111 μs .. 1.164 μs) 151 | std dev 72.29 ns (34.13 ns .. 149.8 ns) 152 | variance introduced by outliers: 76% (severely inflated) 153 | 154 | benchmarking Integers/18446744073709551614 155 | time 1.217 μs (1.194 μs .. 1.246 μs) 156 | 0.994 R² (0.984 R² .. 0.999 R²) 157 | mean 1.202 μs (1.180 μs .. 1.261 μs) 158 | std dev 102.3 ns (47.90 ns .. 193.2 ns) 159 | variance introduced by outliers: 85% (severely inflated) 160 | 161 | benchmarking Buildable (Integer)/0 162 | time 31.41 ns (30.86 ns .. 31.92 ns) 163 | 0.998 R² (0.997 R² .. 0.999 R²) 164 | mean 31.26 ns (30.79 ns .. 32.52 ns) 165 | std dev 2.434 ns (1.153 ns .. 4.595 ns) 166 | variance introduced by outliers: 87% (severely inflated) 167 | 168 | benchmarking Buildable (Integer)/1 169 | time 30.26 ns (29.89 ns .. 30.72 ns) 170 | 0.998 R² (0.997 R² .. 1.000 R²) 171 | mean 30.51 ns (30.18 ns .. 31.14 ns) 172 | std dev 1.478 ns (966.5 ps .. 2.701 ns) 173 | variance introduced by outliers: 71% (severely inflated) 174 | 175 | benchmarking Buildable (Integer)/-1 176 | time 37.22 ns (36.76 ns .. 37.77 ns) 177 | 0.999 R² (0.998 R² .. 0.999 R²) 178 | mean 36.98 ns (36.66 ns .. 37.48 ns) 179 | std dev 1.312 ns (922.2 ps .. 1.821 ns) 180 | variance introduced by outliers: 57% (severely inflated) 181 | 182 | benchmarking Buildable (Integer)/10 183 | time 39.61 ns (39.30 ns .. 39.99 ns) 184 | 0.999 R² (0.998 R² .. 1.000 R²) 185 | mean 39.97 ns (39.59 ns .. 40.85 ns) 186 | std dev 1.934 ns (820.9 ps .. 3.765 ns) 187 | variance introduced by outliers: 71% (severely inflated) 188 | 189 | benchmarking Buildable (Integer)/-10 190 | time 40.11 ns (39.62 ns .. 40.80 ns) 191 | 0.999 R² (0.997 R² .. 1.000 R²) 192 | mean 40.08 ns (39.76 ns .. 40.82 ns) 193 | std dev 1.460 ns (800.3 ps .. 2.784 ns) 194 | variance introduced by outliers: 58% (severely inflated) 195 | 196 | benchmarking Buildable (Integer)/99 197 | time 39.84 ns (39.22 ns .. 40.53 ns) 198 | 0.998 R² (0.996 R² .. 0.999 R²) 199 | mean 39.96 ns (39.37 ns .. 41.63 ns) 200 | std dev 2.891 ns (1.214 ns .. 5.943 ns) 201 | variance introduced by outliers: 84% (severely inflated) 202 | 203 | benchmarking Buildable (Integer)/-99 204 | time 40.10 ns (39.61 ns .. 40.79 ns) 205 | 0.998 R² (0.997 R² .. 1.000 R²) 206 | mean 40.27 ns (39.88 ns .. 41.09 ns) 207 | std dev 1.854 ns (1.134 ns .. 3.046 ns) 208 | variance introduced by outliers: 69% (severely inflated) 209 | 210 | benchmarking Buildable (Integer)/100 211 | time 45.65 ns (44.88 ns .. 46.44 ns) 212 | 0.998 R² (0.996 R² .. 0.999 R²) 213 | mean 45.85 ns (45.04 ns .. 48.62 ns) 214 | std dev 4.639 ns (1.452 ns .. 9.504 ns) 215 | variance introduced by outliers: 92% (severely inflated) 216 | 217 | benchmarking Buildable (Integer)/123 218 | time 45.27 ns (44.96 ns .. 45.64 ns) 219 | 0.999 R² (0.999 R² .. 1.000 R²) 220 | mean 45.65 ns (45.28 ns .. 46.39 ns) 221 | std dev 1.671 ns (879.3 ps .. 3.313 ns) 222 | variance introduced by outliers: 58% (severely inflated) 223 | 224 | benchmarking Buildable (Integer)/12345678 225 | time 71.75 ns (70.82 ns .. 72.90 ns) 226 | 0.997 R² (0.995 R² .. 0.999 R²) 227 | mean 74.55 ns (72.66 ns .. 78.92 ns) 228 | std dev 9.241 ns (5.009 ns .. 16.46 ns) 229 | variance introduced by outliers: 94% (severely inflated) 230 | 231 | benchmarking Buildable (Integer)/9223372036854775807 232 | time 153.2 ns (152.2 ns .. 154.2 ns) 233 | 1.000 R² (0.999 R² .. 1.000 R²) 234 | mean 152.8 ns (151.9 ns .. 154.3 ns) 235 | std dev 3.926 ns (2.740 ns .. 6.211 ns) 236 | variance introduced by outliers: 38% (moderately inflated) 237 | 238 | benchmarking Buildable (Integer)/-9223372036854775807 239 | time 154.3 ns (153.4 ns .. 155.2 ns) 240 | 1.000 R² (1.000 R² .. 1.000 R²) 241 | mean 154.1 ns (153.3 ns .. 155.0 ns) 242 | std dev 2.738 ns (2.218 ns .. 3.600 ns) 243 | variance introduced by outliers: 22% (moderately inflated) 244 | 245 | benchmarking Buildable (Integer)/18446744073709551614 246 | time 1.186 μs (1.168 μs .. 1.208 μs) 247 | 0.998 R² (0.996 R² .. 1.000 R²) 248 | mean 1.179 μs (1.170 μs .. 1.198 μs) 249 | std dev 44.30 ns (27.20 ns .. 74.79 ns) 250 | variance introduced by outliers: 52% (severely inflated) 251 | 252 | Benchmark bench: FINISH 253 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ./pinned-nixpkgs.nix 2 | , compiler ? "default" 3 | , doBenchmark ? false 4 | , doTest ? false 5 | , doProfiling ? true 6 | }: 7 | 8 | let 9 | 10 | inherit pkgs; 11 | 12 | haskellPackages = if compiler == "default" 13 | then pkgs.haskellPackages 14 | else pkgs.haskell.compiler.${compiler}; 15 | 16 | modifiedHaskellPackages = import ./haskell-package-overrides.nix { haskellLib = pkgs.haskell.lib; inherit haskellPackages; }; 17 | 18 | withBench = p: if doBenchmark 19 | then pkgs.haskell.lib.doBenchmark p 20 | else p; 21 | 22 | withTest = p: if doTest 23 | then p 24 | else pkgs.haskell.lib.dontCheck p; 25 | 26 | withProfiling = p: if doProfiling 27 | then pkgs.haskell.lib.enableLibraryProfiling ( pkgs.haskell.lib.enableExecutableProfiling p) 28 | else p; 29 | 30 | pkgname = import ./pkgname.nix; 31 | pkg = modifiedHaskellPackages.callCabal2nix pkgname ./. { }; 32 | 33 | standardata-static = pkgs.haskell.lib.justStaticExecutables pkg; 34 | 35 | tags = import (fetchTarball "https://github.com/tek/thax/tarball/9ac46dfef0a99a74e65c838a89d0bbab00170d8b") { inherit pkgs; }; 36 | 37 | addons = with pkgs; { 38 | projectTags = tags.combined.all { targets = [pkg]; }; 39 | }; 40 | 41 | in 42 | 43 | withProfiling ( withBench ( withTest (pkg) ) ) // addons 44 | -------------------------------------------------------------------------------- /formatting.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: formatting 3 | version: 7.2.0 4 | synopsis: Combinator-based type-safe formatting (like printf() or FORMAT) 5 | description: Combinator-based type-safe formatting (like printf() or FORMAT), modelled from the HoleyMonoids package. 6 | . 7 | See the README at for more info. 8 | homepage: https://github.com/AJChapman/formatting#readme 9 | bug-reports: https://github.com/AJChapman/formatting/issues 10 | license: BSD-3-Clause 11 | license-file: LICENSE 12 | author: Chris Done, Shachaf Ben-Kiki, Martijn van Steenbergen, Mike Meyer, Bryan O'Sullivan, Alex Chapman 13 | maintainer: alex@farfromthere.net 14 | copyright: 2020 Alex Chapman, 2013 Chris Done, Shachaf Ben-Kiki, Martijn van Steenbergen, Mike Meyer, 2011 MailRank, Inc. 15 | category: Text 16 | build-type: Simple 17 | extra-source-files: CHANGELOG.md 18 | README.md 19 | tested-with: GHC == 8.4.4 20 | , GHC == 8.6.5 21 | , GHC == 8.8.4 22 | , GHC == 8.10.7 23 | , GHC == 9.0.2 24 | , GHC == 9.2.2 25 | 26 | common deps 27 | build-depends: 28 | base >= 4.11 && < 5, 29 | text >= 0.11.0.8 30 | 31 | -- Warnings list list taken from 32 | -- https://medium.com/mercury-bank/enable-all-the-warnings-a0517bc081c3 33 | -- Enable all warnings with -Weverything, then disable the ones we 34 | -- don’t care about 35 | default-language: Haskell2010 36 | ghc-options: -Weverything 37 | -Wno-all-missed-specialisations 38 | -Wno-implicit-prelude 39 | -Wno-missed-specialisations 40 | -Wno-missing-exported-signatures 41 | -Wno-missing-import-lists 42 | -Wno-missing-local-signatures 43 | -Wno-monomorphism-restriction 44 | -Wno-missing-deriving-strategies 45 | -Wno-safe 46 | -Wno-unsafe 47 | -fprint-potential-instances 48 | if impl(ghc >= 8.10) 49 | ghc-options: -Wno-prepositive-qualified-module 50 | -Wno-missing-safe-haskell-mode 51 | 52 | flag no-double-conversion 53 | description: Avoid 'double-conversion' dependency, which is large and uses C code 54 | manual: False 55 | default: False 56 | 57 | library 58 | import: deps 59 | hs-source-dirs: src 60 | build-depends: 61 | clock >= 0.4, 62 | old-locale, 63 | scientific >= 0.3.0.0, 64 | time >= 1.5, 65 | transformers, 66 | if !impl(ghcjs) && !flag(no-double-conversion) 67 | build-depends: 68 | double-conversion ^>= 2.0.2.0, 69 | exposed-modules: 70 | Formatting 71 | Formatting.Formatters 72 | Formatting.ShortFormatters 73 | Formatting.Combinators 74 | Formatting.Examples 75 | Formatting.Time 76 | Formatting.Clock 77 | Formatting.Internal 78 | Formatting.Internal.Raw 79 | Formatting.Buildable 80 | Formatting.FromBuilder 81 | other-modules: 82 | Data.Text.Format.Functions 83 | Data.Text.Format.Types 84 | Data.Text.Format 85 | 86 | test-suite formatting-test 87 | import: deps 88 | build-depends: formatting, hspec, scientific, time 89 | type: exitcode-stdio-1.0 90 | hs-source-dirs: test 91 | main-is: Spec.hs 92 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 93 | 94 | benchmark bench 95 | import: deps 96 | type: exitcode-stdio-1.0 97 | hs-source-dirs: bench 98 | main-is: bench.hs 99 | build-depends: formatting, criterion, QuickCheck 100 | ghc-options: -O2 101 | 102 | source-repository head 103 | type: git 104 | location: http://github.com/AJChapman/formatting 105 | -------------------------------------------------------------------------------- /haskell-package-overrides.nix: -------------------------------------------------------------------------------- 1 | { haskellLib 2 | , haskellPackages }: 3 | haskellPackages.override { 4 | overrides = self: super: { 5 | # Put any overrides here, e.g: 6 | # 7 | # Jailbreak a certain package: 8 | # pandoc-lens = haskellLib.doJailbreak super.pandoc-lens; 9 | # 10 | # Use a local copy of another package: 11 | # pandoc-wrapper = super.callPackage ../pandoc-wrapper/pkg.nix { }; 12 | 13 | # Fix some broken stuff 14 | haskell-ci = haskellLib.unmarkBroken super.haskell-ci; 15 | lattices = haskellLib.unmarkBroken super.lattices; 16 | universe-reverse-instances = haskellLib.unmarkBroken super.universe-reverse-instances; 17 | zinza = haskellLib.doJailbreak (haskellLib.unmarkBroken super.zinza); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /nixpkgs.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/NixOS/nixpkgs.git", 3 | "rev": "7837e812d2eaed0f6648999a2cf505b69418428e", 4 | "date": "2022-11-19T10:45:24+08:00", 5 | "path": "/nix/store/lms0iajxjfjkjr89npzk2igps0a21w2k-nixpkgs", 6 | "sha256": "1z2kr1jfj0yajcynrs9kl0lf9hpaygnnc3j2g6dfgyjysm55psyg", 7 | "fetchLFS": false, 8 | "fetchSubmodules": false, 9 | "deepClone": false, 10 | "leaveDotGit": false 11 | } 12 | -------------------------------------------------------------------------------- /pinned-nixpkgs.nix: -------------------------------------------------------------------------------- 1 | # To pin to a newer version of nixpkgs, pick the revision of nixpkgs and run: 2 | # nix-prefetch-git --rev --url https://github.com/NixOS/nixpkgs.git > nixpkgs.json 3 | let 4 | fetcher = { rev, sha256, ... }: builtins.fetchTarball { 5 | inherit sha256; 6 | url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz"; 7 | }; 8 | in 9 | import (fetcher (builtins.fromJSON (builtins.readFile ./nixpkgs.json))) 10 | { 11 | config.allowUnfree = true; 12 | } 13 | -------------------------------------------------------------------------------- /pkgname.nix: -------------------------------------------------------------------------------- 1 | "formatting" 2 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ./pinned-nixpkgs.nix 2 | , compiler ? "default" 3 | , doBenchmark ? false 4 | , doTest ? true 5 | }: 6 | 7 | let 8 | inherit pkgs; 9 | 10 | haskellPackages = if compiler == "default" 11 | then pkgs.haskellPackages 12 | else pkgs.haskell.packages.${compiler}; 13 | modifiedHaskellPackages = import ./haskell-package-overrides.nix { haskellLib = pkgs.haskell.lib; inherit haskellPackages; }; 14 | 15 | drv = import ./default.nix { inherit pkgs compiler doBenchmark doTest; }; 16 | drvWithTools = pkgs.haskell.lib.addBuildDepends drv (with pkgs; [ 17 | cabal-install ghcid modifiedHaskellPackages.pretty-simple modifiedHaskellPackages.weeder modifiedHaskellPackages.doctest modifiedHaskellPackages.haskell-ci modifiedHaskellPackages.hkgr 18 | ]); 19 | in 20 | if pkgs.lib.inNixShell then drvWithTools.env else drv 21 | -------------------------------------------------------------------------------- /src/Data/Text/Format.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RelaxedPolyRec #-} 2 | {-# LANGUAGE CPP #-} 3 | 4 | -- | 5 | -- Module : Data.Text.Format 6 | -- Copyright : (c) 2011 MailRank, Inc. 7 | -- 8 | -- License : BSD-style 9 | -- Maintainer : bos@serpentine.com 10 | -- Stability : experimental 11 | -- Portability : GHC 12 | -- 13 | -- Fast, efficient, flexible support for formatting text strings. 14 | 15 | module Data.Text.Format 16 | ( 17 | -- * Format control 18 | left 19 | , right 20 | -- ** Integers 21 | , hex 22 | -- ** Floating point numbers 23 | , fixed 24 | , shortest 25 | ) where 26 | 27 | #ifdef MIN_VERSION_double_conversion 28 | import Data.Double.Conversion.Text (toFixed, toShortest) 29 | #else 30 | import Numeric (showFFloat, showInt) 31 | #endif 32 | import qualified Formatting.Buildable as B 33 | import Data.Text.Format.Types (Hex(..)) 34 | import qualified Data.Text.Lazy as LT 35 | import Data.Text.Lazy.Builder 36 | import Prelude hiding (exp, print) 37 | 38 | -- | Pad the left hand side of a string until it reaches @k@ 39 | -- characters wide, if necessary filling with character @c@. 40 | left :: B.Buildable a => Int -> Char -> a -> Builder 41 | left k c = 42 | fromLazyText . LT.justifyRight (fromIntegral k) c . toLazyText . B.build 43 | 44 | -- | Pad the right hand side of a string until it reaches @k@ 45 | -- characters wide, if necessary filling with character @c@. 46 | right :: B.Buildable a => Int -> Char -> a -> Builder 47 | right k c = 48 | fromLazyText . LT.justifyLeft (fromIntegral k) c . toLazyText . B.build 49 | 50 | -- | Render a floating point number using normal notation, with the 51 | -- given number of decimal places. 52 | fixed :: (Real a) => 53 | Int 54 | -- ^ Number of digits of precision after the decimal. 55 | -> a -> Builder 56 | #ifdef MIN_VERSION_double_conversion 57 | fixed decs = fromText . toFixed decs . realToFrac 58 | #else 59 | fixed decs = fromString . toFixed . realToFrac 60 | where 61 | toFixed :: Double -> String 62 | toFixed dbl = showFFloat (Just decs) dbl "" 63 | #endif 64 | {-# NOINLINE[0] fixed #-} 65 | 66 | -- | Render a floating point number using the smallest number of 67 | -- digits that correctly represent it. 68 | shortest :: Real a => a -> Builder 69 | #ifdef MIN_VERSION_double_conversion 70 | shortest = fromText . toShortest . realToFrac 71 | #else 72 | shortest = fromString . toShortest . realToFrac 73 | where 74 | toShortest :: Double -> String 75 | toShortest dbl = 76 | -- `showFFloat (Just 0) "" 1.0` gives "1.", but we want "1" 77 | let intPart = (floor dbl :: Int) in 78 | if dbl == (fromIntegral intPart) 79 | then showInt intPart "" 80 | else showFFloat Nothing dbl "" 81 | #endif 82 | {-# INLINE shortest #-} 83 | 84 | -- | Render an integer using hexadecimal notation. (No leading "0x" 85 | -- is added.) 86 | hex :: Integral a => a -> Builder 87 | hex = B.build . Hex 88 | {-# INLINE hex #-} 89 | -------------------------------------------------------------------------------- /src/Data/Text/Format/Functions.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE MagicHash #-} 2 | 3 | -- | 4 | -- Module : Data.Text.Format.Functions 5 | -- Copyright : (c) 2011 MailRank, Inc. 6 | -- 7 | -- License : BSD-style 8 | -- Maintainer : bos@serpentine.com 9 | -- Stability : experimental 10 | -- Portability : GHC 11 | -- 12 | -- Useful functions and combinators. 13 | 14 | module Data.Text.Format.Functions 15 | ( (<>) 16 | , i2d 17 | ) where 18 | 19 | import Data.Text.Lazy.Builder (Builder) 20 | import Prelude hiding ((<>)) 21 | import GHC.Base hiding ((<>)) 22 | 23 | -- | Unsafe conversion for decimal digits. 24 | {-# INLINE i2d #-} 25 | i2d :: Int -> Char 26 | i2d (I# i#) = C# (chr# (ord# '0'# +# i#)) 27 | 28 | -- | The normal 'mappend' function with right associativity instead of 29 | -- left. 30 | (<>) :: Builder -> Builder -> Builder 31 | (<>) = mappend 32 | {-# INLINE (<>) #-} 33 | 34 | infixr 4 <> 35 | -------------------------------------------------------------------------------- /src/Data/Text/Format/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 2 | 3 | -- | 4 | -- Module : Data.Text.Format.Types.Internal 5 | -- Copyright : (c) 2011 MailRank, Inc. 6 | -- 7 | -- License : BSD-style 8 | -- Maintainer : bos@serpentine.com 9 | -- Stability : experimental 10 | -- Portability : GHC 11 | -- 12 | -- Types for text mangling. 13 | 14 | module Data.Text.Format.Types 15 | ( Shown(..) 16 | -- * Integer format control 17 | , Hex(..) 18 | ) where 19 | 20 | -- | Render an integral type in hexadecimal. 21 | newtype Hex a = Hex a 22 | deriving (Eq, Ord, Read, Show, Num, Real, Enum, Integral) 23 | 24 | -- | Render a value using its 'Show' instance. 25 | newtype Shown a = Shown { 26 | shown :: a 27 | } deriving (Eq, Show, Read, Ord, Num, Fractional, Real, RealFrac, 28 | Floating, RealFloat, Enum, Integral, Bounded) 29 | -------------------------------------------------------------------------------- /src/Formatting.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# OPTIONS -Wall #-} 3 | 4 | 5 | -- | 6 | -- Module : Text.Format 7 | -- Copyright : (c) 2013 Chris Done, 2013 Shachaf Ben-Kiki 8 | -- License : BSD3 9 | -- Maintainer : alex@farfromthere.net 10 | -- Stability : experimental 11 | -- Portability : GHC 12 | -- 13 | -- Combinator-based type-safe formatting (like printf() or FORMAT) for Text. 14 | -- 15 | -- Example: 16 | -- 17 | -- >>> format ("Person's name is " % text % ", age is " % hex) "Dave" 54 18 | -- "Person's name is Dave, age is 36" 19 | -- 20 | -- See "Formatting.Formatters" for a list of formatters. 21 | -- See "Formatting.Combinators" for a list of formatting combinators, for combining and altering formatters. 22 | 23 | module Formatting 24 | ( 25 | Format, 26 | (%), 27 | (%+), 28 | (%.), 29 | (<%+>), 30 | now, 31 | later, 32 | mapf, 33 | -- * Top-level functions 34 | runFormat, 35 | format, 36 | sformat, 37 | bprint, 38 | bformat, 39 | fprint, 40 | fprintLn, 41 | hprint, 42 | hprintLn, 43 | formatToString, 44 | formatted, 45 | -- * Formatting library 46 | module Formatting.Formatters, 47 | module Formatting.Combinators 48 | ) where 49 | 50 | 51 | import Formatting.Formatters 52 | import Formatting.Combinators 53 | import Formatting.FromBuilder 54 | import Formatting.Internal 55 | 56 | -- $setup 57 | -- >>> :set -XOverloadedStrings 58 | -------------------------------------------------------------------------------- /src/Formatting/Buildable.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, FlexibleInstances, OverloadedStrings #-} 2 | 3 | -- | 4 | -- Module : Data.Text.Buildable 5 | -- Copyright : (c) 2011 MailRank, Inc. 6 | -- 7 | -- License : BSD-style 8 | -- Maintainer : bos@serpentine.com 9 | -- Stability : experimental 10 | -- Portability : GHC 11 | -- 12 | -- Types that can be rendered to a 'Builder'. 13 | 14 | module Formatting.Buildable 15 | ( 16 | Buildable(..) 17 | ) where 18 | 19 | import Data.Int (Int8, Int16, Int32, Int64) 20 | import Data.Fixed (Fixed, HasResolution, showFixed) 21 | import Data.List (intersperse) 22 | import Data.Ratio (Ratio, denominator, numerator) 23 | import qualified Data.Text.Format.Functions as F ((<>)) 24 | import Data.Text.Lazy.Builder.Int (decimal, hexadecimal) 25 | import Data.Text.Format.Types (Hex(..), Shown(..)) 26 | import Data.Text.Lazy.Builder 27 | import Data.Time.Calendar (Day, showGregorian) 28 | import Data.Time.Clock (getModJulianDate, DiffTime, NominalDiffTime, UTCTime, UniversalTime) 29 | import Data.Time.LocalTime (LocalTime, TimeOfDay, TimeZone, ZonedTime) 30 | import Data.Void (Void, absurd) 31 | import Data.Word (Word8, Word16, Word32, Word64) 32 | import Foreign.Ptr (IntPtr, WordPtr, Ptr, ptrToWordPtr) 33 | import qualified Data.Text as ST 34 | import qualified Data.Text.Lazy as LT 35 | 36 | -- | The class of types that can be rendered to a 'Builder'. 37 | class Buildable p where 38 | build :: p -> Builder 39 | 40 | instance Buildable Builder where 41 | build = id 42 | 43 | instance Buildable Void where 44 | build = absurd 45 | 46 | instance Buildable LT.Text where 47 | build = fromLazyText 48 | {-# INLINE build #-} 49 | 50 | instance Buildable ST.Text where 51 | build = fromText 52 | {-# INLINE build #-} 53 | 54 | instance Buildable Char where 55 | build = singleton 56 | {-# INLINE build #-} 57 | 58 | instance Buildable [Char] where 59 | build = fromString 60 | {-# INLINE build #-} 61 | 62 | instance (Integral a) => Buildable (Hex a) where 63 | build = hexadecimal 64 | {-# INLINE build #-} 65 | 66 | instance Buildable Int8 where 67 | build = decimal 68 | {-# INLINE build #-} 69 | 70 | instance Buildable Int16 where 71 | build = decimal 72 | {-# INLINE build #-} 73 | 74 | instance Buildable Int32 where 75 | build = decimal 76 | {-# INLINE build #-} 77 | 78 | instance Buildable Int where 79 | build = decimal 80 | {-# INLINE build #-} 81 | 82 | instance Buildable Int64 where 83 | build = decimal 84 | {-# INLINE build #-} 85 | 86 | instance Buildable Integer where 87 | build = decimal 88 | {-# INLINE build #-} 89 | 90 | instance (HasResolution a) => Buildable (Fixed a) where 91 | build = build . showFixed False 92 | {-# INLINE build #-} 93 | 94 | instance Buildable Word8 where 95 | build = decimal 96 | {-# INLINE build #-} 97 | 98 | instance Buildable Word16 where 99 | build = decimal 100 | {-# INLINE build #-} 101 | 102 | instance Buildable Word32 where 103 | build = decimal 104 | {-# INLINE build #-} 105 | 106 | instance Buildable Word where 107 | build = decimal 108 | {-# INLINE build #-} 109 | 110 | instance Buildable Word64 where 111 | build = decimal 112 | {-# INLINE build #-} 113 | 114 | instance Buildable a => Buildable (Ratio a) where 115 | {-# SPECIALIZE instance Buildable (Ratio Integer) #-} 116 | build a = build (numerator a) F.<> singleton '/' F.<> build (denominator a) 117 | 118 | instance Buildable Float where 119 | build = build . show 120 | {-# INLINE build #-} 121 | 122 | instance Buildable Double where 123 | build = build . show 124 | {-# INLINE build #-} 125 | 126 | instance Buildable DiffTime where 127 | build = build . Shown 128 | {-# INLINE build #-} 129 | 130 | instance Buildable NominalDiffTime where 131 | build = build . Shown 132 | {-# INLINE build #-} 133 | 134 | instance Buildable UTCTime where 135 | build = build . Shown 136 | {-# INLINE build #-} 137 | 138 | instance Buildable UniversalTime where 139 | build = build . Shown . getModJulianDate 140 | {-# INLINE build #-} 141 | 142 | instance Buildable Day where 143 | build = fromString . showGregorian 144 | {-# INLINE build #-} 145 | 146 | instance (Show a) => Buildable (Shown a) where 147 | build = fromString . show . shown 148 | {-# INLINE build #-} 149 | 150 | instance (Buildable a) => Buildable (Maybe a) where 151 | build Nothing = mempty 152 | build (Just v) = build v 153 | {-# INLINE build #-} 154 | 155 | instance Buildable TimeOfDay where 156 | build = build . Shown 157 | {-# INLINE build #-} 158 | 159 | instance Buildable TimeZone where 160 | build = build . Shown 161 | {-# INLINE build #-} 162 | 163 | instance Buildable LocalTime where 164 | build = build . Shown 165 | {-# INLINE build #-} 166 | 167 | instance Buildable ZonedTime where 168 | build = build . Shown 169 | {-# INLINE build #-} 170 | 171 | instance Buildable IntPtr where 172 | build p = fromText "0x" F.<> hexadecimal p 173 | 174 | instance Buildable WordPtr where 175 | build p = fromText "0x" F.<> hexadecimal p 176 | 177 | instance Buildable (Ptr a) where 178 | build = build . ptrToWordPtr 179 | 180 | instance Buildable Bool where 181 | build True = fromText "True" 182 | build False = fromText "False" 183 | 184 | instance {-# OVERLAPPABLE #-} Buildable a => Buildable [a] where 185 | build xs = "[" F.<> mconcat (intersperse "," (map build xs)) F.<> "]" 186 | {-# INLINE build #-} 187 | -------------------------------------------------------------------------------- /src/Formatting/Clock.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE PatternGuards #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# OPTIONS_GHC -fno-warn-type-defaults #-} 4 | 5 | -- | Formatters for high-res, real-time and timer clock values from "System.Clock". 6 | 7 | module Formatting.Clock (timeSpecs) where 8 | 9 | import Data.Text.Lazy.Builder 10 | import Formatting 11 | import Formatting.Internal 12 | import System.Clock 13 | 14 | fmt :: Integer -> Builder 15 | fmt diff 16 | | Just i <- scale ((10 ^ 9) * 60 * 60 * 24) = bprint (fixed 2 % " d") i 17 | | Just i <- scale ((10 ^ 9) * 60 * 60) = bprint (fixed 2 % " h") i 18 | | Just i <- scale ((10 ^ 9) * 60) = bprint (fixed 2 % " m") i 19 | | Just i <- scale (10 ^ 9) = bprint (fixed 2 % " s") i 20 | | Just i <- scale (10 ^ 6) = bprint (fixed 2 % " ms") i 21 | | Just i <- scale (10 ^ 3) = bprint (fixed 2 % " us") i 22 | | otherwise = bprint (int % " ns") diff 23 | where 24 | scale :: Integer -> Maybe Double 25 | scale i = 26 | if diff >= i 27 | then Just (fromIntegral diff / fromIntegral i) 28 | else Nothing 29 | 30 | -- | Same as @durationNS@ but works on `TimeSpec` from the clock package. 31 | timeSpecs :: Format r (TimeSpec -> TimeSpec -> r) 32 | timeSpecs = Format (\g x y -> g (fmt0 x y)) 33 | where 34 | fmt0 (TimeSpec s1 n1) (TimeSpec s2 n2) = fmt diff 35 | where 36 | diff :: Integer 37 | diff = a2 - a1 38 | a1 = (fromIntegral s1 * 10 ^ 9) + fromIntegral n1 39 | a2 = (fromIntegral s2 * 10 ^ 9) + fromIntegral n2 40 | -------------------------------------------------------------------------------- /src/Formatting/Combinators.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE LambdaCase #-} 3 | {-| 4 | Module : Formatting.Combinators 5 | Copyright : (c) 2020 Alex Chapman 6 | License : BSD3 7 | Maintainer : alex@farfromthere.net 8 | Stability : experimental 9 | Portability : GHC 10 | Description : Formatting combinators for building new formatters, with some useful pre-defined formatters. 11 | 12 | A formatting combinator takes a Format and returns another Format. 13 | Generally we want to change what the original format takes as its *input*, 14 | leaving the output polymorphic. 15 | Many of these combinators can be chained together to form a single 'Format'. 16 | 17 | Implementation detail: in order to be able to chain multiple combinators to make a single 'Format' we need them all to use the same intermediate string type, and we have chosen 'Builder'. 18 | This does not tie you to using 'Builder's, because the final output string type 'r' is still polymorphic. 19 | -} 20 | module Formatting.Combinators 21 | ( 22 | -- * Formatting common containers 23 | maybed 24 | , optioned 25 | , eithered 26 | , lefted 27 | , righted 28 | 29 | -- * Formatting lists of data 30 | , concatenated 31 | , joinedWith 32 | , intercalated 33 | , unworded 34 | , unlined 35 | , spaced 36 | , commaSep 37 | , commaSpaceSep 38 | , list 39 | , qlist 40 | , took 41 | , dropped 42 | 43 | -- * Splitting strings to pass to other formatters 44 | , splat 45 | , splatWith 46 | , splatOn 47 | , worded 48 | , lined 49 | 50 | -- * Altering formatted strings 51 | , alteredWith 52 | , charsKeptIf 53 | , charsRemovedIf 54 | , replaced 55 | , uppercased 56 | , lowercased 57 | , titlecased 58 | , ltruncated 59 | , ctruncated 60 | , rtruncated 61 | , lpadded 62 | , rpadded 63 | , cpadded 64 | , lfixed 65 | , rfixed 66 | , cfixed 67 | 68 | -- * Wrapping formatted strings 69 | , prefixed 70 | , suffixed 71 | , surrounded 72 | , enclosed 73 | , squoted 74 | , dquoted 75 | , parenthesised 76 | , squared 77 | , braced 78 | , angled 79 | , backticked 80 | 81 | -- * Changing indentation 82 | , indented 83 | , indentedLines 84 | , reindented 85 | 86 | -- * Numerical adapters 87 | , roundedTo 88 | , truncatedTo 89 | , ceilingedTo 90 | , flooredTo 91 | 92 | -- * Structure formatting 93 | , viewed 94 | , accessed 95 | 96 | -- * Fixed-width number formatting 97 | , binPrefix 98 | , octPrefix 99 | , hexPrefix 100 | ) where 101 | 102 | import Control.Applicative (Const(..), getConst) 103 | import Control.Category ((>>>)) 104 | import Data.Foldable (toList) 105 | import Data.Function ((&)) 106 | import Data.Int (Int64) 107 | import Data.Text.Lazy (Text) 108 | import qualified Data.Text.Lazy as TL 109 | import Data.Text.Lazy.Builder (Builder) 110 | import qualified Data.Text.Lazy.Builder as TLB 111 | import Formatting.Internal 112 | import Formatting.Formatters 113 | 114 | 115 | -- $setup 116 | -- >>> import Formatting.Internal 117 | -- >>> import Formatting.Formatters 118 | -- >>> import qualified Data.Text.Lazy as TL 119 | -- >>> _1 g (a, x) = fmap (\b -> (b, x)) $ g a 120 | -- 121 | -- We define a simplistic implementation of the lens _1, Not polymorphic in 122 | -- tuple length, but it works for our example without requiring the lens 123 | -- package. 124 | 125 | -- | Render a Maybe value either as a default (if Nothing) or using the given formatter: 126 | -- 127 | -- >>> format (maybed "Goodbye" text) Nothing 128 | -- "Goodbye" 129 | -- 130 | -- >>> format (maybed "Goodbye" text) (Just "Hello") 131 | -- "Hello" 132 | maybed 133 | :: Builder -- ^ The value to use when the input is Nothing 134 | -> Format Builder (a -> Builder) -- ^ The formatter to use on the value in a Just 135 | -> Format r (Maybe a -> r) 136 | maybed whenNothing f = later $ \case 137 | Nothing -> whenNothing 138 | Just x -> bformat f x 139 | {-# INLINE maybed #-} 140 | 141 | -- | Render the value in a Maybe using the given formatter, or produce an empty string: 142 | -- 143 | -- >>> format (optioned text) Nothing 144 | -- "" 145 | -- 146 | -- >>> format (optioned text) (Just "Hello") 147 | -- "Hello" 148 | optioned :: Format Builder (a -> Builder) -> Format r (Maybe a -> r) 149 | optioned = maybed "" 150 | {-# INLINE optioned #-} 151 | 152 | -- | Render the value in an Either: 153 | -- 154 | -- >>> format (eithered text int) (Left "Error!") 155 | -- "Error!" 156 | -- 157 | -- >>> format (eithered text int) (Right 69) 158 | -- "69" 159 | eithered 160 | :: Format Builder (a -> Builder) -- ^ The formatter to use on a value in a Left 161 | -> Format Builder (b -> Builder) -- ^ The formatter to use on a value in a Right 162 | -> Format r (Either a b -> r) 163 | eithered l r = later $ \case 164 | Left x -> bformat l x 165 | Right x -> bformat r x 166 | {-# INLINE eithered #-} 167 | 168 | -- | Render the value in a Left with the given formatter, rendering a Right as an empty string: 169 | -- 170 | -- >>> format (lefted text) (Left "bingo") 171 | -- "bingo" 172 | -- 173 | -- >>> format (lefted text) (Right 16) 174 | -- "" 175 | lefted :: Format Builder (a -> Builder) -> Format r (Either a x -> r) 176 | lefted f = eithered f (fconst "") 177 | {-# INLINE lefted #-} 178 | 179 | -- | Render the value in a Right with the given formatter, rendering a Left as an empty string: 180 | -- 181 | -- >>> format (righted text) (Left 16) 182 | -- "" 183 | -- 184 | -- >>> format (righted text) (Right "bingo") 185 | -- "bingo" 186 | righted :: Format Builder (a -> Builder) -> Format r (Either x a -> r) 187 | righted = eithered (fconst "") 188 | {-# INLINE righted #-} 189 | 190 | -- | Format each value in a list and concatenate them all: 191 | -- 192 | -- >>> format (concatenated text) ["one", "two", "three"] 193 | -- "onetwothree" 194 | -- 195 | -- >>> format (took 15 (concatenated bin)) [1..] 196 | -- "1101110010111011110001001101010111100110111101111" 197 | concatenated :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 198 | concatenated f = later $ foldMap (bformat f) 199 | {-# INLINE concatenated #-} 200 | 201 | -- | Use the given text-joining function to join together the individually rendered items of a list. 202 | -- 203 | -- >>> format (joinedWith (mconcat . reverse) int) [123, 456, 789] 204 | -- "789456123" 205 | joinedWith :: Foldable t => ([Text] -> Text) -> Format Builder (a -> Builder) -> Format r (t a -> r) 206 | joinedWith joiner f = later $ toList 207 | >>> fmap (bformat f >>> TLB.toLazyText) 208 | >>> joiner 209 | >>> TLB.fromLazyText 210 | {-# INLINABLE joinedWith #-} 211 | 212 | -- | Format each value in a list and place the given string between each: 213 | -- 214 | -- >>> fprintLn (intercalated "||" int) [1, 2, 3] 215 | -- 1||2||3 216 | intercalated :: Foldable t => Text -> Format Builder (a -> Builder) -> Format r (t a -> r) 217 | intercalated s = joinedWith (TL.intercalate s) 218 | {-# INLINE intercalated #-} 219 | 220 | -- | Format each value in a list with spaces in between: 221 | -- 222 | -- >>> format (unworded int) [1, 2, 3] 223 | -- "1 2 3" 224 | unworded :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 225 | unworded = joinedWith TL.unwords 226 | {-# INLINE unworded #-} 227 | 228 | -- | Format each value in a list, placing each on its own line: 229 | -- 230 | -- >>> fprint (unlined char) ['a'..'c'] 231 | -- a 232 | -- b 233 | -- c 234 | unlined :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 235 | unlined = joinedWith TL.unlines 236 | {-# INLINE unlined #-} 237 | 238 | -- | Separate the formatted items of the Foldable (e.g. list) with spaces: 239 | -- 240 | -- >>> format (spaced int) [1, 2, 3] 241 | -- "1 2 3" 242 | -- 243 | -- Note that this behaviour is identical to 'unworded', it's just a different way of thinking about it. 244 | spaced :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 245 | spaced = intercalated " " 246 | {-# INLINE spaced #-} 247 | 248 | -- | Separate the formatted items of the Foldable (e.g. list) with commas: 249 | -- 250 | -- >>> format (commaSep stext) ["one", "two", "three", "four", "five"] 251 | -- "one,two,three,four,five" 252 | -- 253 | -- >>> format (took 5 (commaSep int)) [1..] 254 | -- "1,2,3,4,5" 255 | commaSep :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 256 | commaSep = intercalated "," 257 | {-# INLINE commaSep #-} 258 | 259 | -- | Separate the formatted items of the Foldable (e.g. list) with commas and spaces: 260 | -- 261 | -- >>> format (took 3 (commaSpaceSep ords)) [1..] 262 | -- "1st, 2nd, 3rd" 263 | commaSpaceSep :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 264 | commaSpaceSep = intercalated ", " 265 | {-# INLINE commaSpaceSep #-} 266 | 267 | -- | Add square brackets around the Foldable (e.g. a list), and separate each formatted item with a comma and space. 268 | -- 269 | -- >>> format (list stext) ["one", "two", "three"] 270 | -- "[one, two, three]" 271 | -- 272 | -- >>> format (list shown) ["one", "two", "three"] 273 | -- "[\"one\", \"two\", \"three\"]" 274 | list :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 275 | list = commaSpaceSep >>> squared 276 | {-# INLINE list #-} 277 | 278 | -- | Like 'list', but also put double quotes around each rendered item: 279 | -- 280 | -- >>> fprintLn (qlist stext) ["one", "two", "three"] 281 | -- ["one", "two", "three"] 282 | qlist :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) 283 | qlist = dquoted >>> commaSpaceSep >>> squared 284 | {-# INLINE qlist #-} 285 | 286 | -- | Take only the first n items from the list of items. 287 | -- 288 | -- >>> format (took 7 (list bin)) [1..] 289 | -- "[1, 10, 11, 100, 101, 110, 111]" 290 | -- 291 | -- >>> format (list bin) (take 7 [1..]) 292 | -- "[1, 10, 11, 100, 101, 110, 111]" 293 | took :: Int -> Format r ([a] -> r) -> Format r ([a] -> r) 294 | took n = fmap (. take n) 295 | {-# INLINE took #-} 296 | 297 | -- | Drop the first n items from the list of items. 298 | -- 299 | -- >>> format (dropped 3 (list int)) [1..6] 300 | -- "[4, 5, 6]" 301 | dropped :: Int -> Format r ([a] -> r) -> Format r ([a] -> r) 302 | dropped n = fmap (. drop n) 303 | {-# INLINE dropped #-} 304 | 305 | -- | Utility for taking a text-splitting function and turning it into a formatting combinator. 306 | -- 307 | -- >>> format (splatWith (TL.chunksOf 3) list int) 1234567890 308 | -- "[123, 456, 789, 0]" 309 | splatWith 310 | :: (Text -> [Text]) -- ^ The text splitter 311 | -> (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. 312 | -> Format r a -- ^ The base formatter, whose rendered text will be split 313 | -> Format r a 314 | splatWith splitter lf f = later (TLB.toLazyText 315 | >>> splitter 316 | >>> fmap TLB.fromLazyText 317 | >>> bformat (lf builder)) 318 | %. f 319 | {-# INLINABLE splatWith #-} 320 | 321 | -- | Split the formatted item in places the given predicated matches, and use the given list combinator to render the resultant list of strings 322 | -- (this function was sent to us from a parallel universe in which splat is the past participle of split, e.g. "whoops, I splat my pants"). 323 | -- 324 | -- >>> format (splat Data.Char.isSpace commaSpaceSep stext) "This\t is\n\t\t poorly formatted " 325 | -- "This, , , is, , , , , poorly, formatted, , , " 326 | splat 327 | :: (Char -> Bool) -- ^ Whether to split the string at this character 328 | -> (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. 329 | -> Format r a -- ^ The base formatter, whose rendered text will be split 330 | -> Format r a 331 | splat p = splatWith (TL.split p) 332 | {-# INLINE splat #-} 333 | 334 | -- | Split the formatted item at instances of the given string, and use the given list combinator to render the resultant list of strings. 335 | -- 336 | -- >>> fprint (splatOn "," unlined text) "one,two,three" 337 | -- one 338 | -- two 339 | -- three 340 | -- 341 | -- >>> fprint (splatOn "," (indentedLines 4) text) "one,two,three" 342 | -- one 343 | -- two 344 | -- three 345 | splatOn 346 | :: Text -- ^ The text to split on 347 | -> (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. 348 | -> Format r a -- ^ The base formatter, whose rendered text will be split 349 | -> Format r a 350 | splatOn t = splatWith (TL.splitOn t) 351 | {-# INLINE splatOn #-} 352 | 353 | -- | Split the formatted item into words and use the given list combinator to render the resultant list of strings. 354 | -- 355 | -- >>> format (worded list text) "one two three " 356 | -- "[one, two, three]" 357 | worded 358 | :: (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. 359 | -> Format r a -- ^ The base formatter, whose rendered text will be split 360 | -> Format r a 361 | worded = splatWith TL.words 362 | {-# INLINE worded #-} 363 | 364 | -- | Split the formatted item into lines and use the given list combinator to render the resultant list of strings. 365 | -- 366 | -- >>> fprintLn (lined qlist text) "one two three\n\nfour five six\nseven eight nine\n\n" 367 | -- ["one two three", "", "four five six", "seven eight nine", ""] 368 | lined 369 | :: (Format Builder (Builder -> Builder) -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. 370 | -> Format r a -- ^ The base formatter, whose rendered text will be split 371 | -> Format r a 372 | lined = splatWith TL.lines 373 | {-# INLINE lined #-} 374 | 375 | -- | Alter the formatted string with the given function. 376 | -- 377 | -- >>> format (alteredWith Data.Text.Lazy.reverse int) 123456 378 | -- "654321" 379 | alteredWith :: (Text -> Text) -> Format r a -> Format r a 380 | alteredWith alterer f = 381 | later (TLB.toLazyText >>> alterer >>> TLB.fromLazyText) %. f 382 | {-# INLINABLE alteredWith #-} 383 | 384 | -- | Filter the formatted string to contain only characters which pass the given predicate: 385 | -- 386 | -- >>> format (charsKeptIf Data.Char.isUpper text) "Data.Char.isUpper" 387 | -- "DCU" 388 | charsKeptIf :: (Char -> Bool) -> Format r a -> Format r a 389 | charsKeptIf p = alteredWith (TL.filter p) 390 | {-# INLINE charsKeptIf #-} 391 | 392 | -- | Filter the formatted string to not contain characters which pass the given predicate: 393 | -- 394 | -- >>> format (charsRemovedIf Data.Char.isUpper text) "Data.Char.isUpper" 395 | -- "ata.har.ispper" 396 | charsRemovedIf :: (Char -> Bool) -> Format r a -> Format r a 397 | charsRemovedIf p = alteredWith (TL.filter (not . p)) 398 | {-# INLINE charsRemovedIf #-} 399 | 400 | -- | Take a formatter and replace the given needle with the given replacement in its output. 401 | -- 402 | -- >>> format (replaced "Bruce" "" stext) "Bruce replied that Bruce's name was, in fact, ''." 403 | -- " replied that 's name was, in fact, ''." 404 | replaced :: Text -> Text -> Format r a -> Format r a 405 | replaced needle replacement = alteredWith (TL.replace needle replacement) 406 | {-# INLINE replaced #-} 407 | 408 | -- | Convert any letters in the output of the given formatter to upper-case. 409 | -- 410 | -- >>> format (uppercased text) "I'm not shouting, you're shouting." 411 | -- "I'M NOT SHOUTING, YOU'RE SHOUTING." 412 | uppercased :: Format r a -> Format r a 413 | uppercased = alteredWith TL.toUpper 414 | {-# INLINE uppercased #-} 415 | 416 | -- | Convert any letters in the output of the given formatter to lower-case. 417 | -- 418 | -- >>> format (lowercased text) "Cd SrC/; Rm -Rf *" 419 | -- "cd src/; rm -rf *" 420 | lowercased :: Format r a -> Format r a 421 | lowercased = alteredWith TL.toLower 422 | {-# INLINE lowercased #-} 423 | 424 | -- | Convert the formatted string to title case, or something like it: 425 | -- 426 | -- >>> format (titlecased string) "the life of brian" 427 | -- "The Life Of Brian" 428 | titlecased :: Format r a -> Format r a 429 | titlecased = alteredWith TL.toTitle 430 | {-# INLINE titlecased #-} 431 | 432 | -- | Truncate the formatted string at the end so that it is no more than the given number of characters in length, placing an ellipsis at the end such that it does not exceed this length. 433 | -- 434 | -- >>> format (ltruncated 5 text) "hello" 435 | -- "hello" 436 | -- 437 | -- >>> format (ltruncated 5 text) "hellos" 438 | -- "he..." 439 | ltruncated :: Int64 -> Format r a -> Format r a 440 | ltruncated n = ctruncated (n - 3) 0 441 | {-# INLINE ltruncated #-} 442 | 443 | -- | Truncate the formatted string at the start so that it is no more than the given number of characters in length, placing an ellipsis at the start such that it does not exceed this length. 444 | -- 445 | -- >>> format (rtruncated 5 text) "hello" 446 | -- "hello" 447 | -- 448 | -- >>> format (rtruncated 5 text) "hellos" 449 | -- "...os" 450 | rtruncated :: Int64 -> Format r a -> Format r a 451 | rtruncated n = ctruncated 0 (n - 3) 452 | {-# INLINE rtruncated #-} 453 | 454 | -- | Truncate the formatted string in the center, leaving the given number of characters at the start and end, and placing an ellipsis in between. 455 | -- The length will be no longer than `start + end + 3` characters long. 456 | -- 457 | -- >>> format (ctruncated 15 4 text) "The quick brown fox jumps over the lazy dog." 458 | -- "The quick brown...dog." 459 | -- 460 | -- >>> format (ctruncated 15 4 text) "The quick brown fox" 461 | -- "The quick brown fox" 462 | ctruncated :: Int64 -> Int64 -> Format r a -> Format r a 463 | ctruncated start end = alteredWith shorten 464 | where 465 | shorten :: Text -> Text 466 | shorten txt = 467 | let n = start + end + 3 468 | in if TL.length txt <= n 469 | then txt 470 | else TL.take start txt <> "..." <> TL.takeEnd end txt 471 | {-# INLINABLE ctruncated #-} 472 | 473 | -- | Pad the formatted string on the left with the given character to give it the given minimum width: 474 | -- 475 | -- >>> format (lpadded 7 ' ' int) 1 476 | -- " 1" 477 | -- 478 | -- >>> format (lpadded 7 ' ' int) 123456789 479 | -- "123456789" 480 | lpadded :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) 481 | lpadded i c = alteredWith (TL.justifyRight i c) 482 | {-# INLINE lpadded #-} 483 | 484 | -- | Pad the formatted string on the right with the given character to give it the given minimum width: 485 | -- 486 | -- >>> format (rpadded 7 ' ' int) 1 487 | -- "1 " 488 | rpadded :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) 489 | rpadded i c = alteredWith (TL.justifyLeft i c) 490 | {-# INLINE rpadded #-} 491 | 492 | -- | Pad the formatted string on the left and right with the given character to center it, giving it the given minimum width: 493 | -- 494 | -- >>> format (cpadded 7 ' ' int) 1 495 | -- " 1 " 496 | cpadded :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) 497 | cpadded i c = alteredWith (TL.center i c) 498 | {-# INLINE cpadded #-} 499 | 500 | -- | Format the item with a fixed width, padding with the given character on the left to extend, adding an ellipsis on the right to shorten: 501 | -- 502 | -- >>> format (lfixed 10 ' ' int) 123 503 | -- "123 " 504 | -- 505 | -- >>> format (lfixed 10 ' ' int) 1234567890 506 | -- "1234567890" 507 | -- 508 | -- >>> format (lfixed 10 ' ' int) 123456789012345 509 | -- "1234567..." 510 | lfixed :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) 511 | lfixed n c = ltruncated n . rpadded n c 512 | {-# INLINE lfixed #-} 513 | 514 | -- | Format the item with a fixed width, padding with the given character on the right to extend, adding an ellipsis on the right to shorten: 515 | -- 516 | -- >>> format (rfixed 10 ' ' int) 123 517 | -- " 123" 518 | -- 519 | -- >>> format (rfixed 10 ' ' int) 1234567890 520 | -- "1234567890" 521 | -- 522 | -- >>> format (rfixed 10 ' ' int) 123456789012345 523 | -- "...9012345" 524 | rfixed :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) 525 | rfixed n c = rtruncated n . lpadded n c 526 | {-# INLINE rfixed #-} 527 | 528 | -- | Format the item with a fixed width, padding with the given character on either side to extend, adding an ellipsis in the center to shorten. 529 | -- 530 | -- The total length will be `l + r + 3` characters. 531 | -- 532 | -- >>> format (cfixed 4 3 ' ' int) 123 533 | -- " 123 " 534 | -- 535 | -- >>> format (cfixed 4 3 ' ' int) 1234567890 536 | -- "1234567890" 537 | -- 538 | -- >>> format (cfixed 4 3 ' ' int) 123456789012345 539 | -- "1234...345" 540 | cfixed :: Int64 -> Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) 541 | cfixed l r c = ctruncated l r . cpadded (l + r + 3) c 542 | {-# INLINE cfixed #-} 543 | 544 | -- | Add the given prefix to the formatted item: 545 | -- 546 | -- >>> format ("The answer is: " % prefixed "wait for it... " int) 42 547 | -- "The answer is: wait for it... 42" 548 | -- 549 | -- >>> fprint (unlined (indented 4 (prefixed "- " int))) [1, 2, 3] 550 | -- - 1 551 | -- - 2 552 | -- - 3 553 | prefixed :: Builder -> Format r a -> Format r a 554 | prefixed s f = now s % f 555 | {-# INLINE prefixed #-} 556 | 557 | -- | Add the given suffix to the formatted item. 558 | suffixed :: Builder -> Format r a -> Format r a 559 | suffixed s f = f % now s 560 | {-# INLINE suffixed #-} 561 | 562 | -- | Surround the output string with the given string: 563 | -- 564 | -- >>> format (surrounded "***" string) "glue" 565 | -- "***glue***" 566 | surrounded :: Builder -> Format r a -> Format r a 567 | surrounded s f = now s % f % now s 568 | {-# INLINE surrounded #-} 569 | 570 | -- | Enclose the output string with the given strings: 571 | -- 572 | -- >>> format (enclosed "" text) "an html comment" 573 | -- "" 574 | enclosed :: Builder -> Builder -> Format r a -> Format r a 575 | enclosed pre suf f = now pre % f % now suf 576 | {-# INLINE enclosed #-} 577 | 578 | -- | Add single quotes around the formatted item: 579 | -- 580 | -- >>> let obj = Just Nothing in format ("The object is: " % squoted shown % ".") obj 581 | -- "The object is: 'Just Nothing'." 582 | squoted :: Format r a -> Format r a 583 | squoted = surrounded "'" 584 | {-# INLINE squoted #-} 585 | 586 | -- | Add double quotes around the formatted item: 587 | -- 588 | -- >>> fprintLn ("He said it was based on " % dquoted stext % ".") "science" 589 | -- He said it was based on "science". 590 | dquoted :: Format r a -> Format r a 591 | dquoted = surrounded "\"" 592 | {-# INLINE dquoted #-} 593 | 594 | -- | Add parentheses around the formatted item: 595 | -- 596 | -- >>> format ("We found " % parenthesised int % " discrepancies.") 17 597 | -- "We found (17) discrepancies." 598 | -- 599 | -- >>> fprintLn (took 5 (list (parenthesised int))) [1..] 600 | -- [(1), (2), (3), (4), (5)] 601 | parenthesised :: Format r a -> Format r a 602 | parenthesised = enclosed "(" ")" 603 | {-# INLINE parenthesised #-} 604 | 605 | -- | Add square brackets around the formatted item: 606 | -- 607 | -- >>> format (squared int) 7 608 | -- "[7]" 609 | squared :: Format r a -> Format r a 610 | squared = enclosed "[" "]" 611 | {-# INLINE squared #-} 612 | 613 | -- | Add curly brackets around the formatted item: 614 | -- 615 | -- >>> format ("\\begin" % braced text) "section" 616 | -- "\\begin{section}" 617 | braced :: Format r a -> Format r a 618 | braced = enclosed "{" "}" 619 | {-# INLINE braced #-} 620 | 621 | -- | Add angle brackets around the formatted item: 622 | -- 623 | -- >>> format (angled int) 7 624 | -- "<7>" 625 | -- 626 | -- >>> format (list (angled text)) ["html", "head", "title", "body", "div", "span"] 627 | -- "[, , , <body>, <div>, <span>]" 628 | angled :: Format r a -> Format r a 629 | angled = enclosed "<" ">" 630 | {-# INLINE angled #-} 631 | 632 | -- | Add backticks around the formatted item: 633 | -- 634 | -- >>> format ("Be sure to run " % backticked builder % " as root.") ":(){:|:&};:" 635 | -- "Be sure to run `:(){:|:&};:` as root." 636 | backticked :: Format r a -> Format r a 637 | backticked = surrounded "`" 638 | {-# INLINE backticked #-} 639 | 640 | -- | Insert the given number of spaces at the start of the rendered text: 641 | -- 642 | -- >>> format (indented 4 int) 7 643 | -- " 7" 644 | -- 645 | -- Note that this only indents the first line of a multi-line string. 646 | -- To indent all lines see 'reindented'. 647 | indented :: Int -> Format r a -> Format r a 648 | indented n = prefixed spaces 649 | where 650 | spaces = TL.replicate (fromIntegral n) (TL.singleton ' ') & TLB.fromLazyText 651 | {-# INLINABLE indented #-} 652 | 653 | -- | Format a list of items, placing one per line, indented by the given number of spaces. 654 | -- 655 | -- >>> fprint ("The lucky numbers are:\n" % indentedLines 4 int) [7, 13, 1, 42] 656 | -- The lucky numbers are: 657 | -- 7 658 | -- 13 659 | -- 1 660 | -- 42 661 | indentedLines :: Foldable t => Int -> Format Builder (a -> Builder) -> Format r (t a -> r) 662 | indentedLines n = unlined . indented n 663 | {-# INLINE indentedLines #-} 664 | 665 | -- | Indent each line of the formatted string by the given number of spaces: 666 | -- 667 | -- >>> fprint (reindented 2 text) "one\ntwo\nthree" 668 | -- one 669 | -- two 670 | -- three 671 | reindented :: Int -> Format r a -> Format r a 672 | reindented n = lined (indentedLines n) 673 | {-# INLINE reindented #-} 674 | 675 | -- | Take a fractional number and round it before formatting it as the given Format: 676 | -- 677 | -- >>> format (roundedTo int) 6.66 678 | -- "7" 679 | -- >>> format (list (roundedTo int)) [10.66, 6.66, 1.0, 3.4] 680 | -- "[11, 7, 1, 3]" 681 | -- 682 | -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: 683 | -- 684 | -- @ 685 | -- roundedTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) 686 | -- @ 687 | roundedTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) 688 | roundedTo = fmap (. round) 689 | {-# INLINE roundedTo #-} 690 | 691 | -- | Take a fractional number and truncate it before formatting it as the given Format: 692 | -- 693 | -- >>> format (truncatedTo int) 6.66 694 | -- "6" 695 | -- >>> format (list (truncatedTo int)) [10.66, 6.66, 1.0, 3.4] 696 | -- "[10, 6, 1, 3]" 697 | -- 698 | -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: 699 | -- 700 | -- @ 701 | -- truncatedTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) 702 | -- @ 703 | truncatedTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) 704 | truncatedTo = fmap (. truncate) 705 | {-# INLINE truncatedTo #-} 706 | 707 | -- | Take a fractional number and ceiling it before formatting it as the given Format: 708 | -- 709 | -- >>> format (ceilingedTo int) 6.66 710 | -- "7" 711 | -- >>> format (list (ceilingedTo int)) [10.66, 6.66, 1.0, 3.4] 712 | -- "[11, 7, 1, 4]" 713 | -- 714 | -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: 715 | -- 716 | -- @ 717 | -- ceilingedTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) 718 | -- @ 719 | ceilingedTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) 720 | ceilingedTo = fmap (. ceiling) 721 | {-# INLINE ceilingedTo #-} 722 | 723 | -- | Take a fractional number and floor it before formatting it as the given Format: 724 | -- 725 | -- >>> format (flooredTo int) 6.66 726 | -- "6" 727 | -- >>> format (list (flooredTo int)) [10.66, 6.66, 1.0, 3.4] 728 | -- "[10, 6, 1, 3]" 729 | -- 730 | -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: 731 | -- 732 | -- @ 733 | -- flooredTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) 734 | -- @ 735 | flooredTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) 736 | flooredTo = fmap (. floor) 737 | {-# INLINE flooredTo #-} 738 | 739 | -- | Use the given lens to view an item, formatting it with the given formatter. 740 | -- 741 | -- You can think of this as having the type: 742 | -- 743 | -- @ 744 | -- 'viewed' :: 'Lens'' s a -> Format r (a -> r) -> Format r (s -> r) 745 | -- @ 746 | -- 747 | -- >>> format (viewed _1 int) (1, "hello") 748 | -- "1" 749 | -- 750 | -- This is useful when combined with the Monoid instance for Format, because it allows us to give a data structure as an argument only once, and deconstruct it with the formatters: 751 | -- 752 | -- @ 753 | -- data Person = Person 754 | -- { _personName :: Text 755 | -- , _personAge :: Int 756 | -- } 757 | -- makeLenses ''Person 758 | -- 759 | -- me :: Person 760 | -- me = Person "Alex" 38 761 | -- 762 | -- format ("The person's name is " % squoted (viewed personName text) % ", and their age is " <> viewed personAge int) me 763 | -- "The person's name is 'Alex', and their age is 38" 764 | -- @ 765 | viewed :: ((a -> Const a b) -> s -> Const a t) -> Format r (a -> r) -> Format r (s -> r) 766 | viewed l = fmap (. (getConst . l Const)) 767 | {-# INLINE viewed #-} 768 | 769 | -- | Access an element of the structure and format it with the given formatter. 770 | -- 771 | -- >>> format (accessed fst int) (1, "hello") 772 | -- "1" 773 | -- 774 | -- Repeating the example from 'viewed': 775 | -- 776 | -- format ("The person's name is " % squoted (accessed _personName text) % ", and their age is " <> accessed _personAge int) me 777 | -- "The person's name is 'Alex', and their age is 38" 778 | accessed :: (s -> a) -> Format r (a -> r) -> Format r (s -> r) 779 | accessed accessor = fmap (. accessor) 780 | {-# INLINE accessed #-} 781 | 782 | -- | Render an integer using binary notation with a leading 0b, padding with zeroes to the given width: 783 | -- 784 | -- >>> format (binPrefix 16) 4097 785 | -- "0b0001000000000001" 786 | binPrefix :: Integral a => Int64 -> Format r (a -> r) 787 | binPrefix n = "0b" % lpadded n '0' bin 788 | {-# INLINE binPrefix #-} 789 | 790 | -- | Render an integer using octal notation with a leading 0o, padding with zeroes to the given width: 791 | -- 792 | -- >>> format (octPrefix 16) 4097 793 | -- "0o0000000000010001" 794 | octPrefix :: Integral a => Int64 -> Format r (a -> r) 795 | octPrefix n = "0o" % lpadded n '0' oct 796 | {-# INLINE octPrefix #-} 797 | 798 | -- | Render an integer using octal notation with a leading 0x, padding with zeroes to the given width: 799 | -- 800 | -- >>> format (hexPrefix 16) 4097 801 | -- "0x0000000000001001" 802 | hexPrefix :: Integral a => Int64 -> Format r (a -> r) 803 | hexPrefix n = "0x" % lpadded n '0' hex 804 | {-# INLINE hexPrefix #-} 805 | -------------------------------------------------------------------------------- /src/Formatting/Examples.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | Examples that should always compile. If reading on Haddock, you 4 | -- can view the sources to each of these. 5 | 6 | module Formatting.Examples 7 | ( hello 8 | , strings 9 | , texts 10 | , builders 11 | , integers 12 | , floats 13 | , hexes 14 | , padding 15 | ) where 16 | 17 | import Data.Text.Lazy (Text) 18 | import Data.Text.Lazy.Builder (Builder) 19 | import Formatting 20 | 21 | -- | Simple hello, world! 22 | hello :: Text 23 | hello = format "Hello, World!" 24 | 25 | -- | Printing strings. 26 | strings :: Text 27 | strings = 28 | format ("Here comes a string: " % string % " and another " % string) 29 | "Hello, World!" 30 | "Ahoy!" 31 | 32 | -- | Printing texts. 33 | texts :: Text 34 | texts = 35 | format ("Here comes a string: " % text % " and another " % text) 36 | "Hello, World!" 37 | "Ahoy!" 38 | 39 | -- | Printing builders. 40 | builders :: Text 41 | builders = 42 | format ("Here comes a string: " % builder % " and another " % text) 43 | ("Hello, World!" :: Builder) 44 | "Ahoy!" 45 | 46 | -- | Printing integers. 47 | integers :: Text 48 | integers = 49 | format ("Here comes an integer: " % int % " and another: " % int) 50 | (23 :: Int) 51 | (0 :: Integer) 52 | 53 | -- | Printing floating points. 54 | floats :: Text 55 | floats = 56 | format ("Here comes a float: " % float) 57 | (123.2342 :: Float) 58 | 59 | -- | Printing integrals in hex (base-16). 60 | hexes :: Text 61 | hexes = 62 | format ("Here comes a hex: " % hex) 63 | (123 :: Int) 64 | 65 | -- | Padding. 66 | padding :: Text 67 | padding = 68 | format ("A left-padded number: " % left 3 '0') 69 | (9 :: Int) 70 | -------------------------------------------------------------------------------- /src/Formatting/Formatters.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | -- | 4 | -- Module : Formatting.Formatters 5 | -- Copyright : (c) 2013 Chris Done, 2013 Shachaf Ben-Kiki 6 | -- License : BSD3 7 | -- Maintainer : alex@farfromthere.net 8 | -- Stability : experimental 9 | -- Portability : GHC 10 | -- 11 | -- Formatting functions. 12 | 13 | module Formatting.Formatters 14 | ( 15 | -- * Text/string types 16 | text, 17 | stext, 18 | string, 19 | shown, 20 | char, 21 | builder, 22 | fconst, 23 | -- * Numbers 24 | int, 25 | float, 26 | fixed, 27 | sci, 28 | scifmt, 29 | shortest, 30 | groupInt, 31 | commas, 32 | ords, 33 | plural, 34 | asInt, 35 | -- * Padding 36 | left, 37 | right, 38 | center, 39 | fitLeft, 40 | fitRight, 41 | -- * Bases 42 | base, 43 | bin, 44 | oct, 45 | hex, 46 | prefixBin, 47 | prefixOct, 48 | prefixHex, 49 | bytes, 50 | -- * Buildables 51 | build, 52 | Buildable, 53 | ) where 54 | 55 | import Formatting.Internal 56 | 57 | import Data.Char (chr, ord) 58 | import Data.Scientific 59 | import qualified Data.Text as S 60 | import qualified Data.Text as T 61 | import Formatting.Buildable (Buildable) 62 | import qualified Formatting.Buildable as B (build) 63 | import qualified Data.Text.Format as T 64 | import Data.Text.Lazy (Text) 65 | import qualified Data.Text.Lazy as LT 66 | import Data.Text.Lazy.Builder (Builder) 67 | import qualified Data.Text.Lazy.Builder as T 68 | import Data.Text.Lazy.Builder.Scientific 69 | import Numeric (showIntAtBase) 70 | 71 | -- $setup 72 | -- >>> import Formatting.Internal 73 | 74 | -- | Output a lazy text. 75 | text :: Format r (Text -> r) 76 | text = later T.fromLazyText 77 | {-# INLINE text #-} 78 | 79 | -- | Output a strict text. 80 | stext :: Format r (S.Text -> r) 81 | stext = later T.fromText 82 | {-# INLINE stext #-} 83 | 84 | -- | Output a string. 85 | string :: Format r (String -> r) 86 | string = later (T.fromText . T.pack) 87 | {-# INLINE string #-} 88 | 89 | -- | Output a showable value (instance of 'Show') by turning it into 90 | -- 'Text': 91 | -- 92 | -- >>> format ("Value number " % shown % " is " % shown % ".") 42 False 93 | -- "Value number 42 is False." 94 | shown :: Show a => Format r (a -> r) 95 | shown = later (T.fromText . T.pack . show) 96 | {-# INLINE shown #-} 97 | 98 | -- | Output a character. 99 | char :: Format r (Char -> r) 100 | char = later B.build 101 | {-# INLINE char #-} 102 | 103 | -- | Build a builder. 104 | builder :: Format r (Builder -> r) 105 | builder = later id 106 | {-# INLINE builder #-} 107 | 108 | -- | Like `const` but for formatters. 109 | fconst :: Builder -> Format r (a -> r) 110 | fconst m = later (const m) 111 | {-# INLINE fconst #-} 112 | 113 | -- | Build anything that implements the "Buildable" class. 114 | build :: Buildable a => Format r (a -> r) 115 | build = later B.build 116 | {-# INLINE build #-} 117 | 118 | -- | Render an integral e.g. 123 -> \"123\", 0 -> \"0\". 119 | int :: Integral a => Format r (a -> r) 120 | int = base 10 121 | {-# INLINE int #-} 122 | 123 | -- | Render some floating point with the usual notation, e.g. 123.32 => \"123.32\" 124 | float :: Real a => Format r (a -> r) 125 | float = later T.shortest 126 | {-# INLINE float #-} 127 | 128 | -- | Render a floating point number using normal notation, with the 129 | -- given number of decimal places. 130 | fixed :: Real a => Int -> Format r (a -> r) 131 | fixed i = later (T.fixed i) 132 | {-# INLINE fixed #-} 133 | 134 | -- | Render a floating point number using the smallest number of 135 | -- digits that correctly represent it. Note that in the case of whole 136 | -- numbers it will still add one decimal place, e.g. "1.0". 137 | shortest :: Real a => Format r (a -> r) 138 | shortest = later T.shortest 139 | {-# INLINE shortest #-} 140 | 141 | -- | Render a scientific number. 142 | sci :: Format r (Scientific -> r) 143 | sci = later scientificBuilder 144 | {-# INLINE sci #-} 145 | 146 | -- | Render a scientific number with options. 147 | scifmt :: FPFormat -> Maybe Int -> Format r (Scientific -> r) 148 | scifmt f i = later (formatScientificBuilder f i) 149 | {-# INLINE scifmt #-} 150 | 151 | -- | Shows the Int value of Enum instances using 'fromEnum'. 152 | -- 153 | -- >>> format ("Got: " % char % " (" % asInt % ")") 'a' 'a' 154 | -- "Got: a (97)" 155 | asInt :: Enum a => Format r (a -> r) 156 | asInt = later (T.shortest . fromEnum) 157 | {-# INLINE asInt #-} 158 | 159 | -- | Pad the left hand side of a string until it reaches k characters 160 | -- wide, if necessary filling with character c. 161 | left :: Buildable a => Int -> Char -> Format r (a -> r) 162 | left i c = later (T.left i c) 163 | {-# INLINE left #-} 164 | 165 | -- | Pad the right hand side of a string until it reaches k characters 166 | -- wide, if necessary filling with character c. 167 | right :: Buildable a => Int -> Char -> Format r (a -> r) 168 | right i c = later (T.right i c) 169 | {-# INLINE right #-} 170 | 171 | -- | Pad the left & right hand side of a string until it reaches k characters 172 | -- wide, if necessary filling with character c. 173 | center :: Buildable a => Int -> Char -> Format r (a -> r) 174 | center i c = later centerT where 175 | centerT = T.fromLazyText . LT.center (fromIntegral i) c . T.toLazyText . B.build 176 | 177 | -- | Group integral numbers, e.g. groupInt 2 '.' on 123456 -> \"12.34.56\". 178 | groupInt :: (Buildable n,Integral n) => Int -> Char -> Format r (n -> r) 179 | groupInt 0 _ = later B.build 180 | groupInt i c = 181 | later 182 | (\n -> 183 | if n < 0 184 | then "-" <> commaize (negate n) 185 | else commaize n) 186 | where 187 | commaize = 188 | T.fromLazyText . 189 | LT.reverse . 190 | foldr merge "" . 191 | LT.zip (zeros <> cycle' zeros') . LT.reverse . T.toLazyText . B.build 192 | zeros = LT.replicate (fromIntegral i) (LT.singleton '0') 193 | zeros' = LT.singleton c <> LT.tail zeros 194 | merge (f, c') rest 195 | | f == c = LT.singleton c <> LT.singleton c' <> rest 196 | | otherwise = LT.singleton c' <> rest 197 | cycle' xs = xs <> cycle' xs 198 | 199 | -- | Fit in the given length, truncating on the left. 200 | fitLeft :: Buildable a => Int -> Format r (a -> r) 201 | fitLeft size = later (fit (fromIntegral size)) where 202 | fit i = T.fromLazyText . LT.take i . T.toLazyText . B.build 203 | 204 | -- | Fit in the given length, truncating on the right. 205 | fitRight :: Buildable a => Int -> Format r (a -> r) 206 | fitRight size = later (fit (fromIntegral size)) where 207 | fit i = T.fromLazyText . 208 | (\t -> LT.drop (LT.length t - i) t) 209 | . T.toLazyText 210 | . B.build 211 | 212 | -- | Add commas to an integral, e.g 12000 -> \ "12,000". 213 | commas :: (Buildable n,Integral n) => Format r (n -> r) 214 | commas = groupInt 3 ',' 215 | {-# INLINE commas #-} 216 | 217 | -- | Add a suffix to an integral, e.g. 1st, 2nd, 3rd, 21st. 218 | ords :: Integral n => Format r (n -> r) 219 | ords = later go 220 | where go n 221 | | tens > 3 && tens < 21 = T.fixed 0 n <> "th" 222 | | otherwise = 223 | T.fixed 0 n <> 224 | case n `mod` 10 of 225 | 1 -> "st" 226 | 2 -> "nd" 227 | 3 -> "rd" 228 | _ -> "th" 229 | where tens = n `mod` 100 230 | 231 | -- | English plural suffix for an integral. 232 | -- 233 | -- For example: 234 | -- 235 | -- >>> :set -XOverloadedStrings 236 | -- >>> formatPeople = format (int % " " <> plural "person" "people" % ".") :: Int -> Data.Text.Lazy.Text 237 | -- >>> formatPeople 1 238 | -- "1 person." 239 | -- >>> formatPeople 3 240 | -- "3 people." 241 | plural :: (Num a, Eq a) => Text -> Text -> Format r (a -> r) 242 | plural s p = later (\i -> if i == 1 then B.build s else B.build p) 243 | 244 | -- | Render an integral at base n. 245 | base :: Integral a => Int -> Format r (a -> r) 246 | base numBase = later (B.build . atBase numBase) 247 | {-# INLINE base #-} 248 | 249 | -- | Render an integer using binary notation. (No leading 0b is 250 | -- added.) Defined as @bin = 'base' 2@. 251 | bin :: Integral a => Format r (a -> r) 252 | bin = base 2 253 | {-# INLINE bin #-} 254 | 255 | -- | Render an integer using octal notation. (No leading 0o is 256 | -- added.) Defined as @oct = 'base' 8@. 257 | oct :: Integral a => Format r (a -> r) 258 | oct = base 8 259 | {-# INLINE oct #-} 260 | 261 | -- | Render an integer using hexadecimal notation. (No leading 0x is 262 | -- added.) Has a specialized implementation. 263 | hex :: Integral a => Format r (a -> r) 264 | hex = later T.hex 265 | {-# INLINE hex #-} 266 | 267 | -- | Render an integer using binary notation with a leading 0b. 268 | -- 269 | -- See also 'Formatting.Combinators.binPrefix' for fixed-width formatting. 270 | prefixBin :: Integral a => Format r (a -> r) 271 | prefixBin = "0b" % bin 272 | {-# INLINE prefixBin #-} 273 | 274 | -- | Render an integer using octal notation with a leading 0o. 275 | -- 276 | -- See also 'Formatting.Combinators.octPrefix' for fixed-width formatting. 277 | prefixOct :: Integral a => Format r (a -> r) 278 | prefixOct = "0o" % oct 279 | {-# INLINE prefixOct #-} 280 | 281 | -- | Render an integer using hexadecimal notation with a leading 0x. 282 | -- 283 | -- See also 'Formatting.Combinators.hexPrefix' for fixed-width formatting. 284 | prefixHex :: Integral a => Format r (a -> r) 285 | prefixHex = "0x" % hex 286 | {-# INLINE prefixHex #-} 287 | 288 | -- The following code is mostly taken from `Numeric.Lens.' (from 289 | -- `lens') and modified. 290 | 291 | -- | Internal function that converts a number to a base base-2 through 292 | -- base-36. 293 | atBase :: Integral a => Int -> a -> String 294 | atBase b _ | b < 2 || b > 36 = error ("base: Invalid base " ++ show b) 295 | atBase b n = 296 | showSigned' (showIntAtBase (toInteger b) intToDigit') (toInteger n) "" 297 | {-# INLINE atBase #-} 298 | 299 | -- | A simpler variant of 'Numeric.showSigned' that only prepends a dash and 300 | -- doesn't know about parentheses 301 | showSigned' :: Real a => (a -> ShowS) -> a -> ShowS 302 | showSigned' f n 303 | | n < 0 = showChar '-' . f (negate n) 304 | | otherwise = f n 305 | 306 | -- | Like 'Data.Char.intToDigit', but handles up to base-36 307 | intToDigit' :: Int -> Char 308 | intToDigit' i 309 | | i >= 0 && i < 10 = chr (ord '0' + i) 310 | | i >= 10 && i < 36 = chr (ord 'a' + i - 10) 311 | | otherwise = error ("intToDigit': Invalid int " ++ show i) 312 | 313 | -- | Renders a given byte count using an appropiate decimal binary suffix: 314 | -- 315 | -- >>> format (bytes shortest) 1024 316 | -- "1KB" 317 | -- 318 | -- >>> format (bytes (fixed 2 % " ")) (1024*1024*5) 319 | -- "5.00 MB" 320 | -- 321 | bytes :: (Ord f,Integral a,Fractional f) 322 | => Format Builder (f -> Builder) -- ^ formatter for the decimal part 323 | -> Format r (a -> r) 324 | bytes d = later go 325 | where go bs = 326 | bprint d (fromIntegral (signum bs) * dec) <> bytesSuffixes !! 327 | i 328 | where (dec,i) = getSuffix (abs bs) 329 | getSuffix n = 330 | until p 331 | (\(x,y) -> (x / 1024,y + 1)) 332 | (fromIntegral n,0) 333 | where p (n',numDivs) = 334 | n' < 1024 || numDivs == (length bytesSuffixes - 1) 335 | bytesSuffixes = 336 | ["B","KB","MB","GB","TB","PB","EB","ZB","YB"] 337 | -------------------------------------------------------------------------------- /src/Formatting/FromBuilder.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | 3 | module Formatting.FromBuilder 4 | ( FromBuilder(..) 5 | , formatted 6 | ) where 7 | 8 | import qualified Data.Text as T 9 | import qualified Data.Text.Lazy as TL 10 | import Data.Text.Lazy.Builder (Builder) 11 | import qualified Data.Text.Lazy.Builder as TL 12 | import Formatting.Internal (Format (..)) 13 | 14 | -- $setup 15 | -- >>> import qualified Data.Text.Lazy as TL (Text) 16 | -- >>> import qualified Data.Text.Lazy.IO as TL 17 | -- >>> import qualified Data.Text as T (Text) 18 | -- >>> import qualified Data.Text.IO as T 19 | -- >>> import Formatting ((%)) 20 | -- >>> import Formatting.Formatters (int) 21 | -- >>> :set -XOverloadedStrings 22 | -- >>> :set -XTypeApplications 23 | 24 | -- | Anything that can be created from a 'Builder'. 25 | -- This class makes it easier to add formatting to other API's. 26 | -- See 'formatted' for some examples of this class in action. 27 | class FromBuilder a where 28 | fromBuilder :: Builder -> a 29 | 30 | instance FromBuilder Builder where 31 | fromBuilder = id 32 | {-# INLINE fromBuilder #-} 33 | 34 | instance FromBuilder TL.Text where 35 | fromBuilder = TL.toLazyText 36 | {-# INLINE fromBuilder #-} 37 | 38 | instance FromBuilder T.Text where 39 | fromBuilder = TL.toStrict . TL.toLazyText 40 | {-# INLINE fromBuilder #-} 41 | 42 | instance FromBuilder [Char] where 43 | fromBuilder = TL.unpack . TL.toLazyText 44 | {-# INLINE fromBuilder #-} 45 | 46 | -- | Makes it easy to add formatting to any api that is expecting a builder, 47 | -- a strict or lazy text, or a string. 48 | -- It is essentially (flip runFormat), but with a more generous type due to 49 | -- the typeclass. 50 | -- 51 | -- For example: 52 | -- >>> formatted TL.putStr ("x is: " % int % "\n") 7 53 | -- x is: 7 54 | -- >>> formatted T.putStr ("x is: " % int % "\n") 7 55 | -- x is: 7 56 | -- >>> formatted (id @TL.Text) ("x is: " % int % "\n") 7 57 | -- "x is: 7\n" 58 | -- >>> formatted (id @T.Text) ("x is: " % int % "\n") 7 59 | -- "x is: 7\n" 60 | formatted :: FromBuilder t => (t -> o) -> Format o a -> a 61 | formatted k f = runFormat f (k . fromBuilder) 62 | -------------------------------------------------------------------------------- /src/Formatting/Internal.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | 4 | -- | Internal format starters. 5 | 6 | module Formatting.Internal 7 | ( Format(..) 8 | , (%) 9 | , (%+) 10 | , (%.) 11 | , (<%+>) 12 | , now 13 | , bind 14 | , mapf 15 | , later 16 | , format 17 | , sformat 18 | , bprint 19 | , bformat 20 | , fprint 21 | , fprintLn 22 | , hprint 23 | , hprintLn 24 | , formatToString 25 | ) where 26 | 27 | import Control.Category (Category(..)) 28 | import Control.Monad.IO.Class (MonadIO (..)) 29 | import Data.Monoid 30 | import Data.String 31 | import qualified Data.Text as S (Text) 32 | import Data.Text.Lazy (Text) 33 | import qualified Data.Text.Lazy as T 34 | import qualified Data.Text.Lazy as TL 35 | import Data.Text.Lazy.Builder (Builder) 36 | import qualified Data.Text.Lazy.Builder as T 37 | import qualified Data.Text.Lazy.Builder as TLB 38 | import qualified Data.Text.Lazy.IO as T 39 | import Prelude hiding ((.),id) 40 | import System.IO 41 | 42 | -- $setup 43 | -- >>> import Formatting.Formatters 44 | 45 | -- | A formatter. When you construct formatters the first type 46 | -- parameter, @r@, will remain polymorphic. The second type 47 | -- parameter, @a@, will change to reflect the types of the data that 48 | -- will be formatted. For example, in 49 | -- 50 | -- @ 51 | -- myFormat :: Format r (Text -> Int -> r) 52 | -- myFormat = \"Person's name is \" % text % \", age is \" % hex 53 | -- @ 54 | -- 55 | -- the first type parameter remains polymorphic, and the second type 56 | -- parameter is @Text -> Int -> r@, which indicates that it formats a 57 | -- 'Text' and an 'Int'. 58 | -- 59 | -- When you run the 'Format', for example with 'format', you provide 60 | -- the arguments and they will be formatted into a string. 61 | -- 62 | -- @ 63 | -- \> format (\"Person's name is \" % text % \", age is \" % hex) \"Dave\" 54 64 | -- \"Person's name is Dave, age is 36\" 65 | -- @ 66 | newtype Format r a = 67 | Format {runFormat :: (Builder -> r) -> a} 68 | 69 | -- | This can be used almost like contramap, e.g: 70 | -- 71 | -- @ 72 | -- formatter :: Format r (b -> r) 73 | -- formatter = _ 74 | -- 75 | -- adapter :: a -> b 76 | -- adapter = _ 77 | -- 78 | -- adapted :: Format r (a -> r) 79 | -- adapted = fmap (. adapter) formatter 80 | -- @ 81 | instance Functor (Format r) where 82 | fmap f (Format k) = Format (f . k) 83 | 84 | instance Semigroup (Format r (a -> r)) where 85 | m <> n = 86 | Format (\k a -> 87 | runFormat m (\b1 -> runFormat n (\b2 -> k (b1 <> b2)) a) a) 88 | 89 | -- | Like @(<>)@ except put a space between the two formatters. For example: 90 | -- @format (year <%+> month <%+> dayOfMonth) now@ will yield @"2022 06 06"@ 91 | (<%+>) :: Format r (a -> r) -> Format r (a -> r) -> Format r (a -> r) 92 | m <%+> n = 93 | Format (\k a -> 94 | runFormat m (\b1 -> runFormat n (\b2 -> k (b1 <> TLB.singleton ' ' <> b2)) a) a) 95 | 96 | -- | Useful instance for applying two formatters to the same input 97 | -- argument. For example: @format (year <> "/" % month) now@ will 98 | -- yield @"2015/01"@. 99 | instance Monoid (Format r (a -> r)) where 100 | mempty = Format (\k _ -> k mempty) 101 | 102 | -- | Useful instance for writing format string. With this you can 103 | -- write @\"Foo\"@ instead of @now "Foo!"@. 104 | instance (a ~ r) => IsString (Format r a) where 105 | fromString = now . fromString 106 | 107 | -- | The same as (%). At present using 'Category' has an import 108 | -- overhead, but one day it might be imported as standard. 109 | instance Category Format where 110 | id = now mempty 111 | f . g = 112 | f `bind` 113 | \a -> 114 | g `bind` 115 | \b -> now (a `mappend` b) 116 | 117 | -- | Concatenate two formatters. 118 | -- 119 | -- @formatter1 % formatter2@ is a formatter that accepts arguments for 120 | -- @formatter1@ and @formatter2@ and concatenates their results. For example 121 | -- 122 | -- @ 123 | -- format1 :: Format r (Text -> r) 124 | -- format1 = \"Person's name is \" % text 125 | -- @ 126 | -- 127 | -- @ 128 | -- format2 :: Format r r 129 | -- format2 = \", \" 130 | -- @ 131 | -- 132 | -- @ 133 | -- format3 :: Format r (Int -> r) 134 | -- format3 = \"age is \" % hex 135 | -- @ 136 | -- 137 | -- @ 138 | -- myFormat :: Format r (Text -> Int -> r) 139 | -- myFormat = format1 % format2 % format3 140 | -- @ 141 | -- 142 | -- Notice how the argument types of @format1@ and @format3@ are 143 | -- gathered into the type of @myFormat@. 144 | -- 145 | -- (This is actually the composition operator for 'Format's 146 | -- 'Category' instance, but that is (at present) inconvenient to use 147 | -- with regular "Prelude". So this function is provided as a 148 | -- convenience.) 149 | (%) :: Format r a -> Format r' r -> Format r' a 150 | (%) = (.) 151 | infixr 9 % 152 | 153 | -- | Concatenate two formatters with a space in between. 154 | -- 155 | -- >>> :set -XOverloadedStrings 156 | -- >>> format (int %+ "+" %+ int %+ "=" %+ int) 2 3 5 157 | -- "2 + 3 = 5" 158 | -- 159 | (%+) :: Format r a -> Format r' r -> Format r' a 160 | f %+ g = 161 | f `bind` 162 | \a -> 163 | g `bind` 164 | \b -> now (a `mappend` TLB.singleton ' ' `mappend` b) 165 | infixr 9 %+ 166 | 167 | -- | Function compose two formatters. Will feed the result of one 168 | -- formatter into another. 169 | (%.) :: Format r (Builder -> r') -> Format r' a -> Format r a 170 | (%.) (Format a) (Format b) = Format (b . a) 171 | infixr 8 %. 172 | 173 | -- | Don't format any data, just output a constant 'Builder'. 174 | now :: Builder -> Format r r 175 | now a = Format ($ a) 176 | 177 | -- | Monadic indexed bind for holey monoids. 178 | bind :: Format r a -> (Builder -> Format r' r) -> Format r' a 179 | m `bind` f = Format $ \k -> runFormat m (\a -> runFormat (f a) k) 180 | 181 | -- | Functorial map over a formatter's input. Example: @format (mapf (drop 1) string) \"hello\"@ 182 | mapf :: (a -> b) -> Format r (b -> t) -> Format r (a -> t) 183 | mapf f m = Format (\k -> runFormat m k . f) 184 | 185 | -- | Format a value of type @a@ using a function of type @a -> 186 | -- 'Builder'@. For example, @later (f :: Int -> Builder)@ produces 187 | -- @Format r (Int -> r)@. 188 | later :: (a -> Builder) -> Format r (a -> r) 189 | later f = Format (. f) 190 | 191 | -- | Run the formatter and return a lazy 'Text' value. 192 | format :: Format Text a -> a 193 | format m = runFormat m T.toLazyText 194 | 195 | -- | Run the formatter and return a strict 'S.Text' value. 196 | sformat :: Format S.Text a -> a 197 | sformat m = runFormat m (T.toStrict . T.toLazyText) 198 | 199 | -- | Run the formatter and return a 'Builder' value. 200 | bprint :: Format Builder a -> a 201 | bprint m = runFormat m id 202 | 203 | -- | Run the formatter and return a 'Builder' value. 204 | -- 205 | -- This is a newer synonym for 'bprint', following the naming convention set by 'format' and 'sformat'. 206 | bformat :: Format Builder a -> a 207 | bformat m = runFormat m id 208 | 209 | -- | Run the formatter and print out the text to stdout. 210 | fprint :: MonadIO m => Format (m ()) a -> a 211 | fprint m = runFormat m (liftIO . T.putStr . T.toLazyText) 212 | 213 | -- | Run the formatter and print out the text to stdout, followed by a newline. 214 | fprintLn :: MonadIO m => Format (m ()) a -> a 215 | fprintLn m = runFormat m (liftIO . T.putStrLn . T.toLazyText) 216 | 217 | -- | Run the formatter and put the output onto the given 'Handle'. 218 | hprint :: MonadIO m => Handle -> Format (m ()) a -> a 219 | hprint h m = runFormat m (liftIO . T.hPutStr h . T.toLazyText) 220 | 221 | -- | Run the formatter and put the output and a newline onto the given 'Handle'. 222 | hprintLn :: MonadIO m => Handle -> Format (m ()) a -> a 223 | hprintLn h m = runFormat m (liftIO . T.hPutStrLn h . T.toLazyText) 224 | 225 | -- | Run the formatter and return a list of characters. 226 | formatToString :: Format String a -> a 227 | formatToString m = runFormat m (TL.unpack . TLB.toLazyText) 228 | -------------------------------------------------------------------------------- /src/Formatting/Internal/Raw.hs: -------------------------------------------------------------------------------- 1 | -- | Reexports of things that were previously in the @text-format@ package. 2 | 3 | module Formatting.Internal.Raw 4 | ( 5 | module Data.Text.Format, 6 | module Data.Text.Format.Functions, 7 | module Data.Text.Lazy.Builder.Int, 8 | module Data.Text.Format.Types 9 | ) where 10 | 11 | import Data.Text.Format 12 | import Data.Text.Format.Functions 13 | import Data.Text.Lazy.Builder.Int 14 | import Data.Text.Format.Types 15 | -------------------------------------------------------------------------------- /src/Formatting/ShortFormatters.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | -- | 3 | -- Module : Formatting.ShortFormatters 4 | -- Copyright : (c) 2013 Chris Done, 2013 Shachaf Ben-Kiki 5 | -- License : BSD3 6 | -- Maintainer : chrisdone@gmail.com 7 | -- Stability : experimental 8 | -- Portability : GHC 9 | -- 10 | -- Single letters for short formatting. 11 | 12 | module Formatting.ShortFormatters 13 | ( t 14 | , d 15 | , b 16 | , o 17 | , x 18 | , st 19 | , s 20 | , sh 21 | , c 22 | , f 23 | , sf 24 | , l 25 | , r 26 | ) where 27 | 28 | import Formatting.Formatters (bin, int, oct) 29 | import Formatting.Internal 30 | 31 | import qualified Data.Text as S 32 | import qualified Data.Text as T 33 | import qualified Data.Text.Format as T 34 | import Data.Text.Lazy (Text) 35 | import qualified Data.Text.Lazy.Builder as T 36 | import Formatting.Buildable (Buildable) 37 | import qualified Formatting.Buildable as B (build) 38 | 39 | -- | Output a lazy text. 40 | t :: Format r (Text -> r) 41 | t = later T.fromLazyText 42 | {-# INLINE t #-} 43 | 44 | -- | Render an integral e.g. 123 -> \"123\", 0 -> \"0\". 45 | d :: Integral a => Format r (a -> r) 46 | d = int 47 | {-# INLINE d #-} 48 | 49 | -- | Render an integer using binary notation. (No leading 0b is 50 | -- added.) 51 | b :: Integral a => Format r (a -> r) 52 | b = bin 53 | {-# INLINE b #-} 54 | 55 | -- | Render an integer using octal notation. (No leading 0o is added.) 56 | o :: Integral a => Format r (a -> r) 57 | o = oct 58 | {-# INLINE o #-} 59 | 60 | -- | Render an integer using hexadecimal notation. (No leading 0x is 61 | -- added.) 62 | x :: Integral a => Format r (a -> r) 63 | x = later T.hex 64 | {-# INLINE x #-} 65 | 66 | -- | Output a strict text. 67 | st :: Format r (S.Text -> r) 68 | st = later T.fromText 69 | {-# INLINE st #-} 70 | 71 | -- | Output a string. 72 | s :: Format r (String -> r) 73 | s = later (T.fromText . T.pack) 74 | {-# INLINE s #-} 75 | 76 | -- | Output a showable value (instance of 'Show') by turning it into 77 | -- 'Text'. 78 | sh :: Show a => Format r (a -> r) 79 | sh = later (T.fromText . T.pack . show) 80 | {-# INLINE sh #-} 81 | 82 | -- | Output a character. 83 | c :: Format r (Char -> r) 84 | c = later B.build 85 | {-# INLINE c #-} 86 | 87 | -- | Render a floating point number using normal notation, with the 88 | -- given number of decimal places. 89 | f :: Real a => Int -> Format r (a -> r) 90 | f i = later (T.fixed i) 91 | {-# INLINE f #-} 92 | 93 | -- | Render a floating point number using the smallest number of 94 | -- digits that correctly represent it. 95 | sf :: Real a => Format r (a -> r) 96 | sf = later T.shortest 97 | {-# INLINE sf #-} 98 | 99 | -- | Pad the left hand side of a string until it reaches @k@ characters 100 | -- wide, if necessary filling with character @ch@. 101 | l :: Buildable a => Int -> Char -> Format r (a -> r) 102 | l i ch = later (T.left i ch) 103 | {-# INLINE l #-} 104 | 105 | -- | Pad the right hand side of a string until it reaches @k@ characters 106 | -- wide, if necessary filling with character @ch@. 107 | r :: Buildable a => Int -> Char -> Format r (a -> r) 108 | r i ch = later (T.right i ch) 109 | {-# INLINE r #-} 110 | -------------------------------------------------------------------------------- /src/Formatting/Time.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | -- | Formatters for time. 5 | 6 | module Formatting.Time 7 | ( tz 8 | , tzName 9 | , datetime 10 | , hm 11 | , hms 12 | , hmsL 13 | , hmsPL 14 | , dayHalf 15 | , dayHalfU 16 | , hour24 17 | , hour12 18 | , hour24S 19 | , hour12S 20 | , minute 21 | , second 22 | , pico 23 | , decimals 24 | , epoch 25 | , dateSlash 26 | , dateDash 27 | , dateSlashL 28 | , year 29 | , yy 30 | , century 31 | , monthName 32 | , monthNameShort 33 | , month 34 | , dayOfMonth 35 | , dayOfMonthOrd 36 | , dayOfMonthS 37 | , day 38 | , weekYear 39 | , weekYY 40 | , weekCentury 41 | , week 42 | , dayOfWeek 43 | , dayNameShort 44 | , dayName 45 | , weekFromZero 46 | , dayOfWeekFromZero 47 | , weekOfYearMon 48 | , diff 49 | , years 50 | , days 51 | , hours 52 | , minutes 53 | , seconds 54 | , diffComponents 55 | , customDiffComponents 56 | , fmt 57 | , customTimeFmt 58 | ) where 59 | 60 | import Data.List (find) 61 | import Data.Tuple 62 | import Formatting.Formatters hiding (build, base) 63 | import Formatting.Internal 64 | 65 | import Data.Text (Text) 66 | import qualified Data.Text as T 67 | import Formatting.Buildable 68 | import Data.Time (FormatTime, formatTime, defaultTimeLocale) 69 | import System.Locale () 70 | import Control.Monad.Trans.State.Strict 71 | 72 | -- * For 'TimeZone' (and 'ZonedTime' and 'UTCTime'): 73 | 74 | -- | Timezone offset on the format @-HHMM@. 75 | tz :: FormatTime a => Format r (a -> r) 76 | tz = later (build . fmt "%z") 77 | 78 | -- | Timezone name. 79 | tzName :: FormatTime a => Format r (a -> r) 80 | tzName = later (build . fmt "%Z") 81 | 82 | -- | As 'dateTimeFmt' @locale@ (e.g. @%a %b %e %H:%M:%S %Z %Y@). 83 | datetime :: FormatTime a => Format r (a -> r) 84 | datetime = later (build . fmt "%c") 85 | 86 | -- * For 'TimeOfDay' (and 'LocalTime' and 'ZonedTime' and 'UTCTime'): 87 | 88 | -- | Same as @%H:%M@. 89 | hm :: FormatTime a => Format r (a -> r) 90 | hm = later (build . fmt "%R") 91 | 92 | -- | Same as @%H:%M:%S@. 93 | hms :: FormatTime a => Format r (a -> r) 94 | hms = later (build . fmt "%T") 95 | 96 | -- | As 'timeFmt' @locale@ (e.g. @%H:%M:%S@). 97 | hmsL :: FormatTime a => Format r (a -> r) 98 | hmsL = later (build . fmt "%X") 99 | 100 | -- | As 'time12Fmt' @locale@ (e.g. @%I:%M:%S %p@). 101 | hmsPL :: FormatTime a => Format r (a -> r) 102 | hmsPL = later (build . fmt "%r") 103 | 104 | -- | Day half from ('amPm' @locale@), converted to lowercase, @am@, 105 | -- @pm@. 106 | dayHalf :: FormatTime a => Format r (a -> r) 107 | dayHalf = later (build . fmt "%P") 108 | 109 | -- | Day half from ('amPm' @locale@), @AM@, @PM@. 110 | dayHalfU :: FormatTime a => Format r (a -> r) 111 | dayHalfU = later (build . fmt "%p") 112 | 113 | -- | Hour, 24-hour, leading 0 as needed, @00@ - @23@. 114 | hour24 :: FormatTime a => Format r (a -> r) 115 | hour24 = later (build . fmt "%H") 116 | 117 | -- | Hour, 12-hour, leading 0 as needed, @01@ - @12@. 118 | hour12 :: FormatTime a => Format r (a -> r) 119 | hour12 = later (build . fmt "%I") 120 | 121 | -- | Hour, 24-hour, leading space as needed, @ 0@ - @23@. 122 | hour24S :: FormatTime a => Format r (a -> r) 123 | hour24S = later (build . fmt "%k") 124 | 125 | -- | Hour, 12-hour, leading space as needed, @ 1@ - @12@. 126 | hour12S :: FormatTime a => Format r (a -> r) 127 | hour12S = later (build . fmt "%l") 128 | 129 | -- | Minute, @00@ - @59@. 130 | minute :: FormatTime a => Format r (a -> r) 131 | minute = later (build . fmt "%M") 132 | 133 | -- | Second, without decimal part, @00@ - @60@. 134 | second :: FormatTime a => Format r (a -> r) 135 | second = later (build . fmt "%S") 136 | 137 | -- | Picosecond, including trailing zeros, @000000000000@ - 138 | -- @999999999999@. 139 | pico :: FormatTime a => Format r (a -> r) 140 | pico = later (build . fmt "%q") 141 | 142 | -- | Decimal point and up to 12 second decimals, without trailing 143 | -- zeros. For a whole number of seconds, this produces the empty 144 | -- string. 145 | decimals :: FormatTime a => Format r (a -> r) 146 | decimals = later (build . fmt "%Q") 147 | 148 | -- * For 'UTCTime' and 'ZonedTime' 149 | -- 150 | -- Number of whole seconds since the Unix epoch. For times before 151 | -- the Unix epoch, this is a negative number. Note that in @%s.%q@ and @%s%Q@ 152 | -- the decimals are positive, not negative. For example, 0.9 seconds 153 | -- before the Unix epoch is formatted as @-1.1@ with @%s%Q@. 154 | epoch :: FormatTime a => Format r (a -> r) 155 | epoch = later (build . fmt "%s") 156 | 157 | -- * For 'Day' (and 'LocalTime' and 'ZonedTime' and 'UTCTime'): 158 | 159 | -- | Same as @%m\/%d\/%y@. 160 | dateSlash :: FormatTime a => Format r (a -> r) 161 | dateSlash = later (build . fmt "%D") 162 | 163 | -- | Same as @%Y-%m-%d@. 164 | dateDash :: FormatTime a => Format r (a -> r) 165 | dateDash = later (build . fmt "%F") 166 | 167 | -- | As 'dateFmt' @locale@ (e.g. @%m\/%d\/%y@). 168 | dateSlashL :: FormatTime a => Format r (a -> r) 169 | dateSlashL = later (build . fmt "%x") 170 | 171 | -- | Year. 172 | year :: FormatTime a => Format r (a -> r) 173 | year = later (build . fmt "%Y") 174 | 175 | -- | Last two digits of year, @00@ - @99@. 176 | yy :: FormatTime a => Format r (a -> r) 177 | yy = later (build . fmt "%y") 178 | 179 | -- | Century (being the first two digits of the year), @00@ - @99@. 180 | century :: FormatTime a => Format r (a -> r) 181 | century = later (build . fmt "%C") 182 | 183 | -- | Month name, long form ('fst' from 'months' @locale@), @January@ - 184 | -- @December@. 185 | monthName :: FormatTime a => Format r (a -> r) 186 | monthName = later (build . fmt "%B") 187 | 188 | -- | @ %H] month name, short form ('snd' from 'months' @locale@), 189 | -- @Jan@ - @Dec@. 190 | monthNameShort :: FormatTime a => Format r (a -> r) 191 | monthNameShort = later (build . fmt "%b") 192 | 193 | -- | Month of year, leading 0 as needed, @01@ - @12@. 194 | month :: FormatTime a => Format r (a -> r) 195 | month = later (build . fmt "%m") 196 | 197 | -- | Day of month, leading 0 as needed, @01@ - @31@. 198 | dayOfMonth :: FormatTime a => Format r (a -> r) 199 | dayOfMonth = later (build . fmt "%d") 200 | 201 | -- | Day of month, @1st@, @2nd@, @25th@, etc. 202 | dayOfMonthOrd :: FormatTime a => Format r (a -> r) 203 | dayOfMonthOrd = later (bprint ords . toInt) 204 | where toInt :: FormatTime a => a -> Int 205 | toInt = read . formatTime defaultTimeLocale "%d" 206 | 207 | -- | Day of month, leading space as needed, @ 1@ - @31@. 208 | dayOfMonthS :: FormatTime a => Format r (a -> r) 209 | dayOfMonthS = later (build . fmt "%e") 210 | 211 | -- | Day of year for Ordinal Date format, @001@ - @366@. 212 | day :: FormatTime a => Format r (a -> r) 213 | day = later (build . fmt "%j") 214 | 215 | -- | Year for Week Date format e.g. @2013@. 216 | weekYear :: FormatTime a => Format r (a -> r) 217 | weekYear = later (build . fmt "%G") 218 | 219 | -- | Last two digits of year for Week Date format, @00@ - @99@. 220 | weekYY :: FormatTime a => Format r (a -> r) 221 | weekYY = later (build . fmt "%g") 222 | 223 | -- | Century (first two digits of year) for Week Date format, @00@ - 224 | -- @99@. 225 | weekCentury :: FormatTime a => Format r (a -> r) 226 | weekCentury = later (build . fmt "%f") 227 | 228 | -- | Week for Week Date format, @01@ - @53@. 229 | week :: FormatTime a => Format r (a -> r) 230 | week = later (build . fmt "%V") 231 | 232 | -- | Day for Week Date format, @1@ - @7@. 233 | dayOfWeek :: FormatTime a => Format r (a -> r) 234 | dayOfWeek = later (build . fmt "%u") 235 | 236 | -- | Day of week, short form ('snd' from 'wDays' @locale@), @Sun@ - 237 | -- @Sat@. 238 | dayNameShort :: FormatTime a => Format r (a -> r) 239 | dayNameShort = later (build . fmt "%a") 240 | 241 | -- | Day of week, long form ('fst' from 'wDays' @locale@), @Sunday@ - 242 | -- @Saturday@. 243 | dayName :: FormatTime a => Format r (a -> r) 244 | dayName = later (build . fmt "%A") 245 | 246 | -- | Week number of year, where weeks start on Sunday (as 247 | -- 'sundayStartWeek'), @00@ - @53@. 248 | weekFromZero :: FormatTime a => Format r (a -> r) 249 | weekFromZero = later (build . fmt "%U") 250 | 251 | -- | Day of week number, @0@ (= Sunday) - @6@ (= Saturday). 252 | dayOfWeekFromZero :: FormatTime a => Format r (a -> r) 253 | dayOfWeekFromZero = later (build . fmt "%w") 254 | 255 | -- | Week number of year, where weeks start on Monday (as 256 | -- 'mondayStartWeek'), @00@ - @53@. 257 | weekOfYearMon :: FormatTime a => Format r (a -> r) 258 | weekOfYearMon = later (build . fmt "%W") 259 | 260 | -- * Time spans, diffs, 'NominalDiffTime', 'DiffTime', etc. 261 | 262 | -- | Display a time span as one time relative to another. Input is 263 | -- assumed to be seconds. Typical inputs are 'NominalDiffTime' and 264 | -- 'DiffTime'. 265 | diff :: (RealFrac n) 266 | => Bool -- ^ Display 'in/ago'? 267 | -> Format r (n -> r) -- ^ Example: '3 seconds ago', 'in three days'.) 268 | diff fix = 269 | later diffed 270 | where 271 | diffed ts = 272 | case find (\(s,_,_) -> abs ts >= s) (reverse ranges) of 273 | Nothing -> "unknown" 274 | Just (_,f,base) -> bprint (prefix % f % suffix) (toInt ts base) 275 | where prefix = now (if fix && ts > 0 then "in " else "") 276 | suffix = now (if fix && ts < 0 then " ago" else "") 277 | toInt ts base = abs (round (ts / base)) :: Int 278 | ranges = 279 | [(0,int % " milliseconds",0.001) 280 | ,(1,int % " seconds",1) 281 | ,(minute',fconst "a minute",0) 282 | ,(minute'*2,int % " minutes",minute') 283 | ,(minute'*30,fconst "half an hour",0) 284 | ,(minute'*31,int % " minutes",minute') 285 | ,(hour',fconst "an hour",0) 286 | ,(hour'*2,int % " hours",hour') 287 | ,(hour'*3,fconst "a few hours",0) 288 | ,(hour'*4,int % " hours",hour') 289 | ,(day',fconst "a day",0) 290 | ,(day'*2,int % " days",day') 291 | ,(week',fconst "a week",0) 292 | ,(week'*2,int % " weeks",week') 293 | ,(month',fconst "a month",0) 294 | ,(month'*2,int % " months",month') 295 | ,(year',fconst "a year",0) 296 | ,(year'*2,int % " years",year')] 297 | where year' = month' * 12 298 | month' = day' * 30 299 | week' = day' * 7 300 | day' = hour' * 24 301 | hour' = minute' * 60 302 | minute' = 60 303 | 304 | -- | Display the absolute value time span in years. 305 | years :: (RealFrac n) 306 | => Int -- ^ Decimal places. 307 | -> Format r (n -> r) 308 | years n = later (bprint (fixed n) . abs . count) 309 | where count n' = n' / 365 / 24 / 60 / 60 310 | 311 | -- | Display the absolute value time span in days. 312 | days :: (RealFrac n) 313 | => Int -- ^ Decimal places. 314 | -> Format r (n -> r) 315 | days n = later (bprint (fixed n) . abs . count) 316 | where count n' = n' / 24 / 60 / 60 317 | 318 | -- | Display the absolute value time span in hours. 319 | hours :: (RealFrac n) 320 | => Int -- ^ Decimal places. 321 | -> Format r (n -> r) 322 | hours n = later (bprint (fixed n) . abs . count) 323 | where count n' = n' / 60 / 60 324 | 325 | -- | Display the absolute value time span in minutes. 326 | minutes :: (RealFrac n) 327 | => Int -- ^ Decimal places. 328 | -> Format r (n -> r) 329 | minutes n = later (bprint (fixed n) . abs . count) 330 | where count n' = n' / 60 331 | 332 | -- | Display the absolute value time span in seconds. 333 | seconds :: (RealFrac n) 334 | => Int -- ^ Decimal places. 335 | -> Format r (n -> r) 336 | seconds n = later (bprint (fixed n) . abs . count) 337 | where count n' = n' 338 | 339 | -- | Display seconds in the following pattern: 340 | -- @00:00:00:00@, which ranges from days to seconds. 341 | diffComponents :: (RealFrac n) => Format r (n -> r) 342 | diffComponents = customDiffComponents (left 2 '0' % ":" % left 2 '0' % ":" % left 2 '0' % ":" % left 2 '0') 343 | 344 | -- | Variation of 'diffComponents', 345 | -- which lets you explicitly specify how to render each component. 346 | customDiffComponents :: (RealFrac n) => (forall r'. Format r' (Integer -> Integer -> Integer -> Integer -> r')) -> Format r (n -> r) 347 | customDiffComponents subFormat = later builder' where 348 | builder' diffTime = flip evalState (round diffTime) $ do 349 | seconds' <- state (swap . flip divMod 60) 350 | minutes' <- state (swap . flip divMod 60) 351 | hours' <- state (swap . flip divMod 24) 352 | days' <- get 353 | return (bprint subFormat days' hours' minutes' seconds') 354 | 355 | -- * Internal. 356 | 357 | -- | Formatter call. Probably don't want to use this. 358 | fmt :: FormatTime a => Text -> a -> Text 359 | fmt f = T.pack . formatTime defaultTimeLocale (T.unpack f) 360 | 361 | -- | Helper for creating custom time formatters 362 | customTimeFmt :: FormatTime a => Text -> Format r (a -> r) 363 | customTimeFmt f = later (build . fmt f) 364 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-11.13 2 | 3 | packages: 4 | - '.' 5 | -------------------------------------------------------------------------------- /test-ghcs: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | # Test GHC 8.8.4 4 | stack clean 5 | stack setup --resolver lts-16.17 6 | stack build . --resolver lts-16.17 --test --ghc-options=-Werror --force-dirty --ghc-options=-fforce-recomp 7 | 8 | # Test GHC 8.4.1 9 | 10 | echo GHC 8.4.1 ... 11 | stack clean 12 | stack setup --resolver nightly-2018-03-20 13 | stack build . --resolver nightly-2018-03-20 --test --ghc-options=-Werror --force-dirty 14 | 15 | # Test GHC 8.2.2 16 | 17 | echo GHC 8.2.2 ... 18 | stack clean 19 | stack setup --resolver lts-10.0 20 | stack build . --resolver lts-10.0 --test --ghc-options=-Werror --force-dirty --ghc-options=-fforce-recomp 21 | 22 | # Test GHC 8.0.2 23 | 24 | echo GHC 8.0.2 ... 25 | stack clean 26 | stack setup --resolver lts-9.20 27 | stack build . --resolver lts-9.20 --test --ghc-options=-Werror --force-dirty --ghc-options=-fforce-recomp 28 | 29 | # Test GHC 8.0.1 30 | 31 | echo GHC 8.0.1 ... 32 | stack clean 33 | stack setup --resolver nightly-2016-05-27 34 | stack build --resolver nightly-2016-05-27 --test --ghc-options=-Werror --force-dirty --ghc-options=-fforce-recomp 35 | 36 | # Test GHC 7.10.3 37 | 38 | echo GHC 7.10.3 ... 39 | stack clean 40 | stack setup --resolver lts-4.1 41 | stack build --resolver lts-4.1 --test --ghc-options=-Werror --force-dirty --ghc-options=-fforce-recomp 42 | 43 | # Test GHC 7.10.2 44 | 45 | echo GHC 7.10.2 ... 46 | stack clean 47 | stack setup --resolver lts-3.0 48 | stack build --resolver lts-3.0 --test --ghc-options=-Werror --force-dirty --ghc-options=-fforce-recomp 49 | -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# OPTIONS -Wno-type-defaults #-} 3 | 4 | import Control.Monad 5 | import Data.Char (isSpace, isUpper) 6 | import Data.Int 7 | import qualified Data.Monoid 8 | import Data.Scientific 9 | import qualified Data.Semigroup 10 | import qualified Data.Text.Lazy as LT 11 | import Data.Time (Day) 12 | import Formatting as F 13 | import Formatting.Time 14 | import Test.Hspec 15 | 16 | main :: IO () 17 | main = hspec spec 18 | 19 | spec :: Spec 20 | spec = do 21 | describe "Regression tests" $ do 22 | describe "https://github.com/AJChapman/formatting/issues/36" $ do 23 | it "format (later id <> later id) \"x\"" $ format (later id Data.Monoid.<> later id) "x" `shouldBe` "xx" 24 | it "format (later id <> later id) \"x\"" $ format (later id Data.Semigroup.<> later id) "x" `shouldBe` "xx" 25 | 26 | describe "https://github.com/AJChapman/formatting/issues/31" $ 27 | it "10^16-1" $ F.format F.int (10 ^ (16 :: Int) - 1 :: Int) `shouldBe` "9999999999999999" 28 | 29 | describe "https://github.com/AJChapman/formatting/issues/28" $ do 30 | it "-100" $ sformat (groupInt 3 ',') (-100 :: Int) `shouldBe` "-100" 31 | it "-100,000,000" $ sformat (groupInt 3 ',') (-100000000 :: Int) `shouldBe` "-100,000,000" 32 | it "100,000,000" $ sformat (groupInt 3 ',') (100000000 :: Int) `shouldBe` "100,000,000" 33 | 34 | describe "https://github.com/bos/text-format/issues/18" $ do 35 | it "build (minBound :: Int)" $ format build (minBound :: Int64) `shouldBe` "-9223372036854775808" 36 | it "build (maxBound :: Int)" $ format build (maxBound :: Int) `shouldBe` "9223372036854775807" 37 | 38 | describe "https://github.com/AJChapman/formatting/issues/62" $ do 39 | it "left 3 '0' (0 :: Int)" $ format (left 3 '0') (0 ::Int) `shouldBe` "000" 40 | it "left 3 '0' (0 :: Word)" $ format (left 3 '0') (0 ::Word) `shouldBe` "000" 41 | 42 | describe "https://github.com/AJChapman/formatting/issues/60" $ 43 | it "build (minBound :: Word)" $ format build (minBound :: Word) `shouldBe` "0" 44 | 45 | describe "https://github.com/AJChapman/formatting/issues/59" $ 46 | it "shortest not scientific" $ format shortest (0.01 :: Double) `shouldBe` "0.01" 47 | 48 | describe "Floating point" $ do 49 | it "Fixed" $ format (fixed 4) (12.123456 :: Double) `shouldBe` "12.1235" 50 | it "Variable" $ format float (12.123456 :: Double) `shouldBe` "12.123456" 51 | it "Shortest" $ format shortest (12.0000 :: Double) `shouldBe` "12" 52 | 53 | describe "Scientific" $ do 54 | it "sci" $ format sci (scientific 60221409 16) `shouldBe` "6.0221409e23" 55 | it "scifmt" $ format (scifmt Exponent (Just 3)) (scientific 60221409 16) `shouldBe` "6.022e23" 56 | it "scifmt" $ format (scifmt Exponent Nothing) (scientific 60221409 16) `shouldBe` "6.0221409e23" 57 | it "scifmt" $ format (scifmt Fixed Nothing) (scientific 60221409 16) `shouldBe` "602214090000000000000000.0" 58 | it "scifmt" $ format (scifmt Generic (Just 5)) (scientific 60221409 16) `shouldBe` "6.02214e23" 59 | 60 | describe "Bytes" $ do 61 | it "1KB" $ format (bytes shortest) (1024 :: Int) `shouldBe` "1KB" 62 | it "1.15GB" $ format (bytes (fixed 2)) (1234567890 :: Int) `shouldBe` "1.15GB" 63 | 64 | describe 65 | "Buildable a => Buildable [a]" 66 | (do it "\"\" :: [Char] (backwards compatibility)" 67 | (shouldBe (format build ("" :: String)) "") 68 | it "\"hi\" :: [Char] (backwards compatibility)" 69 | (shouldBe (format build ("hi" :: String)) "hi") 70 | it "[1,2,3] :: [Int]" 71 | (shouldBe (format build ([1,2,3] :: [Int])) "[1,2,3]") 72 | it "[] :: [Int]" 73 | (shouldBe (format build ([] :: [Int])) "[]")) 74 | 75 | describe "ords" $ do 76 | let tests :: [(Int, String)] 77 | tests = [ ( 1, "1st") 78 | , ( 2, "2nd") 79 | , ( 3, "3rd") 80 | , ( 4, "4th") 81 | , ( 5, "5th") 82 | , ( 6, "6th") 83 | , ( 7, "7th") 84 | , ( 8, "8th") 85 | , ( 9, "9th") 86 | , (10, "10th") 87 | , (11, "11th") 88 | , (12, "12th") 89 | , (13, "13th") 90 | , (14, "14th") 91 | , (15, "15th") 92 | , (16, "16th") 93 | , (17, "17th") 94 | , (18, "18th") 95 | , (19, "19th") 96 | , (20, "20th") 97 | , (21, "21st") 98 | , (22, "22nd") 99 | , (23, "23rd") 100 | , (24, "24th") 101 | , (25, "25th") 102 | , (26, "26th") 103 | , (27, "27th") 104 | , (28, "28th") 105 | , (29, "29th") 106 | , (30, "30th") 107 | , (31, "31st") 108 | , (31, "31st") 109 | , (32, "32nd") 110 | , (33, "33rd") 111 | , (34, "34th") 112 | ] 113 | 114 | forM_ tests $ \(input, output) -> it output $ format ords input `shouldBe` LT.pack output 115 | 116 | describe "plural" $ do 117 | let formatPeople = format (int % " " <> plural "person" "people" % ".") 118 | it "formats a person" $ formatPeople (1 :: Int) `shouldBe` "1 person." 119 | it "formats a person" $ formatPeople (3 :: Int) `shouldBe` "3 people." 120 | 121 | describe "diffComponents" $ do 122 | it "59s" $ flip shouldBe "00:00:00:59" $ format diffComponents (59 :: Double) 123 | it "minute" $ flip shouldBe "00:00:01:00" $ format diffComponents (60 :: Double) 124 | it "90s" $ flip shouldBe "00:00:01:30" $ format diffComponents (90 :: Double) 125 | it "hour" $ flip shouldBe "00:01:00:00" $ format diffComponents (3600 :: Double) 126 | it "day" $ flip shouldBe "01:00:00:00" $ format diffComponents (86400 :: Double) 127 | 128 | describe "container formatters" $ do 129 | it "maybed Nothing" $ format (maybed "Goodbye" text) Nothing `shouldBe` "Goodbye" 130 | it "maybed Just" $ format (maybed "Goodbye" text) (Just "Hello") `shouldBe` "Hello" 131 | it "optioned Nothing" $ format (optioned text) Nothing `shouldBe` "" 132 | it "optioned Just" $ format (optioned text) (Just "Hello") `shouldBe` "Hello" 133 | it "eithered Left" $ format (eithered text int) (Left "Error!") `shouldBe` "Error!" 134 | it "eithered Right" $ format (eithered text int) (Right 69) `shouldBe` "69" 135 | it "lefted Left" $ format (lefted text) (Left "bingo") `shouldBe` "bingo" 136 | it "lefted Right" $ format (lefted text) (Right 16) `shouldBe` "" 137 | it "righted Left" $ format (righted text) (Left 16) `shouldBe` "" 138 | it "righted Right" $ format (righted text) (Right "bingo") `shouldBe` "bingo" 139 | 140 | describe "list formatters" $ do 141 | it "concatenated" $ format (concatenated text) ["one", "two", "three"] `shouldBe` "onetwothree" 142 | it "joinedWith" $ format (joinedWith (mconcat . reverse) int) [123, 456, 789] `shouldBe` "789456123" 143 | it "intercalated" $ format (intercalated "||" int) [1, 2, 3] `shouldBe` "1||2||3" 144 | it "unworded" $ format (unworded int) [1, 2, 3] `shouldBe` "1 2 3" 145 | it "unlined" $ format (unlined char) ['a'..'c'] `shouldBe` "a\nb\nc\n" 146 | it "spaced" $ format (spaced int) [1, 2, 3] `shouldBe` "1 2 3" 147 | it "commaSep" $ format (took 5 (commaSep int)) [1..] `shouldBe` "1,2,3,4,5" 148 | it "commaSpaceSep" $ format (took 3 (commaSpaceSep ords)) [1..] `shouldBe` "1st, 2nd, 3rd" 149 | it "list" $ format (list stext) ["one", "two", "three"] `shouldBe` "[one, two, three]" 150 | it "qlist" $ format (qlist stext) ["one", "two", "three"] `shouldBe` "[\"one\", \"two\", \"three\"]" 151 | it "took" $ format (took 7 (list bin)) [1..] `shouldBe` "[1, 10, 11, 100, 101, 110, 111]" 152 | it "dropped" $ format (dropped 3 (list int)) [1..6] `shouldBe` "[4, 5, 6]" 153 | 154 | describe "splitting formatters" $ do 155 | it "splat" $ format (splat isSpace commaSpaceSep stext) "This\t is\n\t\t poorly formatted " `shouldBe` "This, , , is, , , , , poorly, formatted, , , " 156 | it "splatWith" $ format (splatWith (LT.chunksOf 3) list int) 1234567890 `shouldBe` "[123, 456, 789, 0]" 157 | it "splatOn" $ format (splatOn "," unlined text) "one,two,three" `shouldBe` "one\ntwo\nthree\n" 158 | it "worded" $ format (worded list text) "one two three " `shouldBe` "[one, two, three]" 159 | it "lined" $ format (lined qlist text) "one two three\n\nfour five six\nseven eight nine\n\n" `shouldBe` "[\"one two three\", \"\", \"four five six\", \"seven eight nine\", \"\"]" 160 | 161 | describe "altering combinators" $ do 162 | it "alteredWith" $ format (alteredWith LT.reverse int) 123456 `shouldBe` "654321" 163 | it "charsKeptIf" $ format (charsKeptIf isUpper text) "Data.Char.isUpper" `shouldBe` "DCU" 164 | it "charsRemovedIf" $ format (charsRemovedIf isUpper text) "Data.Char.isUpper" `shouldBe` "ata.har.ispper" 165 | it "replaced" $ format (replaced "Bruce" "<redacted>" stext) "Bruce replied that Bruce's name was, in fact, '<redacted>'." `shouldBe` "<redacted> replied that <redacted>'s name was, in fact, '<redacted>'." 166 | it "uppercased" $ format (uppercased text) "I'm not shouting, you're shouting." `shouldBe` "I'M NOT SHOUTING, YOU'RE SHOUTING." 167 | it "lowercased" $ format (lowercased text) "Cd SrC/; Rm -Rf *" `shouldBe` "cd src/; rm -rf *" 168 | it "titlecased" $ format (titlecased string) "the life of brian" `shouldBe` "The Life Of Brian" 169 | it "ltruncated" $ format (ltruncated 5 text) "hellos" `shouldBe` "he..." 170 | it "ltruncated, non-truncated" $ format (ltruncated 5 text) "hello" `shouldBe` "hello" 171 | it "rtruncated" $ format (rtruncated 5 text) "hellos" `shouldBe` "...os" 172 | it "rtruncated, non-truncated" $ format (rtruncated 5 text) "hello" `shouldBe` "hello" 173 | it "ctruncated" $ format (ctruncated 15 4 text) "The quick brown fox jumps over the lazy dog." `shouldBe` "The quick brown...dog." 174 | it "ctruncated, non-truncated" $ format (ctruncated 15 4 text) "The quick brown fox" `shouldBe` "The quick brown fox" 175 | it "lpadded" $ format (lpadded 7 ' ' int) 1 `shouldBe` " 1" 176 | it "lpadded doesn't shorten" $ format (lpadded 7 ' ' int) 123456789 `shouldBe` "123456789" 177 | it "rpadded" $ format (rpadded 7 ' ' int) 1 `shouldBe` "1 " 178 | it "cpadded" $ format (cpadded 7 ' ' int) 1 `shouldBe` " 1 " 179 | it "lfixed short" $ format (lfixed 10 ' ' int) 123 `shouldBe` "123 " 180 | it "lfixed at length" $ format (lfixed 10 ' ' int) 1234567890 `shouldBe` "1234567890" 181 | it "lfixed long" $ format (lfixed 10 ' ' int) 123456789012345 `shouldBe` "1234567..." 182 | it "rfixed short" $ format (rfixed 10 ' ' int) 123 `shouldBe` " 123" 183 | it "rfixed at length" $ format (rfixed 10 ' ' int) 1234567890 `shouldBe` "1234567890" 184 | it "rfixed long" $ format (rfixed 10 ' ' int) 123456789012345 `shouldBe` "...9012345" 185 | it "cfixed short" $ format (cfixed 4 3 ' ' int) 123 `shouldBe` " 123 " 186 | it "cfixed at length" $ format (cfixed 4 3 ' ' int) 1234567890 `shouldBe` "1234567890" 187 | it "cfixed long" $ format (cfixed 4 3 ' ' int) 123456789012345 `shouldBe` "1234...345" 188 | 189 | describe "wrapping combinators" $ do 190 | it "prefixed" $ format ("The answer is: " % prefixed "wait for it... " int) 42 `shouldBe` "The answer is: wait for it... 42" 191 | it "prefixed, combining" $ format (unlined (indented 4 (prefixed "- " int))) [1, 2, 3] `shouldBe` " - 1\n - 2\n - 3\n" 192 | it "suffixed" $ format (suffixed "!!!" int) 7 `shouldBe` "7!!!" 193 | it "surrounded" $ format (surrounded "***" string) "glue" `shouldBe` "***glue***" 194 | it "enclosed" $ format (enclosed "<!--" "-->" text) "an html comment" `shouldBe` "<!--an html comment-->" 195 | it "squoted" $ let obj :: Maybe (Maybe Int); obj = Just Nothing in format ("The object is: " % squoted shown % ".") obj `shouldBe` "The object is: 'Just Nothing'." 196 | it "dquoted" $ format ("He said it was based on " % dquoted stext % ".") "science" `shouldBe` "He said it was based on \"science\"." 197 | it "parenthesised" $ format (took 5 (list (parenthesised int))) [1..] `shouldBe` "[(1), (2), (3), (4), (5)]" 198 | it "squared" $ format (squared int) 7 `shouldBe` "[7]" 199 | it "braced" $ format ("\\begin" % braced text) "section" `shouldBe` "\\begin{section}" 200 | it "angled" $ format (list (angled text)) ["html", "head", "title", "body", "div", "span"] `shouldBe` "[<html>, <head>, <title>, <body>, <div>, <span>]" 201 | it "backticked" $ format ("Be sure to run" %+ backticked builder %+ "as root.") ":(){:|:&};:" `shouldBe` "Be sure to run `:(){:|:&};:` as root." 202 | 203 | describe "indenters" $ do 204 | it "indented" $ format (indented 4 int) 7 `shouldBe` " 7" 205 | it "indentedLines" $ format ("The lucky numbers are:\n" % indentedLines 4 int) [7, 13, 1, 42] `shouldBe` "The lucky numbers are:\n 7\n 13\n 1\n 42\n" 206 | it "reindented" $ format (reindented 2 text) "one\ntwo\nthree" `shouldBe` " one\n two\n three\n" 207 | 208 | describe "numerical adapters" $ do 209 | it "roundedTo" $ format (list (roundedTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[11, 7, 1, 3]" 210 | it "truncatedTo" $ format (list (truncatedTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[10, 6, 1, 3]" 211 | it "ceilingedTo" $ format (list (ceilingedTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[11, 7, 1, 4]" 212 | it "flooredTo" $ format (list (flooredTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[10, 6, 1, 3]" 213 | 214 | describe "structure formatting" $ 215 | it "accessed" $ format (accessed fst int) (1, "hello") `shouldBe` "1" 216 | -- describe "lens formatters" $ do 217 | -- it "viewed" $ flip shouldBe "(viewed _1 int) (1, "hello")" $ format 218 | 219 | describe "fixed-width numbers" $ do 220 | it "binPrefix" $ format (binPrefix 16) 4097 `shouldBe` "0b0001000000000001" 221 | it "octPrefix" $ format (octPrefix 16) 4097 `shouldBe` "0o0000000000010001" 222 | it "hexPrefix" $ format (hexPrefix 16) 4097 `shouldBe` "0x0000000000001001" 223 | 224 | describe "mappend with added space (<%+>)" $ do 225 | let testTime = (read "2022-06-06") :: Day 226 | it "combines formatters, adding a space" $ format (year <%+> month <%+> dayOfMonth) testTime `shouldBe` "2022 06 06" 227 | --------------------------------------------------------------------------------