formatting-7.2.0/0000755000000000000000000000000007346545000012063 5ustar0000000000000000formatting-7.2.0/CHANGELOG.md0000644000000000000000000000663607346545000013707 0ustar00000000000000007.2.0 * Added `FromBuilder` and `formatted` to simplify using formatting with other APIs (thanks Kyle Butt). * Updated examples in comments to pass cabal-docspec (thanks Kyle Butt). * Fixed haddock parsing in ghc-8.8.4 (thanks Oleg Grenrus). * Generalised IO printing functions to use MonadIO (thanks Oleg Grenrus). * Added `(%+)` and `(<%+>)` for appending formatters with a space between them, the latter also behaving like `(<>)` (thanks Oleg Grenrus). * Allow building with Cabal 2.2 (thanks Jens Petersen). * Removed unused dependency on `ghc-prim` * Add a `no-double-conversion` build flag to optionally remove the dependency on `double-conversion` (Thanks Janus Troelsen) * The `no-double-conversion` flag also fixes the build on GHC 9.4 on which `double-conversion` is apparently broken 7.1.3 * 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). 7.1.2 * 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. * formatting now compiles on GHCJS (due to the change above). * Added some benchmarking, starting based on code from the `string-interpolate` package. * Added INLINE pragmas to many very short functions. Results in better performance in the benchmarks. 7.1.1 * Added `charsKeptIf` and `charsRemovedIf`. 7.1.0 * Added common container formatter combinators: `maybed`, `optioned`, `eithered`, `lefted`, and `righted`. 7.0.0.2 * Removed unnecessary dependencies on array and bytestring * Actually removed code to support GHC < 8.4 7.0.0.1 * Added README.md to extra-source-files so it shows up on Hackage 7.0.0 * Introduced `Formatting.Combinators`. * Fixed: #62 and #60: incorrect formatting of Integral types that do not have negative values (e.g. Word) * Fixed: #59 rendering of floats e.g. 0.01 as "0.01" rather than "1.0e-2" * Added dependency of double-conversion to provide fast and correct rendering of floating-point numbers (including the fix for #59). * Make compatible with bytestring-0.11.0.0 * Removed -O2 ghc flag * Updated .cabal file version from 1.8 to 2.4 * Drop support for GHC < 8.4 6.3.7 * Introduced instance `Buildable a => Buildable [a]`. 6.3.6 * Bring back `int :: Integral a => Format r (a -> r)` 6.3.5 * Avoid pointless conversions on Float/Double. 6.3.3 * The `Data.Text.Format` hierarchy was reexported as `Formatting.Internal.Raw`. 6.3.1 * Proper GHC 7.10 -> GHC 8.4 support 6.3.0 * Folded the `text-format` package into this package, removed the `double-conversion` dependency. Lost the following functions in this: * `prec` * `expt` * Added a test suite with regression tests: * Fixed: #31 * Fixed: #28 * Fixed: https://github.com/bos/text-format/issues/18 6.2.5 * Changed microseconds to display as "us" to avoid unicode issues. 6.2.1 * Added bytesDecimal 6.2.0 * Dropped Holey/HoleyT in favour of simpler Format type. * Added Monoid instance. * Added back Category instance. * Dropped Functor instance. 6.1.1 * Add support for GHC 7.10 (time update). 6.1.0 * Add formatter for TimeSpec. 6.0.0 * Changed the type of `Format`. Now you write `Format r (a -> r)` instead of `Format a`. * Add `formatToString` function. formatting-7.2.0/LICENSE0000644000000000000000000000275607346545000013102 0ustar0000000000000000Copyright (c) 2013, Chris Done All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Chris Done nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. formatting-7.2.0/README.md0000644000000000000000000011667607346545000013363 0ustar0000000000000000# 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) Formatting is a type-safe and flexible library for formatting text from built-in or custom data types. - [Hackage Documentation](https://hackage.haskell.org/package/formatting) - [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}` ## Usage You will probably need the `OverloadedStrings` language extension, and to import `Formatting`: ```haskell {-# LANGUAGE OverloadedStrings #-} import Formatting ``` You may also need some or all of these: ```haskell import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as TLB ``` Now a simple example: ```haskell > format ("Person's name is " % text % " and age is " % int) "Dave" 54 "Person's name is Dave and age is 54" ``` 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`. They are all joined together using the `%` operator, producing a formatter which takes two arguments: a lazy `Text` and an `Integral`. It produces a lazy `Text`, because we used `format`. To produce other string types, or print the result instead, refer to this table: | To produce a | use | | ------------- | ------------------ | | `TL.Text` | [`format`] | | `T.Text` | [`sformat`] | | `Builder` | [`bformat`] | | `String` | [`formatToString`] | To print the values instead, refer to this table: | To print to | use | | ----------------------------- | ------------- | | `stdout` | [`fprint`] | | `stdout`, appending a newline | [`fprintLn`] | | a handle | [`hprint`] | | a handle, appending a newline | [`hprintLn`] | 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. More on this below. ### Formatter Quick Reference Built-in formatters: | To format a | e.g. | as | use | short form | | --------------------------------------:| ------------------------ | ---------------- | ---------------------------------- | ---------- | | lazy `Text` | `"Hello"` | `"Hello"` | [`text`] | [`t`] | | strict `Text` | `"World!"` | `"World!"` | [`stext`] | [`st`] | | `String` | `"Goodbye"` | `"Goodbye"` | [`string`] | [`s`] | | `Builder` | `"Bathtub"` | `"Bathtub"` | [`builder`] | | | `Show a => a` | `[1, 2, 3]` | `"[1, 2, 3]"` | [`shown`] | [`sh`] | | `Char` | `'!'` | `"!"` | [`char`] | [`c`] | | `Integral a => a` | `23` | `"23"` | [`int`] | [`d`] | | `Real a => a` | `123.32` | `"123.32"` | [`float`] | [`sf`] | | `Real a => a` | `123.32` | `"123.320"` | [`fixed`] `3` | [`f`] | | `Scientific` | `scientific 60221409 16` | `"6.0221409e23"` | [`sci`] | | | `Scientific` | `scientific 60221409 16` | `"6.022e23"` | [`scifmt`] `Exponent (Just 3)` | | | `Buildable n, Integral n => n` | `123456` | `"12.34.56"` | [`groupInt`] `2 '.'` | | | `Buildable n, Integral n => n` | `12000` | `"12,000"` | [`commas`] | | | `Integral n => n` | `32` | `"32nd"` | [`ords`] | | | `Num a, Eq a => a` | `1` | `"1 ant"` | `int <>` [`plural`] `"ant" "ants"` | | | `Num a, Eq a => a` | `2` | `"2 ants"` | `int <>` [`plural`] `"ant" "ants"` | | | `Enum a => a` | `a` | `"97"` | [`asInt`] | | | `Integral a => a` | `23` | `"10111"` | [`bin`] | [`b`] | | `Integral a => a` | `23` | `"0b10111"` | [`prefixBin`] | | | `Integral a => a` | `23` | `"27"` | [`oct`] | [`o`] | | `Integral a => a` | `23` | `"0o27"` | [`prefixOct`] | | | `Integral a => a` | `23` | `"17"` | [`hex`] | [`x`] | | `Integral a => a` | `23` | `"0x17"` | [`prefixHex`] | | | `Integral a => a` | `23` | `"13"` | [`base`] `20` | | | `Buildable a => a` | `10` | `" 10"` | [`left`] `4 ' '` | [`l`] | | `Buildable a => a` | `10` | `"10 "` | [`right`] `4 ' '` | [`r`] | | `Buildable a => a` | `10` | `" 10 "` | [`center`] `4 ' '` | | | `Buildable a => a` | `123456` | `"123"` | [`fitLeft`] `3` | | | `Buildable a => a` | `123456` | `"456"` | [`fitRight`] `3` | | | `Buildable a => a` | `True` | `"True"` | [`build`] | | | `a` | `undefined` | `"gronk!"` | [`fconst`] `"gronk!"` | | ### Formatter Combinator Quick Reference Formatter combinators take a formatter and modify it somehow, e.g. by using it to format elements of a list, or changing its output. Built-in formatter combinators: | To format a | e.g. | as | use | | ----------------------------------------:| ------------------------ | ----------------------------------- | ---------------------------------------- | | `Maybe a` | `Nothing` | `"Goodbye"` | [`maybed`] `"Goodbye" text` | | `Maybe a` | `Just "Hello"` | `"Hello"` | [`maybed`] `"Goodbye" text` | | `Maybe a` | `Nothing` | `""` | [`optioned`] `text` | | `Maybe a` | `Just "Hello"` | `"Hello"` | [`optioned`] `text` | | `Either a b` | `Left "Error!"` | `"Error!"` | [`eithered`] `text int` | | `Either a b` | `Right 69` | `"69"` | [`eithered`] `text int` | | `Either a x` | `Left "bingo"` | `"bingo"` | [`lefted`] `text` | | `Either a x` | `Right 16` | `""` | [`lefted`] `text` | | `Either x a` | `Right "bingo"` | `"bingo"` | [`righted`] `text` | | `Either x a` | `Left 16` | `""` | [`righted`] `text` | | `Foldable t => t a` | `[1, 2, 3]` | `"1st2nd3rd"` | [`concatenated`] `ords` | | `Foldable t => t a` | `[123, 456, 789]` | `"789456123"` | [`joinedWith`] `(mconcat . reverse) int` | | `Foldable t => t a` | `[1, 2, 3]` | `"1\|\|2\|\|3"` | [`intercalated`] `"\|\|" int` | | `Foldable t => t a` | `[1, 2, 3]` | `"1 2 3"` | [`unworded`] `int` | | `Foldable t => t a` | `[1, 2, 3]` | `"1\n2\n3"` | [`unlined`] `d` | | `Foldable t => t a` | `[1, 2, 3]` | `"1 2 3"` | [`spaced`] `int` | | `Foldable t => t a` | `[1, 2, 3]` | `"1,2,3"` | [`commaSep`] `int` | | `Foldable t => t a` | `[1, 2, 3]` | `"1st, 2nd, 3rd"` | [`commaSpaceSep`] `ords` | | `Foldable t => t a` | `["one", "two", "three"]` | `"[one, two, three]"` | [`list`] `t` | | `Foldable t => t a` | `["one", "two", "three"]` | `"[\"one\", \"two\", \"three\"]"` | [`qlist`] `t` | | `[a]` | `[1..]` | `"[1, 10, 11, 100]"` | [`took`] `4 (list bin)` | | `[a]` | `[1..6]` | `"[4, 5, 6]"` | [`dropped`] `3 (list int)` | | `a` | `"one two\tthree\nfour` | `"one, two, three, four"` | [`splat`] `isSpace commaSpaceSep stext` | | `a` | `1234567890` | `"[123, 456, 789, 0]"` | [`splatWith`] `(chunksOf 3) list int` | | `a` | `"one,two,three"` | `"one\ntwo\nthree\n"` | [`splatOn`] `"," unlined t` | | `a` | `"one two three "` | `"[one, two, three]"` | [`worded`] `list text` | | `a` | `"one\n\ntwo\nthree\n\n` | `"["one", "", "two", "three", ""]"` | [`lined`] `qlist text` | | `a` | `123456` | `"654321"` | [`alteredWith`] `TL.reverse int` | | `a` | `"Data.Char.isUpper` | `"DCU"` | [`charsKeptIf`] `isUpper string` | | `a` | `"Data.Char.isUpper` | `"ata.har.ispper"` | [`charsRemovedIf`] `isUpper string` | | `a` | `"look and boot"` | `"leek and beet"` | [`replaced`] `"oo" "ee" text` | | `a` | `"look and boot"` | `"LOOK AND BOOT"` | [`uppercased`] | | `a` | `"Look and Boot"` | `"look and boot"` | [`lowercased`] | | `a` | `"look and boot"` | `"Look And Boot"` | [`titlecased`] | | `a` | `"hellos"` | `"he..."` | [`ltruncated`] `5 text` | | `a` | `"hellos"` | `"h...s"` | [`ctruncated`] | | `a` | `"hellos"` | `"...os"` | [`rtruncated`] `5 text` | | `a` | `1` | `" 1"` | [`lpadded`] `3 int` | | `a` | `1` | `"1 "` | [`rpadded`] `3 int` | | `a` | `1` | `" 1 "` | [`cpadded`] `3 int` | | `a` | `123` | `"123 "` | [`lfixed`] `4 int` | | `a` | `123456` | `"1..."` | [`lfixed`] `4 int` | | `a` | `123` | `" 123"` | [`rfixed`] `4 int` | | `a` | `123456` | `"...6"` | [`rfixed`] `4 int` | | `a` | `123` | `" 123 "` | [`cfixed`] `2 1 ' ' int` | | `a` | `1234567` | `"12...7"` | [`cfixed`] `2 1 ' ' int` | | `a` | `"Goo"` | `"McGoo"` | [`prefixed`] `"Mc" t` | | `a` | `"Goo"` | `"Goosen"` | [`suffixed`] `"sen" t` | | `a` | `"Goo"` | `"McGooMc"` | [`surrounded`] `"Mc" t` | | `a` | `"Goo"` | `"McGoosen"` | [`enclosed`] `"Mc" "sen" t` | | `a` | `"Goo"` | `"'Goo'"` | [`squoted`] `t` | | `a` | `"Goo"` | `"\"Goo\""` | [`dquoted`] `t` | | `a` | `"Goo"` | `"(Goo)"` | [`parenthesised`] `t` | | `a` | `"Goo"` | `"[Goo]"` | [`squared`] `t` | | `a` | `"Goo"` | `"{Goo}"` | [`braced`] `t` | | `a` | `"Goo"` | `""` | [`angled`] `t` | | `a` | `"Goo"` | ``"`Goo`"`` | [`backticked`] `t` | | `a` | `"Goo"` | `" Goo"` | [`indented`] `3 t` | | `Foldable t => t a` | `[1, 2, 3]` | `" 1\n 2\n 3"` | [`indentedLines`] `2 d` | | `a` | `"1\n2\n3"` | `" 1\n 2\n 3"` | [`reindented`] `2 t` | | `Integral i, RealFrac d => d` | `6.66` | `"7"` | [`roundedTo`] `int` | | `Integral i, RealFrac d => d` | `6.66` | `"6"` | [`truncatedTo`] `int` | | `Integral i, RealFrac d => d` | `6.66` | `"7"` | [`ceilingedTo`] `int` | | `Integral i, RealFrac d => d` | `6.66` | `"6"` | [`flooredTo`] `int` | | field through a `Lens' s a` | `(1, "goo")` | `"goo"` | [`viewed`] `_2 t` | | field through a record accessor `s -> a` | `(1, "goo")` | `"1"` | [`accessed`] `fst d` | | `Integral a => a` | `4097` | `"0b0001000000000001"` | [`binPrefix`] `16` | | `Integral a => a` | `4097` | `"0o0000000000010001"` | [`octPrefix`] `16` | | `Integral a => a` | `4097` | `"0x0000000000001001"` | [`hexPrefix`] `16` | | `Ord f, Integral a, Fractional f => a` | `1024` | `"1KB"` | [`bytes`] `shortest` | | `Ord f, Integral a, Fractional f => a` | `1234567890` | `"1.15GB"` | [`bytes`] `(fixed 2)` | ## Composing formatters `%.` is like `%` but feeds one formatter into another: ``` haskell λ> format (left 2 '0' %. hex) 10 "0a" ``` ## Using more than one formatter on the same argument ``` haskell λ> now <- getCurrentTime λ> format (year % "/" <> month <> "/" % dayOfMonth) now "2015/01/27" ``` ## The Buildable Typeclass 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. 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`. Once this is defined for a type, you can use the `build` formatter (which is distinct from the `build` method of `Buildable`!): ```haskell > format ("Int: " % build % ", Text: " % build) 23 "hello" "Int: 23, Text: hello" ``` 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. Note also that if a type already has a `Show` instance then you can use this instead, by using the `shown` formatter. ## Understanding the Types Formatters generally have a type like this: ```haskell Format r (a -> r) ``` This describes a formatter that will eventually produce some string type `r`, and takes an `a` as an argument. For example: ```haskell int :: Integral a => Format r (a -> r) ``` This takes an `Integral a` argument, and eventually produces an `r`. Let's work through using this with `format`: ```haskell -- format has this type: format :: Format TL.Text a -> a -- so in 'format int', called with an 'Int', 'int's type specialises to: int :: Format TL.Text (Int -> TL.Text) -- and 'format's 'a' parameter specialises to 'Int -> TL.Text': format :: Format TL.Text (Int -> TL.Text) -> Int -> TL.Text -- so 'format int' takes an Int and produces text: format int :: Int -> TL.Text ``` 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`. Now let's look at what happens when we use the `%` operator to append formatters: ```haskell -- Here are the types of the functions we will use: (%) :: Format r a -> Format r' r -> Format r' a int :: Format r (Int -> r) -- simplified for this use stext :: Format r (T.Text -> r) -- Within the call to '%', in the expression 'int % stext', the type parameters expand like this: -- r = T.Text -> r' -- a = Int -> T.Text -> r' -- and so we have these types: int :: Format (T.Text -> r') (Int -> T.Text -> r') stext :: Format r' (T.Text -> r') int % stext :: Format r' (Int -> T.Text -> r') -- And so when we use 'format' we get a function that takes two arguments and produces text: format (int % stext) :: Int -> T.Text -> TL.Text ``` ## Comparison with Other Languages Example: ``` haskell format ("Person's name is " % text % ", age is " % hex) "Dave" 54 ``` or with short-names: ``` haskell format ("Person's name is " % t % ", age is " % x) "Dave" 54 ``` Similar to C's `printf`: ``` c printf("Person's name is %s, age is %x","Dave",54); ``` and Common Lisp's `FORMAT`: ``` lisp (format nil "Person's name is ~a, age is ~x" "Dave" 54) ``` ## Formatter Examples ### "Hello, World!": Texts ``` haskell > format (text % "!") "Hi!" "Hi!!" > format (string % "!") "Hi!" "Hi!!" ``` ### 123: Integers ``` haskell > format int 23 "23" ``` ### 23.4: Decimals ``` haskell > format (fixed 0) 23.3 "23" > format (fixed 2) 23.3333 "23.33" > format shortest 23.3333 "23.3333" > format shortest 0.0 "0.0" > format sci 2.3 "2.3" > format (scifmt Fixed (Just 0)) 2.3 "2" ``` ### 1,242: Commas ``` haskell > format commas 123456778 "123,456,778" > format commas 1234 "1,234" ``` ### 1st: Ordinals ``` haskell > format ords 1 "1st" > format ords 2 "2nd" > format ords 3 "3rd" > format ords 4 "4th" ``` ### 3F: Hex ``` haskell > format hex 15 "f" > format hex 25 "19" ``` ### Monday 1st June: Dates & times ``` haskell > now <- getCurrentTime > later <- getCurrentTime > format (dayOfMonth % "/" % month % "/" % year) now now now "16/06/2014" > format day now "167" > format hms now "08:24:41" > format tz now "+0000" > format datetime now "Mon Jun 16 08:24:41 UTC 2014" > format century now "20" > format (dayOfMonthOrd % " of " % monthName) now now "16th of June" ``` ### 3 years ago: Time spans ``` haskell > format (diff False) (diffUTCTime later now) "2 seconds" > format (diff True) (diffUTCTime later now) "in 2 seconds" > format (diff True) (diffUTCTime now later) "2 seconds ago" > format (seconds 0 % " secs") (diffUTCTime now later) "2 secs" ``` ``` haskell > let Just old = parseTime defaultTimeLocale "%Y" "1980" :: Maybe UTCTime > format (years 0) (diffUTCTime now old) "34" > format (diff True) (diffUTCTime now old) "in 35 years" > format (diff True) (diffUTCTime old now) "35 years ago" > format (days 0) (diffUTCTime old now) "12585" > format (days 0 % " days") (diffUTCTime old now) "12585 days" ``` ### File sizes ``` haskell > format (bytes shortest) 1024 "1KB" > format (bytes (fixed 2 % " ")) (1024*1024*5) "5.00 MB" ``` ### Scientific If you're using a type which provides its own builder, like the `Scientific` type: ``` haskell import Data.Text.Lazy.Builder.Scientific scientificBuilder :: Scientific -> Builder formatScientificBuilder :: FPFormat -> Maybe Int -> Scientific -> Builder ``` Then you can use `later` easily: ``` haskell > format (later scientificBuilder) 23.4 "23.4" ``` Actually, there are now already two handy combinators (`sci` and `scifmt`) for the `Scientific` type as shown above in the Decimals section. ## Writing your own Formatters You can include things verbatim in the formatter: ``` haskell > format (now "This is printed now.") "This is printed now." ``` Although with `OverloadedStrings` you can just use string literals: ``` haskell > format "This is printed now." "This is printed now." ``` You can handle things later which makes the formatter accept arguments: ``` haskell > format (later (const "This is printed later.")) () "This is printed later." ``` The type of the function passed to `later` should return an instance of `Monoid`. ``` haskell later :: (a -> Builder) -> Format r (a -> r) ``` The function you format with (`format`, `bprint`, etc.) will determine the monoid of choice. In the case of this library, the top-level formating functions expect you to build a text `Builder`: ``` haskell format :: Format Text a -> a ``` Because builders are efficient generators. So in this case we will be expected to produce Builders from arguments: ``` haskell format . later :: (a -> Builder) -> a -> Text ``` To do that for common types you can just re-use the formatting library and use bprint: ``` haskell λ> :t bprint bprint :: Format Builder a -> a > :t bprint int 23 bprint int 23 :: Builder ``` Coming back to `later`, we can now use it to build our own printer combinators: ``` haskell > let mint = later (maybe "" (bprint int)) > :t mint mint :: Integral a => Format r (Maybe a -> r) ``` Now `mint` is a formatter to show `Maybe Integer`: ``` haskell > format mint (readMaybe "23") "23" > format mint (readMaybe "foo") "" ``` Although a better, more general combinator might be: ``` haskell > let mfmt x f = later (maybe x (bprint f)) ``` Now you can use it to maybe format things: ``` haskell > format (mfmt "Nope!" int) (readMaybe "foo") "Nope!" ``` ## Using it with other APIs As a convenience, we provide the `FromBuilder` typeclass and the `formatted` combinator. `formatted` makes it simple to add formatting to any API that is expecting a `Builder`, a strict or lazy `Text`, or a `String`. For example if you have functions `logDebug`, `logWarning` and `logInfo` all of type `Text -> IO ()` you can do the following: ``` haskell > formatted logDebug ("x is: " % int) x > formatted logInfo ("y is: " % squared int) y > formatted logWarning ("z is: " % braced int) z ``` The above example will work for either strict or lazy `Text` ## Hacking ### Building with Nix See [README-nix.md](./README-nix.md). ### Running the Tests From within your `nix-shell`, run `cabal test`. The tests are in `test/Spec.hs`. ### Running the Benchmarks Start `nix-shell` like this: `nix-shell --arg doBenchmark true`. From within your `nix-shell`, run `cabal bench`. 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. 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. The benchmarks are in `bench/bench.hs`. [`format`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:format [`sformat`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:sformat [`bformat`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:bformat [`formatToString`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:formatToString [`fprint`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:fprint [`fprintLn`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:fprintLn [`hprint`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:hprint [`hprintLn`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:hprintLn [`text`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:text [`stext`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:stext [`string`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:string [`builder`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:builder [`shown`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:shown [`char`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:char [`int`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:int [`float`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:float [`fixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fixed [`sci`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:sci [`scifmt`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:scifmt [`groupInt`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:groupInt [`commas`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:commas [`ords`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:ords [`plural`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:plural [`asInt`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:asInt [`bin`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:bin [`prefixBin`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:prefixBin [`oct`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:oct [`prefixOct`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:prefixOct [`hex`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:hex [`prefixHex`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:prefixHex [`base`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:base [`left`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:left [`right`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:right [`center`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:center [`fitLeft`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fitLeft [`fitRight`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fitRight [`build`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:build [`fconst`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:fconst [`bytes`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Formatters.html#v:bytes [`t`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:t [`st`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:st [`s`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:s [`sh`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:sh [`c`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:c [`d`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:d [`sf`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:sf [`f`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:f [`b`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:b [`o`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:o [`x`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:x [`l`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:l [`r`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-ShortFormatters.html#v:r [`maybed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:maybed [`optioned`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:optioned [`eithered`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:eithered [`lefted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:lefted [`righted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:righted [`concatenated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting.html#v:concatenated [`joinedWith`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:joinedWith [`intercalated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:intercalated [`unworded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:unworded [`unlined`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:unlined [`spaced`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:spaced [`commaSep`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:commaSep [`commaSpaceSep`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:commaSpaceSep [`list`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:list [`qlist`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:qlist [`took`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:took [`dropped`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:dropped [`splat`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:splat [`splatWith`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:splatWith [`splatOn`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:splatOn [`worded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:worded [`lined`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lined [`alteredWith`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:alteredWith [`charsKeptIf`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:charsKeptIf [`charsRemovedIf`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#vata.har.isppercharsRemovedIf [`replaced`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:replaced [`uppercased`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:uppercased [`lowercased`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lowercased [`titlecased`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:titlecased [`ltruncated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:ltruncated [`ctruncated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:ctruncated [`rtruncated`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:rtruncated [`lpadded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lpadded [`rpadded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:rpadded [`cpadded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:cpadded [`lfixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:lfixed [`rfixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:rfixed [`cfixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:cfixed [`prefixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:prefixed [`suffixed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:suffixed [`surrounded`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:surrounded [`enclosed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:enclosed [`squoted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:squoted [`dquoted`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:dquoted [`parenthesised`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:parenthesised [`squared`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:squared [`braced`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:braced [`angled`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:angled [`backticked`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:backticked [`indented`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:indented [`indentedLines`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:indentedLines [`reindented`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:reindented [`roundedTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:roundedTo [`truncatedTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:truncatedTo [`ceilingedTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:ceilingedTo [`flooredTo`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:flooredTo [`viewed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:viewed [`accessed`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:accessed [`binPrefix`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:binPrefix [`octPrefix`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:octPrefix [`hexPrefix`]: https://hackage.haskell.org/package/formatting-7.1.1/docs/Formatting-Combinators.html#v:hexPrefix formatting-7.2.0/Setup.hs0000644000000000000000000000005607346545000013520 0ustar0000000000000000import Distribution.Simple main = defaultMain formatting-7.2.0/bench/0000755000000000000000000000000007346545000013142 5ustar0000000000000000formatting-7.2.0/bench/bench.hs0000644000000000000000000000543007346545000014557 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeApplications #-} import Criterion (bench, bgroup, env, nf, whnf) import Criterion.Main (defaultMain) import Test.QuickCheck import qualified Data.Text as T import qualified Data.Text.Lazy as LT import Formatting ((%)) import qualified Formatting as F import qualified Formatting.ShortFormatters as F -- From string-interpolate's benchmarks stringF :: String -> String stringF = F.formatToString ("A fine day to die, " % F.s % ".") multiStringF :: (Int, String, Bool) -> String multiStringF (x, y, z) = F.formatToString (" foo " % F.d % " bar " % F.s % " baz " % F.sh % " quux ") x y z textF :: T.Text -> T.Text textF = F.sformat ("A fine day to die, " % F.st % ".") multiTextF :: (Int, T.Text, Bool) -> T.Text multiTextF (x, y, z) = F.sformat (" foo " % F.d % " bar " % F.st % " baz " % F.sh % " quux ") x y z lazyTextF :: LT.Text -> LT.Text lazyTextF = F.format ("A find day to die, " % F.t % ".") multiLazyTextF :: (Int, LT.Text, Bool) -> LT.Text multiLazyTextF (x, y, z) = F.format (" foo " % F.d % " bar " % F.t % " baz " % F.sh % " quux ") x y z integerF :: Integer -> LT.Text integerF = F.format F.int buildF :: F.Buildable a => a -> LT.Text buildF = F.format F.build main :: IO () main = defaultMain [ bench "Small Strings" $ nf stringF "William" , bench "Small Text" $ nf textF "William" , bench "Small Lazy Text" $ nf lazyTextF "William" , bench "Multiple Interpolations String" $ nf multiStringF (42, "CATALLAXY", True) , bench "Multiple Interpolations Text" $ nf multiTextF (42, "CATALLAXY", True) , bench "Multiple Interpolations Lazy Text" $ nf multiLazyTextF (42, "CATALLAXY", True) , env largeishText $ \ ~t -> bench "Largeish Text" $ nf textF t , env largeishLazyText $ \ ~lt -> bench "Largeish Lazy Text" $ nf lazyTextF lt , bgroup "Integers" $ (\n -> bench (show n) $ whnf integerF n) <$> integersToTest , bgroup "Buildable (Integer)" $ (\n -> bench (show n) $ whnf buildF n) <$> integersToTest ] where integersToTest :: [Integer] integersToTest = [0, 1, -1, 10, -10, 99, -99, 100, 123, 12345678, maxIntInteger, -maxIntInteger, maxIntInteger * 2] maxIntInteger :: Integer maxIntInteger = fromIntegral (maxBound @Int) largeishText :: IO T.Text largeishText = generate $ T.pack . Prelude.take 100000 <$> infiniteListOf arbitrary largeishLazyText :: IO LT.Text largeishLazyText = generate $ LT.pack . Prelude.take 100000 <$> infiniteListOf arbitrary formatting-7.2.0/formatting.cabal0000644000000000000000000000710707346545000015226 0ustar0000000000000000cabal-version: 2.2 name: formatting version: 7.2.0 synopsis: Combinator-based type-safe formatting (like printf() or FORMAT) description: Combinator-based type-safe formatting (like printf() or FORMAT), modelled from the HoleyMonoids package. . See the README at for more info. homepage: https://github.com/AJChapman/formatting#readme bug-reports: https://github.com/AJChapman/formatting/issues license: BSD-3-Clause license-file: LICENSE author: Chris Done, Shachaf Ben-Kiki, Martijn van Steenbergen, Mike Meyer, Bryan O'Sullivan, Alex Chapman maintainer: alex@farfromthere.net copyright: 2020 Alex Chapman, 2013 Chris Done, Shachaf Ben-Kiki, Martijn van Steenbergen, Mike Meyer, 2011 MailRank, Inc. category: Text build-type: Simple extra-source-files: CHANGELOG.md README.md tested-with: GHC == 8.4.4 , GHC == 8.6.5 , GHC == 8.8.4 , GHC == 8.10.7 , GHC == 9.0.2 , GHC == 9.2.2 common deps build-depends: base >= 4.11 && < 5, text >= 0.11.0.8 -- Warnings list list taken from -- https://medium.com/mercury-bank/enable-all-the-warnings-a0517bc081c3 -- Enable all warnings with -Weverything, then disable the ones we -- don’t care about default-language: Haskell2010 ghc-options: -Weverything -Wno-all-missed-specialisations -Wno-implicit-prelude -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-deriving-strategies -Wno-safe -Wno-unsafe -fprint-potential-instances if impl(ghc >= 8.10) ghc-options: -Wno-prepositive-qualified-module -Wno-missing-safe-haskell-mode flag no-double-conversion description: Avoid 'double-conversion' dependency, which is large and uses C code manual: False default: False library import: deps hs-source-dirs: src build-depends: clock >= 0.4, old-locale, scientific >= 0.3.0.0, time >= 1.5, transformers, if !impl(ghcjs) && !flag(no-double-conversion) build-depends: double-conversion ^>= 2.0.2.0, exposed-modules: Formatting Formatting.Formatters Formatting.ShortFormatters Formatting.Combinators Formatting.Examples Formatting.Time Formatting.Clock Formatting.Internal Formatting.Internal.Raw Formatting.Buildable Formatting.FromBuilder other-modules: Data.Text.Format.Functions Data.Text.Format.Types Data.Text.Format test-suite formatting-test import: deps build-depends: formatting, hspec, scientific, time type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs ghc-options: -threaded -rtsopts -with-rtsopts=-N benchmark bench import: deps type: exitcode-stdio-1.0 hs-source-dirs: bench main-is: bench.hs build-depends: formatting, criterion, QuickCheck ghc-options: -O2 source-repository head type: git location: http://github.com/AJChapman/formatting formatting-7.2.0/src/Data/Text/0000755000000000000000000000000007346545000014447 5ustar0000000000000000formatting-7.2.0/src/Data/Text/Format.hs0000644000000000000000000000512307346545000016234 0ustar0000000000000000{-# LANGUAGE RelaxedPolyRec #-} {-# LANGUAGE CPP #-} -- | -- Module : Data.Text.Format -- Copyright : (c) 2011 MailRank, Inc. -- -- License : BSD-style -- Maintainer : bos@serpentine.com -- Stability : experimental -- Portability : GHC -- -- Fast, efficient, flexible support for formatting text strings. module Data.Text.Format ( -- * Format control left , right -- ** Integers , hex -- ** Floating point numbers , fixed , shortest ) where #ifdef MIN_VERSION_double_conversion import Data.Double.Conversion.Text (toFixed, toShortest) #else import Numeric (showFFloat, showInt) #endif import qualified Formatting.Buildable as B import Data.Text.Format.Types (Hex(..)) import qualified Data.Text.Lazy as LT import Data.Text.Lazy.Builder import Prelude hiding (exp, print) -- | Pad the left hand side of a string until it reaches @k@ -- characters wide, if necessary filling with character @c@. left :: B.Buildable a => Int -> Char -> a -> Builder left k c = fromLazyText . LT.justifyRight (fromIntegral k) c . toLazyText . B.build -- | Pad the right hand side of a string until it reaches @k@ -- characters wide, if necessary filling with character @c@. right :: B.Buildable a => Int -> Char -> a -> Builder right k c = fromLazyText . LT.justifyLeft (fromIntegral k) c . toLazyText . B.build -- | Render a floating point number using normal notation, with the -- given number of decimal places. fixed :: (Real a) => Int -- ^ Number of digits of precision after the decimal. -> a -> Builder #ifdef MIN_VERSION_double_conversion fixed decs = fromText . toFixed decs . realToFrac #else fixed decs = fromString . toFixed . realToFrac where toFixed :: Double -> String toFixed dbl = showFFloat (Just decs) dbl "" #endif {-# NOINLINE[0] fixed #-} -- | Render a floating point number using the smallest number of -- digits that correctly represent it. shortest :: Real a => a -> Builder #ifdef MIN_VERSION_double_conversion shortest = fromText . toShortest . realToFrac #else shortest = fromString . toShortest . realToFrac where toShortest :: Double -> String toShortest dbl = -- `showFFloat (Just 0) "" 1.0` gives "1.", but we want "1" let intPart = (floor dbl :: Int) in if dbl == (fromIntegral intPart) then showInt intPart "" else showFFloat Nothing dbl "" #endif {-# INLINE shortest #-} -- | Render an integer using hexadecimal notation. (No leading "0x" -- is added.) hex :: Integral a => a -> Builder hex = B.build . Hex {-# INLINE hex #-} formatting-7.2.0/src/Data/Text/Format/0000755000000000000000000000000007346545000015677 5ustar0000000000000000formatting-7.2.0/src/Data/Text/Format/Functions.hs0000644000000000000000000000133607346545000020206 0ustar0000000000000000{-# LANGUAGE MagicHash #-} -- | -- Module : Data.Text.Format.Functions -- Copyright : (c) 2011 MailRank, Inc. -- -- License : BSD-style -- Maintainer : bos@serpentine.com -- Stability : experimental -- Portability : GHC -- -- Useful functions and combinators. module Data.Text.Format.Functions ( (<>) , i2d ) where import Data.Text.Lazy.Builder (Builder) import Prelude hiding ((<>)) import GHC.Base hiding ((<>)) -- | Unsafe conversion for decimal digits. {-# INLINE i2d #-} i2d :: Int -> Char i2d (I# i#) = C# (chr# (ord# '0'# +# i#)) -- | The normal 'mappend' function with right associativity instead of -- left. (<>) :: Builder -> Builder -> Builder (<>) = mappend {-# INLINE (<>) #-} infixr 4 <> formatting-7.2.0/src/Data/Text/Format/Types.hs0000644000000000000000000000135207346545000017340 0ustar0000000000000000{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module : Data.Text.Format.Types.Internal -- Copyright : (c) 2011 MailRank, Inc. -- -- License : BSD-style -- Maintainer : bos@serpentine.com -- Stability : experimental -- Portability : GHC -- -- Types for text mangling. module Data.Text.Format.Types ( Shown(..) -- * Integer format control , Hex(..) ) where -- | Render an integral type in hexadecimal. newtype Hex a = Hex a deriving (Eq, Ord, Read, Show, Num, Real, Enum, Integral) -- | Render a value using its 'Show' instance. newtype Shown a = Shown { shown :: a } deriving (Eq, Show, Read, Ord, Num, Fractional, Real, RealFrac, Floating, RealFloat, Enum, Integral, Bounded) formatting-7.2.0/src/0000755000000000000000000000000007346545000012652 5ustar0000000000000000formatting-7.2.0/src/Formatting.hs0000644000000000000000000000216707346545000015326 0ustar0000000000000000{-# LANGUAGE RankNTypes #-} {-# OPTIONS -Wall #-} -- | -- Module : Text.Format -- Copyright : (c) 2013 Chris Done, 2013 Shachaf Ben-Kiki -- License : BSD3 -- Maintainer : alex@farfromthere.net -- Stability : experimental -- Portability : GHC -- -- Combinator-based type-safe formatting (like printf() or FORMAT) for Text. -- -- Example: -- -- >>> format ("Person's name is " % text % ", age is " % hex) "Dave" 54 -- "Person's name is Dave, age is 36" -- -- See "Formatting.Formatters" for a list of formatters. -- See "Formatting.Combinators" for a list of formatting combinators, for combining and altering formatters. module Formatting ( Format, (%), (%+), (%.), (<%+>), now, later, mapf, -- * Top-level functions runFormat, format, sformat, bprint, bformat, fprint, fprintLn, hprint, hprintLn, formatToString, formatted, -- * Formatting library module Formatting.Formatters, module Formatting.Combinators ) where import Formatting.Formatters import Formatting.Combinators import Formatting.FromBuilder import Formatting.Internal -- $setup -- >>> :set -XOverloadedStrings formatting-7.2.0/src/Formatting/0000755000000000000000000000000007346545000014764 5ustar0000000000000000formatting-7.2.0/src/Formatting/Buildable.hs0000644000000000000000000001130207346545000017200 0ustar0000000000000000{-# LANGUAGE CPP, FlexibleInstances, OverloadedStrings #-} -- | -- Module : Data.Text.Buildable -- Copyright : (c) 2011 MailRank, Inc. -- -- License : BSD-style -- Maintainer : bos@serpentine.com -- Stability : experimental -- Portability : GHC -- -- Types that can be rendered to a 'Builder'. module Formatting.Buildable ( Buildable(..) ) where import Data.Int (Int8, Int16, Int32, Int64) import Data.Fixed (Fixed, HasResolution, showFixed) import Data.List (intersperse) import Data.Ratio (Ratio, denominator, numerator) import qualified Data.Text.Format.Functions as F ((<>)) import Data.Text.Lazy.Builder.Int (decimal, hexadecimal) import Data.Text.Format.Types (Hex(..), Shown(..)) import Data.Text.Lazy.Builder import Data.Time.Calendar (Day, showGregorian) import Data.Time.Clock (getModJulianDate, DiffTime, NominalDiffTime, UTCTime, UniversalTime) import Data.Time.LocalTime (LocalTime, TimeOfDay, TimeZone, ZonedTime) import Data.Void (Void, absurd) import Data.Word (Word8, Word16, Word32, Word64) import Foreign.Ptr (IntPtr, WordPtr, Ptr, ptrToWordPtr) import qualified Data.Text as ST import qualified Data.Text.Lazy as LT -- | The class of types that can be rendered to a 'Builder'. class Buildable p where build :: p -> Builder instance Buildable Builder where build = id instance Buildable Void where build = absurd instance Buildable LT.Text where build = fromLazyText {-# INLINE build #-} instance Buildable ST.Text where build = fromText {-# INLINE build #-} instance Buildable Char where build = singleton {-# INLINE build #-} instance Buildable [Char] where build = fromString {-# INLINE build #-} instance (Integral a) => Buildable (Hex a) where build = hexadecimal {-# INLINE build #-} instance Buildable Int8 where build = decimal {-# INLINE build #-} instance Buildable Int16 where build = decimal {-# INLINE build #-} instance Buildable Int32 where build = decimal {-# INLINE build #-} instance Buildable Int where build = decimal {-# INLINE build #-} instance Buildable Int64 where build = decimal {-# INLINE build #-} instance Buildable Integer where build = decimal {-# INLINE build #-} instance (HasResolution a) => Buildable (Fixed a) where build = build . showFixed False {-# INLINE build #-} instance Buildable Word8 where build = decimal {-# INLINE build #-} instance Buildable Word16 where build = decimal {-# INLINE build #-} instance Buildable Word32 where build = decimal {-# INLINE build #-} instance Buildable Word where build = decimal {-# INLINE build #-} instance Buildable Word64 where build = decimal {-# INLINE build #-} instance Buildable a => Buildable (Ratio a) where {-# SPECIALIZE instance Buildable (Ratio Integer) #-} build a = build (numerator a) F.<> singleton '/' F.<> build (denominator a) instance Buildable Float where build = build . show {-# INLINE build #-} instance Buildable Double where build = build . show {-# INLINE build #-} instance Buildable DiffTime where build = build . Shown {-# INLINE build #-} instance Buildable NominalDiffTime where build = build . Shown {-# INLINE build #-} instance Buildable UTCTime where build = build . Shown {-# INLINE build #-} instance Buildable UniversalTime where build = build . Shown . getModJulianDate {-# INLINE build #-} instance Buildable Day where build = fromString . showGregorian {-# INLINE build #-} instance (Show a) => Buildable (Shown a) where build = fromString . show . shown {-# INLINE build #-} instance (Buildable a) => Buildable (Maybe a) where build Nothing = mempty build (Just v) = build v {-# INLINE build #-} instance Buildable TimeOfDay where build = build . Shown {-# INLINE build #-} instance Buildable TimeZone where build = build . Shown {-# INLINE build #-} instance Buildable LocalTime where build = build . Shown {-# INLINE build #-} instance Buildable ZonedTime where build = build . Shown {-# INLINE build #-} instance Buildable IntPtr where build p = fromText "0x" F.<> hexadecimal p instance Buildable WordPtr where build p = fromText "0x" F.<> hexadecimal p instance Buildable (Ptr a) where build = build . ptrToWordPtr instance Buildable Bool where build True = fromText "True" build False = fromText "False" instance {-# OVERLAPPABLE #-} Buildable a => Buildable [a] where build xs = "[" F.<> mconcat (intersperse "," (map build xs)) F.<> "]" {-# INLINE build #-} formatting-7.2.0/src/Formatting/Clock.hs0000644000000000000000000000251007346545000016351 0ustar0000000000000000{-# LANGUAGE PatternGuards #-} {-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} -- | Formatters for high-res, real-time and timer clock values from "System.Clock". module Formatting.Clock (timeSpecs) where import Data.Text.Lazy.Builder import Formatting import Formatting.Internal import System.Clock fmt :: Integer -> Builder fmt diff | Just i <- scale ((10 ^ 9) * 60 * 60 * 24) = bprint (fixed 2 % " d") i | Just i <- scale ((10 ^ 9) * 60 * 60) = bprint (fixed 2 % " h") i | Just i <- scale ((10 ^ 9) * 60) = bprint (fixed 2 % " m") i | Just i <- scale (10 ^ 9) = bprint (fixed 2 % " s") i | Just i <- scale (10 ^ 6) = bprint (fixed 2 % " ms") i | Just i <- scale (10 ^ 3) = bprint (fixed 2 % " us") i | otherwise = bprint (int % " ns") diff where scale :: Integer -> Maybe Double scale i = if diff >= i then Just (fromIntegral diff / fromIntegral i) else Nothing -- | Same as @durationNS@ but works on `TimeSpec` from the clock package. timeSpecs :: Format r (TimeSpec -> TimeSpec -> r) timeSpecs = Format (\g x y -> g (fmt0 x y)) where fmt0 (TimeSpec s1 n1) (TimeSpec s2 n2) = fmt diff where diff :: Integer diff = a2 - a1 a1 = (fromIntegral s1 * 10 ^ 9) + fromIntegral n1 a2 = (fromIntegral s2 * 10 ^ 9) + fromIntegral n2 formatting-7.2.0/src/Formatting/Combinators.hs0000644000000000000000000006367007346545000017614 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-} {-| Module : Formatting.Combinators Copyright : (c) 2020 Alex Chapman License : BSD3 Maintainer : alex@farfromthere.net Stability : experimental Portability : GHC Description : Formatting combinators for building new formatters, with some useful pre-defined formatters. A formatting combinator takes a Format and returns another Format. Generally we want to change what the original format takes as its *input*, leaving the output polymorphic. Many of these combinators can be chained together to form a single 'Format'. 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'. This does not tie you to using 'Builder's, because the final output string type 'r' is still polymorphic. -} module Formatting.Combinators ( -- * Formatting common containers maybed , optioned , eithered , lefted , righted -- * Formatting lists of data , concatenated , joinedWith , intercalated , unworded , unlined , spaced , commaSep , commaSpaceSep , list , qlist , took , dropped -- * Splitting strings to pass to other formatters , splat , splatWith , splatOn , worded , lined -- * Altering formatted strings , alteredWith , charsKeptIf , charsRemovedIf , replaced , uppercased , lowercased , titlecased , ltruncated , ctruncated , rtruncated , lpadded , rpadded , cpadded , lfixed , rfixed , cfixed -- * Wrapping formatted strings , prefixed , suffixed , surrounded , enclosed , squoted , dquoted , parenthesised , squared , braced , angled , backticked -- * Changing indentation , indented , indentedLines , reindented -- * Numerical adapters , roundedTo , truncatedTo , ceilingedTo , flooredTo -- * Structure formatting , viewed , accessed -- * Fixed-width number formatting , binPrefix , octPrefix , hexPrefix ) where import Control.Applicative (Const(..), getConst) import Control.Category ((>>>)) import Data.Foldable (toList) import Data.Function ((&)) import Data.Int (Int64) import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL import Data.Text.Lazy.Builder (Builder) import qualified Data.Text.Lazy.Builder as TLB import Formatting.Internal import Formatting.Formatters -- $setup -- >>> import Formatting.Internal -- >>> import Formatting.Formatters -- >>> import qualified Data.Text.Lazy as TL -- >>> _1 g (a, x) = fmap (\b -> (b, x)) $ g a -- -- We define a simplistic implementation of the lens _1, Not polymorphic in -- tuple length, but it works for our example without requiring the lens -- package. -- | Render a Maybe value either as a default (if Nothing) or using the given formatter: -- -- >>> format (maybed "Goodbye" text) Nothing -- "Goodbye" -- -- >>> format (maybed "Goodbye" text) (Just "Hello") -- "Hello" maybed :: Builder -- ^ The value to use when the input is Nothing -> Format Builder (a -> Builder) -- ^ The formatter to use on the value in a Just -> Format r (Maybe a -> r) maybed whenNothing f = later $ \case Nothing -> whenNothing Just x -> bformat f x {-# INLINE maybed #-} -- | Render the value in a Maybe using the given formatter, or produce an empty string: -- -- >>> format (optioned text) Nothing -- "" -- -- >>> format (optioned text) (Just "Hello") -- "Hello" optioned :: Format Builder (a -> Builder) -> Format r (Maybe a -> r) optioned = maybed "" {-# INLINE optioned #-} -- | Render the value in an Either: -- -- >>> format (eithered text int) (Left "Error!") -- "Error!" -- -- >>> format (eithered text int) (Right 69) -- "69" eithered :: Format Builder (a -> Builder) -- ^ The formatter to use on a value in a Left -> Format Builder (b -> Builder) -- ^ The formatter to use on a value in a Right -> Format r (Either a b -> r) eithered l r = later $ \case Left x -> bformat l x Right x -> bformat r x {-# INLINE eithered #-} -- | Render the value in a Left with the given formatter, rendering a Right as an empty string: -- -- >>> format (lefted text) (Left "bingo") -- "bingo" -- -- >>> format (lefted text) (Right 16) -- "" lefted :: Format Builder (a -> Builder) -> Format r (Either a x -> r) lefted f = eithered f (fconst "") {-# INLINE lefted #-} -- | Render the value in a Right with the given formatter, rendering a Left as an empty string: -- -- >>> format (righted text) (Left 16) -- "" -- -- >>> format (righted text) (Right "bingo") -- "bingo" righted :: Format Builder (a -> Builder) -> Format r (Either x a -> r) righted = eithered (fconst "") {-# INLINE righted #-} -- | Format each value in a list and concatenate them all: -- -- >>> format (concatenated text) ["one", "two", "three"] -- "onetwothree" -- -- >>> format (took 15 (concatenated bin)) [1..] -- "1101110010111011110001001101010111100110111101111" concatenated :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) concatenated f = later $ foldMap (bformat f) {-# INLINE concatenated #-} -- | Use the given text-joining function to join together the individually rendered items of a list. -- -- >>> format (joinedWith (mconcat . reverse) int) [123, 456, 789] -- "789456123" joinedWith :: Foldable t => ([Text] -> Text) -> Format Builder (a -> Builder) -> Format r (t a -> r) joinedWith joiner f = later $ toList >>> fmap (bformat f >>> TLB.toLazyText) >>> joiner >>> TLB.fromLazyText {-# INLINABLE joinedWith #-} -- | Format each value in a list and place the given string between each: -- -- >>> fprintLn (intercalated "||" int) [1, 2, 3] -- 1||2||3 intercalated :: Foldable t => Text -> Format Builder (a -> Builder) -> Format r (t a -> r) intercalated s = joinedWith (TL.intercalate s) {-# INLINE intercalated #-} -- | Format each value in a list with spaces in between: -- -- >>> format (unworded int) [1, 2, 3] -- "1 2 3" unworded :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) unworded = joinedWith TL.unwords {-# INLINE unworded #-} -- | Format each value in a list, placing each on its own line: -- -- >>> fprint (unlined char) ['a'..'c'] -- a -- b -- c unlined :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) unlined = joinedWith TL.unlines {-# INLINE unlined #-} -- | Separate the formatted items of the Foldable (e.g. list) with spaces: -- -- >>> format (spaced int) [1, 2, 3] -- "1 2 3" -- -- Note that this behaviour is identical to 'unworded', it's just a different way of thinking about it. spaced :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) spaced = intercalated " " {-# INLINE spaced #-} -- | Separate the formatted items of the Foldable (e.g. list) with commas: -- -- >>> format (commaSep stext) ["one", "two", "three", "four", "five"] -- "one,two,three,four,five" -- -- >>> format (took 5 (commaSep int)) [1..] -- "1,2,3,4,5" commaSep :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) commaSep = intercalated "," {-# INLINE commaSep #-} -- | Separate the formatted items of the Foldable (e.g. list) with commas and spaces: -- -- >>> format (took 3 (commaSpaceSep ords)) [1..] -- "1st, 2nd, 3rd" commaSpaceSep :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) commaSpaceSep = intercalated ", " {-# INLINE commaSpaceSep #-} -- | Add square brackets around the Foldable (e.g. a list), and separate each formatted item with a comma and space. -- -- >>> format (list stext) ["one", "two", "three"] -- "[one, two, three]" -- -- >>> format (list shown) ["one", "two", "three"] -- "[\"one\", \"two\", \"three\"]" list :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) list = commaSpaceSep >>> squared {-# INLINE list #-} -- | Like 'list', but also put double quotes around each rendered item: -- -- >>> fprintLn (qlist stext) ["one", "two", "three"] -- ["one", "two", "three"] qlist :: Foldable t => Format Builder (a -> Builder) -> Format r (t a -> r) qlist = dquoted >>> commaSpaceSep >>> squared {-# INLINE qlist #-} -- | Take only the first n items from the list of items. -- -- >>> format (took 7 (list bin)) [1..] -- "[1, 10, 11, 100, 101, 110, 111]" -- -- >>> format (list bin) (take 7 [1..]) -- "[1, 10, 11, 100, 101, 110, 111]" took :: Int -> Format r ([a] -> r) -> Format r ([a] -> r) took n = fmap (. take n) {-# INLINE took #-} -- | Drop the first n items from the list of items. -- -- >>> format (dropped 3 (list int)) [1..6] -- "[4, 5, 6]" dropped :: Int -> Format r ([a] -> r) -> Format r ([a] -> r) dropped n = fmap (. drop n) {-# INLINE dropped #-} -- | Utility for taking a text-splitting function and turning it into a formatting combinator. -- -- >>> format (splatWith (TL.chunksOf 3) list int) 1234567890 -- "[123, 456, 789, 0]" splatWith :: (Text -> [Text]) -- ^ The text splitter -> (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. -> Format r a -- ^ The base formatter, whose rendered text will be split -> Format r a splatWith splitter lf f = later (TLB.toLazyText >>> splitter >>> fmap TLB.fromLazyText >>> bformat (lf builder)) %. f {-# INLINABLE splatWith #-} -- | Split the formatted item in places the given predicated matches, and use the given list combinator to render the resultant list of strings -- (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"). -- -- >>> format (splat Data.Char.isSpace commaSpaceSep stext) "This\t is\n\t\t poorly formatted " -- "This, , , is, , , , , poorly, formatted, , , " splat :: (Char -> Bool) -- ^ Whether to split the string at this character -> (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. -> Format r a -- ^ The base formatter, whose rendered text will be split -> Format r a splat p = splatWith (TL.split p) {-# INLINE splat #-} -- | Split the formatted item at instances of the given string, and use the given list combinator to render the resultant list of strings. -- -- >>> fprint (splatOn "," unlined text) "one,two,three" -- one -- two -- three -- -- >>> fprint (splatOn "," (indentedLines 4) text) "one,two,three" -- one -- two -- three splatOn :: Text -- ^ The text to split on -> (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. -> Format r a -- ^ The base formatter, whose rendered text will be split -> Format r a splatOn t = splatWith (TL.splitOn t) {-# INLINE splatOn #-} -- | Split the formatted item into words and use the given list combinator to render the resultant list of strings. -- -- >>> format (worded list text) "one two three " -- "[one, two, three]" worded :: (Format r' (Builder -> r') -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. -> Format r a -- ^ The base formatter, whose rendered text will be split -> Format r a worded = splatWith TL.words {-# INLINE worded #-} -- | Split the formatted item into lines and use the given list combinator to render the resultant list of strings. -- -- >>> fprintLn (lined qlist text) "one two three\n\nfour five six\nseven eight nine\n\n" -- ["one two three", "", "four five six", "seven eight nine", ""] lined :: (Format Builder (Builder -> Builder) -> Format Builder ([Builder] -> Builder)) -- ^ A list-formatting combinator, e.g. 'unworded', 'list', 'concatenated', etc. -> Format r a -- ^ The base formatter, whose rendered text will be split -> Format r a lined = splatWith TL.lines {-# INLINE lined #-} -- | Alter the formatted string with the given function. -- -- >>> format (alteredWith Data.Text.Lazy.reverse int) 123456 -- "654321" alteredWith :: (Text -> Text) -> Format r a -> Format r a alteredWith alterer f = later (TLB.toLazyText >>> alterer >>> TLB.fromLazyText) %. f {-# INLINABLE alteredWith #-} -- | Filter the formatted string to contain only characters which pass the given predicate: -- -- >>> format (charsKeptIf Data.Char.isUpper text) "Data.Char.isUpper" -- "DCU" charsKeptIf :: (Char -> Bool) -> Format r a -> Format r a charsKeptIf p = alteredWith (TL.filter p) {-# INLINE charsKeptIf #-} -- | Filter the formatted string to not contain characters which pass the given predicate: -- -- >>> format (charsRemovedIf Data.Char.isUpper text) "Data.Char.isUpper" -- "ata.har.ispper" charsRemovedIf :: (Char -> Bool) -> Format r a -> Format r a charsRemovedIf p = alteredWith (TL.filter (not . p)) {-# INLINE charsRemovedIf #-} -- | Take a formatter and replace the given needle with the given replacement in its output. -- -- >>> format (replaced "Bruce" "" stext) "Bruce replied that Bruce's name was, in fact, ''." -- " replied that 's name was, in fact, ''." replaced :: Text -> Text -> Format r a -> Format r a replaced needle replacement = alteredWith (TL.replace needle replacement) {-# INLINE replaced #-} -- | Convert any letters in the output of the given formatter to upper-case. -- -- >>> format (uppercased text) "I'm not shouting, you're shouting." -- "I'M NOT SHOUTING, YOU'RE SHOUTING." uppercased :: Format r a -> Format r a uppercased = alteredWith TL.toUpper {-# INLINE uppercased #-} -- | Convert any letters in the output of the given formatter to lower-case. -- -- >>> format (lowercased text) "Cd SrC/; Rm -Rf *" -- "cd src/; rm -rf *" lowercased :: Format r a -> Format r a lowercased = alteredWith TL.toLower {-# INLINE lowercased #-} -- | Convert the formatted string to title case, or something like it: -- -- >>> format (titlecased string) "the life of brian" -- "The Life Of Brian" titlecased :: Format r a -> Format r a titlecased = alteredWith TL.toTitle {-# INLINE titlecased #-} -- | 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. -- -- >>> format (ltruncated 5 text) "hello" -- "hello" -- -- >>> format (ltruncated 5 text) "hellos" -- "he..." ltruncated :: Int64 -> Format r a -> Format r a ltruncated n = ctruncated (n - 3) 0 {-# INLINE ltruncated #-} -- | 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. -- -- >>> format (rtruncated 5 text) "hello" -- "hello" -- -- >>> format (rtruncated 5 text) "hellos" -- "...os" rtruncated :: Int64 -> Format r a -> Format r a rtruncated n = ctruncated 0 (n - 3) {-# INLINE rtruncated #-} -- | Truncate the formatted string in the center, leaving the given number of characters at the start and end, and placing an ellipsis in between. -- The length will be no longer than `start + end + 3` characters long. -- -- >>> format (ctruncated 15 4 text) "The quick brown fox jumps over the lazy dog." -- "The quick brown...dog." -- -- >>> format (ctruncated 15 4 text) "The quick brown fox" -- "The quick brown fox" ctruncated :: Int64 -> Int64 -> Format r a -> Format r a ctruncated start end = alteredWith shorten where shorten :: Text -> Text shorten txt = let n = start + end + 3 in if TL.length txt <= n then txt else TL.take start txt <> "..." <> TL.takeEnd end txt {-# INLINABLE ctruncated #-} -- | Pad the formatted string on the left with the given character to give it the given minimum width: -- -- >>> format (lpadded 7 ' ' int) 1 -- " 1" -- -- >>> format (lpadded 7 ' ' int) 123456789 -- "123456789" lpadded :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) lpadded i c = alteredWith (TL.justifyRight i c) {-# INLINE lpadded #-} -- | Pad the formatted string on the right with the given character to give it the given minimum width: -- -- >>> format (rpadded 7 ' ' int) 1 -- "1 " rpadded :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) rpadded i c = alteredWith (TL.justifyLeft i c) {-# INLINE rpadded #-} -- | Pad the formatted string on the left and right with the given character to center it, giving it the given minimum width: -- -- >>> format (cpadded 7 ' ' int) 1 -- " 1 " cpadded :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) cpadded i c = alteredWith (TL.center i c) {-# INLINE cpadded #-} -- | 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: -- -- >>> format (lfixed 10 ' ' int) 123 -- "123 " -- -- >>> format (lfixed 10 ' ' int) 1234567890 -- "1234567890" -- -- >>> format (lfixed 10 ' ' int) 123456789012345 -- "1234567..." lfixed :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) lfixed n c = ltruncated n . rpadded n c {-# INLINE lfixed #-} -- | 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: -- -- >>> format (rfixed 10 ' ' int) 123 -- " 123" -- -- >>> format (rfixed 10 ' ' int) 1234567890 -- "1234567890" -- -- >>> format (rfixed 10 ' ' int) 123456789012345 -- "...9012345" rfixed :: Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) rfixed n c = rtruncated n . lpadded n c {-# INLINE rfixed #-} -- | 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. -- -- The total length will be `l + r + 3` characters. -- -- >>> format (cfixed 4 3 ' ' int) 123 -- " 123 " -- -- >>> format (cfixed 4 3 ' ' int) 1234567890 -- "1234567890" -- -- >>> format (cfixed 4 3 ' ' int) 123456789012345 -- "1234...345" cfixed :: Int64 -> Int64 -> Char -> Format r (a -> r) -> Format r (a -> r) cfixed l r c = ctruncated l r . cpadded (l + r + 3) c {-# INLINE cfixed #-} -- | Add the given prefix to the formatted item: -- -- >>> format ("The answer is: " % prefixed "wait for it... " int) 42 -- "The answer is: wait for it... 42" -- -- >>> fprint (unlined (indented 4 (prefixed "- " int))) [1, 2, 3] -- - 1 -- - 2 -- - 3 prefixed :: Builder -> Format r a -> Format r a prefixed s f = now s % f {-# INLINE prefixed #-} -- | Add the given suffix to the formatted item. suffixed :: Builder -> Format r a -> Format r a suffixed s f = f % now s {-# INLINE suffixed #-} -- | Surround the output string with the given string: -- -- >>> format (surrounded "***" string) "glue" -- "***glue***" surrounded :: Builder -> Format r a -> Format r a surrounded s f = now s % f % now s {-# INLINE surrounded #-} -- | Enclose the output string with the given strings: -- -- >>> format (enclosed "" text) "an html comment" -- "" enclosed :: Builder -> Builder -> Format r a -> Format r a enclosed pre suf f = now pre % f % now suf {-# INLINE enclosed #-} -- | Add single quotes around the formatted item: -- -- >>> let obj = Just Nothing in format ("The object is: " % squoted shown % ".") obj -- "The object is: 'Just Nothing'." squoted :: Format r a -> Format r a squoted = surrounded "'" {-# INLINE squoted #-} -- | Add double quotes around the formatted item: -- -- >>> fprintLn ("He said it was based on " % dquoted stext % ".") "science" -- He said it was based on "science". dquoted :: Format r a -> Format r a dquoted = surrounded "\"" {-# INLINE dquoted #-} -- | Add parentheses around the formatted item: -- -- >>> format ("We found " % parenthesised int % " discrepancies.") 17 -- "We found (17) discrepancies." -- -- >>> fprintLn (took 5 (list (parenthesised int))) [1..] -- [(1), (2), (3), (4), (5)] parenthesised :: Format r a -> Format r a parenthesised = enclosed "(" ")" {-# INLINE parenthesised #-} -- | Add square brackets around the formatted item: -- -- >>> format (squared int) 7 -- "[7]" squared :: Format r a -> Format r a squared = enclosed "[" "]" {-# INLINE squared #-} -- | Add curly brackets around the formatted item: -- -- >>> format ("\\begin" % braced text) "section" -- "\\begin{section}" braced :: Format r a -> Format r a braced = enclosed "{" "}" {-# INLINE braced #-} -- | Add angle brackets around the formatted item: -- -- >>> format (angled int) 7 -- "<7>" -- -- >>> format (list (angled text)) ["html", "head", "title", "body", "div", "span"] -- "[, , , <body>, <div>, <span>]" angled :: Format r a -> Format r a angled = enclosed "<" ">" {-# INLINE angled #-} -- | Add backticks around the formatted item: -- -- >>> format ("Be sure to run " % backticked builder % " as root.") ":(){:|:&};:" -- "Be sure to run `:(){:|:&};:` as root." backticked :: Format r a -> Format r a backticked = surrounded "`" {-# INLINE backticked #-} -- | Insert the given number of spaces at the start of the rendered text: -- -- >>> format (indented 4 int) 7 -- " 7" -- -- Note that this only indents the first line of a multi-line string. -- To indent all lines see 'reindented'. indented :: Int -> Format r a -> Format r a indented n = prefixed spaces where spaces = TL.replicate (fromIntegral n) (TL.singleton ' ') & TLB.fromLazyText {-# INLINABLE indented #-} -- | Format a list of items, placing one per line, indented by the given number of spaces. -- -- >>> fprint ("The lucky numbers are:\n" % indentedLines 4 int) [7, 13, 1, 42] -- The lucky numbers are: -- 7 -- 13 -- 1 -- 42 indentedLines :: Foldable t => Int -> Format Builder (a -> Builder) -> Format r (t a -> r) indentedLines n = unlined . indented n {-# INLINE indentedLines #-} -- | Indent each line of the formatted string by the given number of spaces: -- -- >>> fprint (reindented 2 text) "one\ntwo\nthree" -- one -- two -- three reindented :: Int -> Format r a -> Format r a reindented n = lined (indentedLines n) {-# INLINE reindented #-} -- | Take a fractional number and round it before formatting it as the given Format: -- -- >>> format (roundedTo int) 6.66 -- "7" -- >>> format (list (roundedTo int)) [10.66, 6.66, 1.0, 3.4] -- "[11, 7, 1, 3]" -- -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: -- -- @ -- roundedTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) -- @ roundedTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) roundedTo = fmap (. round) {-# INLINE roundedTo #-} -- | Take a fractional number and truncate it before formatting it as the given Format: -- -- >>> format (truncatedTo int) 6.66 -- "6" -- >>> format (list (truncatedTo int)) [10.66, 6.66, 1.0, 3.4] -- "[10, 6, 1, 3]" -- -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: -- -- @ -- truncatedTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) -- @ truncatedTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) truncatedTo = fmap (. truncate) {-# INLINE truncatedTo #-} -- | Take a fractional number and ceiling it before formatting it as the given Format: -- -- >>> format (ceilingedTo int) 6.66 -- "7" -- >>> format (list (ceilingedTo int)) [10.66, 6.66, 1.0, 3.4] -- "[11, 7, 1, 4]" -- -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: -- -- @ -- ceilingedTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) -- @ ceilingedTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) ceilingedTo = fmap (. ceiling) {-# INLINE ceilingedTo #-} -- | Take a fractional number and floor it before formatting it as the given Format: -- -- >>> format (flooredTo int) 6.66 -- "6" -- >>> format (list (flooredTo int)) [10.66, 6.66, 1.0, 3.4] -- "[10, 6, 1, 3]" -- -- Note: the type variable 'f' will almost always be 'Format r', so the type of this function can be thought of as: -- -- @ -- flooredTo :: (Integral i, RealFrac d) => Format r (i -> r) -> Format r (d -> r) -- @ flooredTo :: (Integral i, RealFrac d, Functor f) => f (i -> r) -> f (d -> r) flooredTo = fmap (. floor) {-# INLINE flooredTo #-} -- | Use the given lens to view an item, formatting it with the given formatter. -- -- You can think of this as having the type: -- -- @ -- 'viewed' :: 'Lens'' s a -> Format r (a -> r) -> Format r (s -> r) -- @ -- -- >>> format (viewed _1 int) (1, "hello") -- "1" -- -- 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: -- -- @ -- data Person = Person -- { _personName :: Text -- , _personAge :: Int -- } -- makeLenses ''Person -- -- me :: Person -- me = Person "Alex" 38 -- -- format ("The person's name is " % squoted (viewed personName text) % ", and their age is " <> viewed personAge int) me -- "The person's name is 'Alex', and their age is 38" -- @ viewed :: ((a -> Const a b) -> s -> Const a t) -> Format r (a -> r) -> Format r (s -> r) viewed l = fmap (. (getConst . l Const)) {-# INLINE viewed #-} -- | Access an element of the structure and format it with the given formatter. -- -- >>> format (accessed fst int) (1, "hello") -- "1" -- -- Repeating the example from 'viewed': -- -- format ("The person's name is " % squoted (accessed _personName text) % ", and their age is " <> accessed _personAge int) me -- "The person's name is 'Alex', and their age is 38" accessed :: (s -> a) -> Format r (a -> r) -> Format r (s -> r) accessed accessor = fmap (. accessor) {-# INLINE accessed #-} -- | Render an integer using binary notation with a leading 0b, padding with zeroes to the given width: -- -- >>> format (binPrefix 16) 4097 -- "0b0001000000000001" binPrefix :: Integral a => Int64 -> Format r (a -> r) binPrefix n = "0b" % lpadded n '0' bin {-# INLINE binPrefix #-} -- | Render an integer using octal notation with a leading 0o, padding with zeroes to the given width: -- -- >>> format (octPrefix 16) 4097 -- "0o0000000000010001" octPrefix :: Integral a => Int64 -> Format r (a -> r) octPrefix n = "0o" % lpadded n '0' oct {-# INLINE octPrefix #-} -- | Render an integer using octal notation with a leading 0x, padding with zeroes to the given width: -- -- >>> format (hexPrefix 16) 4097 -- "0x0000000000001001" hexPrefix :: Integral a => Int64 -> Format r (a -> r) hexPrefix n = "0x" % lpadded n '0' hex {-# INLINE hexPrefix #-} ������������������������������������������������������������������������formatting-7.2.0/src/Formatting/Examples.hs���������������������������������������������������������0000644�0000000�0000000�00000002662�07346545000�017104� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{-# LANGUAGE OverloadedStrings #-} -- | Examples that should always compile. If reading on Haddock, you -- can view the sources to each of these. module Formatting.Examples ( hello , strings , texts , builders , integers , floats , hexes , padding ) where import Data.Text.Lazy (Text) import Data.Text.Lazy.Builder (Builder) import Formatting -- | Simple hello, world! hello :: Text hello = format "Hello, World!" -- | Printing strings. strings :: Text strings = format ("Here comes a string: " % string % " and another " % string) "Hello, World!" "Ahoy!" -- | Printing texts. texts :: Text texts = format ("Here comes a string: " % text % " and another " % text) "Hello, World!" "Ahoy!" -- | Printing builders. builders :: Text builders = format ("Here comes a string: " % builder % " and another " % text) ("Hello, World!" :: Builder) "Ahoy!" -- | Printing integers. integers :: Text integers = format ("Here comes an integer: " % int % " and another: " % int) (23 :: Int) (0 :: Integer) -- | Printing floating points. floats :: Text floats = format ("Here comes a float: " % float) (123.2342 :: Float) -- | Printing integrals in hex (base-16). hexes :: Text hexes = format ("Here comes a hex: " % hex) (123 :: Int) -- | Padding. padding :: Text padding = format ("A left-padded number: " % left 3 '0') (9 :: Int) ������������������������������������������������������������������������������formatting-7.2.0/src/Formatting/Formatters.hs�������������������������������������������������������0000644�0000000�0000000�00000023137�07346545000�017454� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-} -- | -- Module : Formatting.Formatters -- Copyright : (c) 2013 Chris Done, 2013 Shachaf Ben-Kiki -- License : BSD3 -- Maintainer : alex@farfromthere.net -- Stability : experimental -- Portability : GHC -- -- Formatting functions. module Formatting.Formatters ( -- * Text/string types text, stext, string, shown, char, builder, fconst, -- * Numbers int, float, fixed, sci, scifmt, shortest, groupInt, commas, ords, plural, asInt, -- * Padding left, right, center, fitLeft, fitRight, -- * Bases base, bin, oct, hex, prefixBin, prefixOct, prefixHex, bytes, -- * Buildables build, Buildable, ) where import Formatting.Internal import Data.Char (chr, ord) import Data.Scientific import qualified Data.Text as S import qualified Data.Text as T import Formatting.Buildable (Buildable) import qualified Formatting.Buildable as B (build) import qualified Data.Text.Format as T import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as LT import Data.Text.Lazy.Builder (Builder) import qualified Data.Text.Lazy.Builder as T import Data.Text.Lazy.Builder.Scientific import Numeric (showIntAtBase) -- $setup -- >>> import Formatting.Internal -- | Output a lazy text. text :: Format r (Text -> r) text = later T.fromLazyText {-# INLINE text #-} -- | Output a strict text. stext :: Format r (S.Text -> r) stext = later T.fromText {-# INLINE stext #-} -- | Output a string. string :: Format r (String -> r) string = later (T.fromText . T.pack) {-# INLINE string #-} -- | Output a showable value (instance of 'Show') by turning it into -- 'Text': -- -- >>> format ("Value number " % shown % " is " % shown % ".") 42 False -- "Value number 42 is False." shown :: Show a => Format r (a -> r) shown = later (T.fromText . T.pack . show) {-# INLINE shown #-} -- | Output a character. char :: Format r (Char -> r) char = later B.build {-# INLINE char #-} -- | Build a builder. builder :: Format r (Builder -> r) builder = later id {-# INLINE builder #-} -- | Like `const` but for formatters. fconst :: Builder -> Format r (a -> r) fconst m = later (const m) {-# INLINE fconst #-} -- | Build anything that implements the "Buildable" class. build :: Buildable a => Format r (a -> r) build = later B.build {-# INLINE build #-} -- | Render an integral e.g. 123 -> \"123\", 0 -> \"0\". int :: Integral a => Format r (a -> r) int = base 10 {-# INLINE int #-} -- | Render some floating point with the usual notation, e.g. 123.32 => \"123.32\" float :: Real a => Format r (a -> r) float = later T.shortest {-# INLINE float #-} -- | Render a floating point number using normal notation, with the -- given number of decimal places. fixed :: Real a => Int -> Format r (a -> r) fixed i = later (T.fixed i) {-# INLINE fixed #-} -- | Render a floating point number using the smallest number of -- digits that correctly represent it. Note that in the case of whole -- numbers it will still add one decimal place, e.g. "1.0". shortest :: Real a => Format r (a -> r) shortest = later T.shortest {-# INLINE shortest #-} -- | Render a scientific number. sci :: Format r (Scientific -> r) sci = later scientificBuilder {-# INLINE sci #-} -- | Render a scientific number with options. scifmt :: FPFormat -> Maybe Int -> Format r (Scientific -> r) scifmt f i = later (formatScientificBuilder f i) {-# INLINE scifmt #-} -- | Shows the Int value of Enum instances using 'fromEnum'. -- -- >>> format ("Got: " % char % " (" % asInt % ")") 'a' 'a' -- "Got: a (97)" asInt :: Enum a => Format r (a -> r) asInt = later (T.shortest . fromEnum) {-# INLINE asInt #-} -- | Pad the left hand side of a string until it reaches k characters -- wide, if necessary filling with character c. left :: Buildable a => Int -> Char -> Format r (a -> r) left i c = later (T.left i c) {-# INLINE left #-} -- | Pad the right hand side of a string until it reaches k characters -- wide, if necessary filling with character c. right :: Buildable a => Int -> Char -> Format r (a -> r) right i c = later (T.right i c) {-# INLINE right #-} -- | Pad the left & right hand side of a string until it reaches k characters -- wide, if necessary filling with character c. center :: Buildable a => Int -> Char -> Format r (a -> r) center i c = later centerT where centerT = T.fromLazyText . LT.center (fromIntegral i) c . T.toLazyText . B.build -- | Group integral numbers, e.g. groupInt 2 '.' on 123456 -> \"12.34.56\". groupInt :: (Buildable n,Integral n) => Int -> Char -> Format r (n -> r) groupInt 0 _ = later B.build groupInt i c = later (\n -> if n < 0 then "-" <> commaize (negate n) else commaize n) where commaize = T.fromLazyText . LT.reverse . foldr merge "" . LT.zip (zeros <> cycle' zeros') . LT.reverse . T.toLazyText . B.build zeros = LT.replicate (fromIntegral i) (LT.singleton '0') zeros' = LT.singleton c <> LT.tail zeros merge (f, c') rest | f == c = LT.singleton c <> LT.singleton c' <> rest | otherwise = LT.singleton c' <> rest cycle' xs = xs <> cycle' xs -- | Fit in the given length, truncating on the left. fitLeft :: Buildable a => Int -> Format r (a -> r) fitLeft size = later (fit (fromIntegral size)) where fit i = T.fromLazyText . LT.take i . T.toLazyText . B.build -- | Fit in the given length, truncating on the right. fitRight :: Buildable a => Int -> Format r (a -> r) fitRight size = later (fit (fromIntegral size)) where fit i = T.fromLazyText . (\t -> LT.drop (LT.length t - i) t) . T.toLazyText . B.build -- | Add commas to an integral, e.g 12000 -> \ "12,000". commas :: (Buildable n,Integral n) => Format r (n -> r) commas = groupInt 3 ',' {-# INLINE commas #-} -- | Add a suffix to an integral, e.g. 1st, 2nd, 3rd, 21st. ords :: Integral n => Format r (n -> r) ords = later go where go n | tens > 3 && tens < 21 = T.fixed 0 n <> "th" | otherwise = T.fixed 0 n <> case n `mod` 10 of 1 -> "st" 2 -> "nd" 3 -> "rd" _ -> "th" where tens = n `mod` 100 -- | English plural suffix for an integral. -- -- For example: -- -- >>> :set -XOverloadedStrings -- >>> formatPeople = format (int % " " <> plural "person" "people" % ".") :: Int -> Data.Text.Lazy.Text -- >>> formatPeople 1 -- "1 person." -- >>> formatPeople 3 -- "3 people." plural :: (Num a, Eq a) => Text -> Text -> Format r (a -> r) plural s p = later (\i -> if i == 1 then B.build s else B.build p) -- | Render an integral at base n. base :: Integral a => Int -> Format r (a -> r) base numBase = later (B.build . atBase numBase) {-# INLINE base #-} -- | Render an integer using binary notation. (No leading 0b is -- added.) Defined as @bin = 'base' 2@. bin :: Integral a => Format r (a -> r) bin = base 2 {-# INLINE bin #-} -- | Render an integer using octal notation. (No leading 0o is -- added.) Defined as @oct = 'base' 8@. oct :: Integral a => Format r (a -> r) oct = base 8 {-# INLINE oct #-} -- | Render an integer using hexadecimal notation. (No leading 0x is -- added.) Has a specialized implementation. hex :: Integral a => Format r (a -> r) hex = later T.hex {-# INLINE hex #-} -- | Render an integer using binary notation with a leading 0b. -- -- See also 'Formatting.Combinators.binPrefix' for fixed-width formatting. prefixBin :: Integral a => Format r (a -> r) prefixBin = "0b" % bin {-# INLINE prefixBin #-} -- | Render an integer using octal notation with a leading 0o. -- -- See also 'Formatting.Combinators.octPrefix' for fixed-width formatting. prefixOct :: Integral a => Format r (a -> r) prefixOct = "0o" % oct {-# INLINE prefixOct #-} -- | Render an integer using hexadecimal notation with a leading 0x. -- -- See also 'Formatting.Combinators.hexPrefix' for fixed-width formatting. prefixHex :: Integral a => Format r (a -> r) prefixHex = "0x" % hex {-# INLINE prefixHex #-} -- The following code is mostly taken from `Numeric.Lens.' (from -- `lens') and modified. -- | Internal function that converts a number to a base base-2 through -- base-36. atBase :: Integral a => Int -> a -> String atBase b _ | b < 2 || b > 36 = error ("base: Invalid base " ++ show b) atBase b n = showSigned' (showIntAtBase (toInteger b) intToDigit') (toInteger n) "" {-# INLINE atBase #-} -- | A simpler variant of 'Numeric.showSigned' that only prepends a dash and -- doesn't know about parentheses showSigned' :: Real a => (a -> ShowS) -> a -> ShowS showSigned' f n | n < 0 = showChar '-' . f (negate n) | otherwise = f n -- | Like 'Data.Char.intToDigit', but handles up to base-36 intToDigit' :: Int -> Char intToDigit' i | i >= 0 && i < 10 = chr (ord '0' + i) | i >= 10 && i < 36 = chr (ord 'a' + i - 10) | otherwise = error ("intToDigit': Invalid int " ++ show i) -- | Renders a given byte count using an appropiate decimal binary suffix: -- -- >>> format (bytes shortest) 1024 -- "1KB" -- -- >>> format (bytes (fixed 2 % " ")) (1024*1024*5) -- "5.00 MB" -- bytes :: (Ord f,Integral a,Fractional f) => Format Builder (f -> Builder) -- ^ formatter for the decimal part -> Format r (a -> r) bytes d = later go where go bs = bprint d (fromIntegral (signum bs) * dec) <> bytesSuffixes !! i where (dec,i) = getSuffix (abs bs) getSuffix n = until p (\(x,y) -> (x / 1024,y + 1)) (fromIntegral n,0) where p (n',numDivs) = n' < 1024 || numDivs == (length bytesSuffixes - 1) bytesSuffixes = ["B","KB","MB","GB","TB","PB","EB","ZB","YB"] ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/src/Formatting/FromBuilder.hs������������������������������������������������������0000644�0000000�0000000�00000003557�07346545000�017544� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{-# LANGUAGE FlexibleInstances #-} module Formatting.FromBuilder ( FromBuilder(..) , formatted ) where import qualified Data.Text as T import qualified Data.Text.Lazy as TL import Data.Text.Lazy.Builder (Builder) import qualified Data.Text.Lazy.Builder as TL import Formatting.Internal (Format (..)) -- $setup -- >>> import qualified Data.Text.Lazy as TL (Text) -- >>> import qualified Data.Text.Lazy.IO as TL -- >>> import qualified Data.Text as T (Text) -- >>> import qualified Data.Text.IO as T -- >>> import Formatting ((%)) -- >>> import Formatting.Formatters (int) -- >>> :set -XOverloadedStrings -- >>> :set -XTypeApplications -- | Anything that can be created from a 'Builder'. -- This class makes it easier to add formatting to other API's. -- See 'formatted' for some examples of this class in action. class FromBuilder a where fromBuilder :: Builder -> a instance FromBuilder Builder where fromBuilder = id {-# INLINE fromBuilder #-} instance FromBuilder TL.Text where fromBuilder = TL.toLazyText {-# INLINE fromBuilder #-} instance FromBuilder T.Text where fromBuilder = TL.toStrict . TL.toLazyText {-# INLINE fromBuilder #-} instance FromBuilder [Char] where fromBuilder = TL.unpack . TL.toLazyText {-# INLINE fromBuilder #-} -- | Makes it easy to add formatting to any api that is expecting a builder, -- a strict or lazy text, or a string. -- It is essentially (flip runFormat), but with a more generous type due to -- the typeclass. -- -- For example: -- >>> formatted TL.putStr ("x is: " % int % "\n") 7 -- x is: 7 -- >>> formatted T.putStr ("x is: " % int % "\n") 7 -- x is: 7 -- >>> formatted (id @TL.Text) ("x is: " % int % "\n") 7 -- "x is: 7\n" -- >>> formatted (id @T.Text) ("x is: " % int % "\n") 7 -- "x is: 7\n" formatted :: FromBuilder t => (t -> o) -> Format o a -> a formatted k f = runFormat f (k . fromBuilder) �������������������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/src/Formatting/Internal.hs���������������������������������������������������������0000644�0000000�0000000�00000015404�07346545000�017100� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleInstances #-} -- | Internal format starters. module Formatting.Internal ( Format(..) , (%) , (%+) , (%.) , (<%+>) , now , bind , mapf , later , format , sformat , bprint , bformat , fprint , fprintLn , hprint , hprintLn , formatToString ) where import Control.Category (Category(..)) import Control.Monad.IO.Class (MonadIO (..)) import Data.Monoid import Data.String import qualified Data.Text as S (Text) import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as T import qualified Data.Text.Lazy as TL import Data.Text.Lazy.Builder (Builder) import qualified Data.Text.Lazy.Builder as T import qualified Data.Text.Lazy.Builder as TLB import qualified Data.Text.Lazy.IO as T import Prelude hiding ((.),id) import System.IO -- $setup -- >>> import Formatting.Formatters -- | A formatter. When you construct formatters the first type -- parameter, @r@, will remain polymorphic. The second type -- parameter, @a@, will change to reflect the types of the data that -- will be formatted. For example, in -- -- @ -- myFormat :: Format r (Text -> Int -> r) -- myFormat = \"Person's name is \" % text % \", age is \" % hex -- @ -- -- the first type parameter remains polymorphic, and the second type -- parameter is @Text -> Int -> r@, which indicates that it formats a -- 'Text' and an 'Int'. -- -- When you run the 'Format', for example with 'format', you provide -- the arguments and they will be formatted into a string. -- -- @ -- \> format (\"Person's name is \" % text % \", age is \" % hex) \"Dave\" 54 -- \"Person's name is Dave, age is 36\" -- @ newtype Format r a = Format {runFormat :: (Builder -> r) -> a} -- | This can be used almost like contramap, e.g: -- -- @ -- formatter :: Format r (b -> r) -- formatter = _ -- -- adapter :: a -> b -- adapter = _ -- -- adapted :: Format r (a -> r) -- adapted = fmap (. adapter) formatter -- @ instance Functor (Format r) where fmap f (Format k) = Format (f . k) instance Semigroup (Format r (a -> r)) where m <> n = Format (\k a -> runFormat m (\b1 -> runFormat n (\b2 -> k (b1 <> b2)) a) a) -- | Like @(<>)@ except put a space between the two formatters. For example: -- @format (year <%+> month <%+> dayOfMonth) now@ will yield @"2022 06 06"@ (<%+>) :: Format r (a -> r) -> Format r (a -> r) -> Format r (a -> r) m <%+> n = Format (\k a -> runFormat m (\b1 -> runFormat n (\b2 -> k (b1 <> TLB.singleton ' ' <> b2)) a) a) -- | Useful instance for applying two formatters to the same input -- argument. For example: @format (year <> "/" % month) now@ will -- yield @"2015/01"@. instance Monoid (Format r (a -> r)) where mempty = Format (\k _ -> k mempty) -- | Useful instance for writing format string. With this you can -- write @\"Foo\"@ instead of @now "Foo!"@. instance (a ~ r) => IsString (Format r a) where fromString = now . fromString -- | The same as (%). At present using 'Category' has an import -- overhead, but one day it might be imported as standard. instance Category Format where id = now mempty f . g = f `bind` \a -> g `bind` \b -> now (a `mappend` b) -- | Concatenate two formatters. -- -- @formatter1 % formatter2@ is a formatter that accepts arguments for -- @formatter1@ and @formatter2@ and concatenates their results. For example -- -- @ -- format1 :: Format r (Text -> r) -- format1 = \"Person's name is \" % text -- @ -- -- @ -- format2 :: Format r r -- format2 = \", \" -- @ -- -- @ -- format3 :: Format r (Int -> r) -- format3 = \"age is \" % hex -- @ -- -- @ -- myFormat :: Format r (Text -> Int -> r) -- myFormat = format1 % format2 % format3 -- @ -- -- Notice how the argument types of @format1@ and @format3@ are -- gathered into the type of @myFormat@. -- -- (This is actually the composition operator for 'Format's -- 'Category' instance, but that is (at present) inconvenient to use -- with regular "Prelude". So this function is provided as a -- convenience.) (%) :: Format r a -> Format r' r -> Format r' a (%) = (.) infixr 9 % -- | Concatenate two formatters with a space in between. -- -- >>> :set -XOverloadedStrings -- >>> format (int %+ "+" %+ int %+ "=" %+ int) 2 3 5 -- "2 + 3 = 5" -- (%+) :: Format r a -> Format r' r -> Format r' a f %+ g = f `bind` \a -> g `bind` \b -> now (a `mappend` TLB.singleton ' ' `mappend` b) infixr 9 %+ -- | Function compose two formatters. Will feed the result of one -- formatter into another. (%.) :: Format r (Builder -> r') -> Format r' a -> Format r a (%.) (Format a) (Format b) = Format (b . a) infixr 8 %. -- | Don't format any data, just output a constant 'Builder'. now :: Builder -> Format r r now a = Format ($ a) -- | Monadic indexed bind for holey monoids. bind :: Format r a -> (Builder -> Format r' r) -> Format r' a m `bind` f = Format $ \k -> runFormat m (\a -> runFormat (f a) k) -- | Functorial map over a formatter's input. Example: @format (mapf (drop 1) string) \"hello\"@ mapf :: (a -> b) -> Format r (b -> t) -> Format r (a -> t) mapf f m = Format (\k -> runFormat m k . f) -- | Format a value of type @a@ using a function of type @a -> -- 'Builder'@. For example, @later (f :: Int -> Builder)@ produces -- @Format r (Int -> r)@. later :: (a -> Builder) -> Format r (a -> r) later f = Format (. f) -- | Run the formatter and return a lazy 'Text' value. format :: Format Text a -> a format m = runFormat m T.toLazyText -- | Run the formatter and return a strict 'S.Text' value. sformat :: Format S.Text a -> a sformat m = runFormat m (T.toStrict . T.toLazyText) -- | Run the formatter and return a 'Builder' value. bprint :: Format Builder a -> a bprint m = runFormat m id -- | Run the formatter and return a 'Builder' value. -- -- This is a newer synonym for 'bprint', following the naming convention set by 'format' and 'sformat'. bformat :: Format Builder a -> a bformat m = runFormat m id -- | Run the formatter and print out the text to stdout. fprint :: MonadIO m => Format (m ()) a -> a fprint m = runFormat m (liftIO . T.putStr . T.toLazyText) -- | Run the formatter and print out the text to stdout, followed by a newline. fprintLn :: MonadIO m => Format (m ()) a -> a fprintLn m = runFormat m (liftIO . T.putStrLn . T.toLazyText) -- | Run the formatter and put the output onto the given 'Handle'. hprint :: MonadIO m => Handle -> Format (m ()) a -> a hprint h m = runFormat m (liftIO . T.hPutStr h . T.toLazyText) -- | Run the formatter and put the output and a newline onto the given 'Handle'. hprintLn :: MonadIO m => Handle -> Format (m ()) a -> a hprintLn h m = runFormat m (liftIO . T.hPutStrLn h . T.toLazyText) -- | Run the formatter and return a list of characters. formatToString :: Format String a -> a formatToString m = runFormat m (TL.unpack . TLB.toLazyText) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/src/Formatting/Internal/�����������������������������������������������������������0000755�0000000�0000000�00000000000�07346545000�016540� 5����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/src/Formatting/Internal/Raw.hs�����������������������������������������������������0000644�0000000�0000000�00000000571�07346545000�017630� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- | Reexports of things that were previously in the @text-format@ package. module Formatting.Internal.Raw ( module Data.Text.Format, module Data.Text.Format.Functions, module Data.Text.Lazy.Builder.Int, module Data.Text.Format.Types ) where import Data.Text.Format import Data.Text.Format.Functions import Data.Text.Lazy.Builder.Int import Data.Text.Format.Types ���������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/src/Formatting/ShortFormatters.hs��������������������������������������������������0000644�0000000�0000000�00000005220�07346545000�020465� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{-# LANGUAGE RankNTypes #-} -- | -- Module : Formatting.ShortFormatters -- Copyright : (c) 2013 Chris Done, 2013 Shachaf Ben-Kiki -- License : BSD3 -- Maintainer : chrisdone@gmail.com -- Stability : experimental -- Portability : GHC -- -- Single letters for short formatting. module Formatting.ShortFormatters ( t , d , b , o , x , st , s , sh , c , f , sf , l , r ) where import Formatting.Formatters (bin, int, oct) import Formatting.Internal import qualified Data.Text as S import qualified Data.Text as T import qualified Data.Text.Format as T import Data.Text.Lazy (Text) import qualified Data.Text.Lazy.Builder as T import Formatting.Buildable (Buildable) import qualified Formatting.Buildable as B (build) -- | Output a lazy text. t :: Format r (Text -> r) t = later T.fromLazyText {-# INLINE t #-} -- | Render an integral e.g. 123 -> \"123\", 0 -> \"0\". d :: Integral a => Format r (a -> r) d = int {-# INLINE d #-} -- | Render an integer using binary notation. (No leading 0b is -- added.) b :: Integral a => Format r (a -> r) b = bin {-# INLINE b #-} -- | Render an integer using octal notation. (No leading 0o is added.) o :: Integral a => Format r (a -> r) o = oct {-# INLINE o #-} -- | Render an integer using hexadecimal notation. (No leading 0x is -- added.) x :: Integral a => Format r (a -> r) x = later T.hex {-# INLINE x #-} -- | Output a strict text. st :: Format r (S.Text -> r) st = later T.fromText {-# INLINE st #-} -- | Output a string. s :: Format r (String -> r) s = later (T.fromText . T.pack) {-# INLINE s #-} -- | Output a showable value (instance of 'Show') by turning it into -- 'Text'. sh :: Show a => Format r (a -> r) sh = later (T.fromText . T.pack . show) {-# INLINE sh #-} -- | Output a character. c :: Format r (Char -> r) c = later B.build {-# INLINE c #-} -- | Render a floating point number using normal notation, with the -- given number of decimal places. f :: Real a => Int -> Format r (a -> r) f i = later (T.fixed i) {-# INLINE f #-} -- | Render a floating point number using the smallest number of -- digits that correctly represent it. sf :: Real a => Format r (a -> r) sf = later T.shortest {-# INLINE sf #-} -- | Pad the left hand side of a string until it reaches @k@ characters -- wide, if necessary filling with character @ch@. l :: Buildable a => Int -> Char -> Format r (a -> r) l i ch = later (T.left i ch) {-# INLINE l #-} -- | Pad the right hand side of a string until it reaches @k@ characters -- wide, if necessary filling with character @ch@. r :: Buildable a => Int -> Char -> Format r (a -> r) r i ch = later (T.right i ch) {-# INLINE r #-} ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/src/Formatting/Time.hs�������������������������������������������������������������0000644�0000000�0000000�00000026015�07346545000�016222� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-} -- | Formatters for time. module Formatting.Time ( tz , tzName , datetime , hm , hms , hmsL , hmsPL , dayHalf , dayHalfU , hour24 , hour12 , hour24S , hour12S , minute , second , pico , decimals , epoch , dateSlash , dateDash , dateSlashL , year , yy , century , monthName , monthNameShort , month , dayOfMonth , dayOfMonthOrd , dayOfMonthS , day , weekYear , weekYY , weekCentury , week , dayOfWeek , dayNameShort , dayName , weekFromZero , dayOfWeekFromZero , weekOfYearMon , diff , years , days , hours , minutes , seconds , diffComponents , customDiffComponents , fmt , customTimeFmt ) where import Data.List (find) import Data.Tuple import Formatting.Formatters hiding (build, base) import Formatting.Internal import Data.Text (Text) import qualified Data.Text as T import Formatting.Buildable import Data.Time (FormatTime, formatTime, defaultTimeLocale) import System.Locale () import Control.Monad.Trans.State.Strict -- * For 'TimeZone' (and 'ZonedTime' and 'UTCTime'): -- | Timezone offset on the format @-HHMM@. tz :: FormatTime a => Format r (a -> r) tz = later (build . fmt "%z") -- | Timezone name. tzName :: FormatTime a => Format r (a -> r) tzName = later (build . fmt "%Z") -- | As 'dateTimeFmt' @locale@ (e.g. @%a %b %e %H:%M:%S %Z %Y@). datetime :: FormatTime a => Format r (a -> r) datetime = later (build . fmt "%c") -- * For 'TimeOfDay' (and 'LocalTime' and 'ZonedTime' and 'UTCTime'): -- | Same as @%H:%M@. hm :: FormatTime a => Format r (a -> r) hm = later (build . fmt "%R") -- | Same as @%H:%M:%S@. hms :: FormatTime a => Format r (a -> r) hms = later (build . fmt "%T") -- | As 'timeFmt' @locale@ (e.g. @%H:%M:%S@). hmsL :: FormatTime a => Format r (a -> r) hmsL = later (build . fmt "%X") -- | As 'time12Fmt' @locale@ (e.g. @%I:%M:%S %p@). hmsPL :: FormatTime a => Format r (a -> r) hmsPL = later (build . fmt "%r") -- | Day half from ('amPm' @locale@), converted to lowercase, @am@, -- @pm@. dayHalf :: FormatTime a => Format r (a -> r) dayHalf = later (build . fmt "%P") -- | Day half from ('amPm' @locale@), @AM@, @PM@. dayHalfU :: FormatTime a => Format r (a -> r) dayHalfU = later (build . fmt "%p") -- | Hour, 24-hour, leading 0 as needed, @00@ - @23@. hour24 :: FormatTime a => Format r (a -> r) hour24 = later (build . fmt "%H") -- | Hour, 12-hour, leading 0 as needed, @01@ - @12@. hour12 :: FormatTime a => Format r (a -> r) hour12 = later (build . fmt "%I") -- | Hour, 24-hour, leading space as needed, @ 0@ - @23@. hour24S :: FormatTime a => Format r (a -> r) hour24S = later (build . fmt "%k") -- | Hour, 12-hour, leading space as needed, @ 1@ - @12@. hour12S :: FormatTime a => Format r (a -> r) hour12S = later (build . fmt "%l") -- | Minute, @00@ - @59@. minute :: FormatTime a => Format r (a -> r) minute = later (build . fmt "%M") -- | Second, without decimal part, @00@ - @60@. second :: FormatTime a => Format r (a -> r) second = later (build . fmt "%S") -- | Picosecond, including trailing zeros, @000000000000@ - -- @999999999999@. pico :: FormatTime a => Format r (a -> r) pico = later (build . fmt "%q") -- | Decimal point and up to 12 second decimals, without trailing -- zeros. For a whole number of seconds, this produces the empty -- string. decimals :: FormatTime a => Format r (a -> r) decimals = later (build . fmt "%Q") -- * For 'UTCTime' and 'ZonedTime' -- -- Number of whole seconds since the Unix epoch. For times before -- the Unix epoch, this is a negative number. Note that in @%s.%q@ and @%s%Q@ -- the decimals are positive, not negative. For example, 0.9 seconds -- before the Unix epoch is formatted as @-1.1@ with @%s%Q@. epoch :: FormatTime a => Format r (a -> r) epoch = later (build . fmt "%s") -- * For 'Day' (and 'LocalTime' and 'ZonedTime' and 'UTCTime'): -- | Same as @%m\/%d\/%y@. dateSlash :: FormatTime a => Format r (a -> r) dateSlash = later (build . fmt "%D") -- | Same as @%Y-%m-%d@. dateDash :: FormatTime a => Format r (a -> r) dateDash = later (build . fmt "%F") -- | As 'dateFmt' @locale@ (e.g. @%m\/%d\/%y@). dateSlashL :: FormatTime a => Format r (a -> r) dateSlashL = later (build . fmt "%x") -- | Year. year :: FormatTime a => Format r (a -> r) year = later (build . fmt "%Y") -- | Last two digits of year, @00@ - @99@. yy :: FormatTime a => Format r (a -> r) yy = later (build . fmt "%y") -- | Century (being the first two digits of the year), @00@ - @99@. century :: FormatTime a => Format r (a -> r) century = later (build . fmt "%C") -- | Month name, long form ('fst' from 'months' @locale@), @January@ - -- @December@. monthName :: FormatTime a => Format r (a -> r) monthName = later (build . fmt "%B") -- | @ %H] month name, short form ('snd' from 'months' @locale@), -- @Jan@ - @Dec@. monthNameShort :: FormatTime a => Format r (a -> r) monthNameShort = later (build . fmt "%b") -- | Month of year, leading 0 as needed, @01@ - @12@. month :: FormatTime a => Format r (a -> r) month = later (build . fmt "%m") -- | Day of month, leading 0 as needed, @01@ - @31@. dayOfMonth :: FormatTime a => Format r (a -> r) dayOfMonth = later (build . fmt "%d") -- | Day of month, @1st@, @2nd@, @25th@, etc. dayOfMonthOrd :: FormatTime a => Format r (a -> r) dayOfMonthOrd = later (bprint ords . toInt) where toInt :: FormatTime a => a -> Int toInt = read . formatTime defaultTimeLocale "%d" -- | Day of month, leading space as needed, @ 1@ - @31@. dayOfMonthS :: FormatTime a => Format r (a -> r) dayOfMonthS = later (build . fmt "%e") -- | Day of year for Ordinal Date format, @001@ - @366@. day :: FormatTime a => Format r (a -> r) day = later (build . fmt "%j") -- | Year for Week Date format e.g. @2013@. weekYear :: FormatTime a => Format r (a -> r) weekYear = later (build . fmt "%G") -- | Last two digits of year for Week Date format, @00@ - @99@. weekYY :: FormatTime a => Format r (a -> r) weekYY = later (build . fmt "%g") -- | Century (first two digits of year) for Week Date format, @00@ - -- @99@. weekCentury :: FormatTime a => Format r (a -> r) weekCentury = later (build . fmt "%f") -- | Week for Week Date format, @01@ - @53@. week :: FormatTime a => Format r (a -> r) week = later (build . fmt "%V") -- | Day for Week Date format, @1@ - @7@. dayOfWeek :: FormatTime a => Format r (a -> r) dayOfWeek = later (build . fmt "%u") -- | Day of week, short form ('snd' from 'wDays' @locale@), @Sun@ - -- @Sat@. dayNameShort :: FormatTime a => Format r (a -> r) dayNameShort = later (build . fmt "%a") -- | Day of week, long form ('fst' from 'wDays' @locale@), @Sunday@ - -- @Saturday@. dayName :: FormatTime a => Format r (a -> r) dayName = later (build . fmt "%A") -- | Week number of year, where weeks start on Sunday (as -- 'sundayStartWeek'), @00@ - @53@. weekFromZero :: FormatTime a => Format r (a -> r) weekFromZero = later (build . fmt "%U") -- | Day of week number, @0@ (= Sunday) - @6@ (= Saturday). dayOfWeekFromZero :: FormatTime a => Format r (a -> r) dayOfWeekFromZero = later (build . fmt "%w") -- | Week number of year, where weeks start on Monday (as -- 'mondayStartWeek'), @00@ - @53@. weekOfYearMon :: FormatTime a => Format r (a -> r) weekOfYearMon = later (build . fmt "%W") -- * Time spans, diffs, 'NominalDiffTime', 'DiffTime', etc. -- | Display a time span as one time relative to another. Input is -- assumed to be seconds. Typical inputs are 'NominalDiffTime' and -- 'DiffTime'. diff :: (RealFrac n) => Bool -- ^ Display 'in/ago'? -> Format r (n -> r) -- ^ Example: '3 seconds ago', 'in three days'.) diff fix = later diffed where diffed ts = case find (\(s,_,_) -> abs ts >= s) (reverse ranges) of Nothing -> "unknown" Just (_,f,base) -> bprint (prefix % f % suffix) (toInt ts base) where prefix = now (if fix && ts > 0 then "in " else "") suffix = now (if fix && ts < 0 then " ago" else "") toInt ts base = abs (round (ts / base)) :: Int ranges = [(0,int % " milliseconds",0.001) ,(1,int % " seconds",1) ,(minute',fconst "a minute",0) ,(minute'*2,int % " minutes",minute') ,(minute'*30,fconst "half an hour",0) ,(minute'*31,int % " minutes",minute') ,(hour',fconst "an hour",0) ,(hour'*2,int % " hours",hour') ,(hour'*3,fconst "a few hours",0) ,(hour'*4,int % " hours",hour') ,(day',fconst "a day",0) ,(day'*2,int % " days",day') ,(week',fconst "a week",0) ,(week'*2,int % " weeks",week') ,(month',fconst "a month",0) ,(month'*2,int % " months",month') ,(year',fconst "a year",0) ,(year'*2,int % " years",year')] where year' = month' * 12 month' = day' * 30 week' = day' * 7 day' = hour' * 24 hour' = minute' * 60 minute' = 60 -- | Display the absolute value time span in years. years :: (RealFrac n) => Int -- ^ Decimal places. -> Format r (n -> r) years n = later (bprint (fixed n) . abs . count) where count n' = n' / 365 / 24 / 60 / 60 -- | Display the absolute value time span in days. days :: (RealFrac n) => Int -- ^ Decimal places. -> Format r (n -> r) days n = later (bprint (fixed n) . abs . count) where count n' = n' / 24 / 60 / 60 -- | Display the absolute value time span in hours. hours :: (RealFrac n) => Int -- ^ Decimal places. -> Format r (n -> r) hours n = later (bprint (fixed n) . abs . count) where count n' = n' / 60 / 60 -- | Display the absolute value time span in minutes. minutes :: (RealFrac n) => Int -- ^ Decimal places. -> Format r (n -> r) minutes n = later (bprint (fixed n) . abs . count) where count n' = n' / 60 -- | Display the absolute value time span in seconds. seconds :: (RealFrac n) => Int -- ^ Decimal places. -> Format r (n -> r) seconds n = later (bprint (fixed n) . abs . count) where count n' = n' -- | Display seconds in the following pattern: -- @00:00:00:00@, which ranges from days to seconds. diffComponents :: (RealFrac n) => Format r (n -> r) diffComponents = customDiffComponents (left 2 '0' % ":" % left 2 '0' % ":" % left 2 '0' % ":" % left 2 '0') -- | Variation of 'diffComponents', -- which lets you explicitly specify how to render each component. customDiffComponents :: (RealFrac n) => (forall r'. Format r' (Integer -> Integer -> Integer -> Integer -> r')) -> Format r (n -> r) customDiffComponents subFormat = later builder' where builder' diffTime = flip evalState (round diffTime) $ do seconds' <- state (swap . flip divMod 60) minutes' <- state (swap . flip divMod 60) hours' <- state (swap . flip divMod 24) days' <- get return (bprint subFormat days' hours' minutes' seconds') -- * Internal. -- | Formatter call. Probably don't want to use this. fmt :: FormatTime a => Text -> a -> Text fmt f = T.pack . formatTime defaultTimeLocale (T.unpack f) -- | Helper for creating custom time formatters customTimeFmt :: FormatTime a => Text -> Format r (a -> r) customTimeFmt f = later (build . fmt f) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/test/������������������������������������������������������������������������������0000755�0000000�0000000�00000000000�07346545000�013042� 5����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������formatting-7.2.0/test/Spec.hs�����������������������������������������������������������������������0000644�0000000�0000000�00000032527�07346545000�014301� 0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS -Wno-type-defaults #-} import Control.Monad import Data.Char (isSpace, isUpper) import Data.Int import qualified Data.Monoid import Data.Scientific import qualified Data.Semigroup import qualified Data.Text.Lazy as LT import Data.Time (Day) import Formatting as F import Formatting.Time import Test.Hspec main :: IO () main = hspec spec spec :: Spec spec = do describe "Regression tests" $ do describe "https://github.com/AJChapman/formatting/issues/36" $ do it "format (later id <> later id) \"x\"" $ format (later id Data.Monoid.<> later id) "x" `shouldBe` "xx" it "format (later id <> later id) \"x\"" $ format (later id Data.Semigroup.<> later id) "x" `shouldBe` "xx" describe "https://github.com/AJChapman/formatting/issues/31" $ it "10^16-1" $ F.format F.int (10 ^ (16 :: Int) - 1 :: Int) `shouldBe` "9999999999999999" describe "https://github.com/AJChapman/formatting/issues/28" $ do it "-100" $ sformat (groupInt 3 ',') (-100 :: Int) `shouldBe` "-100" it "-100,000,000" $ sformat (groupInt 3 ',') (-100000000 :: Int) `shouldBe` "-100,000,000" it "100,000,000" $ sformat (groupInt 3 ',') (100000000 :: Int) `shouldBe` "100,000,000" describe "https://github.com/bos/text-format/issues/18" $ do it "build (minBound :: Int)" $ format build (minBound :: Int64) `shouldBe` "-9223372036854775808" it "build (maxBound :: Int)" $ format build (maxBound :: Int) `shouldBe` "9223372036854775807" describe "https://github.com/AJChapman/formatting/issues/62" $ do it "left 3 '0' (0 :: Int)" $ format (left 3 '0') (0 ::Int) `shouldBe` "000" it "left 3 '0' (0 :: Word)" $ format (left 3 '0') (0 ::Word) `shouldBe` "000" describe "https://github.com/AJChapman/formatting/issues/60" $ it "build (minBound :: Word)" $ format build (minBound :: Word) `shouldBe` "0" describe "https://github.com/AJChapman/formatting/issues/59" $ it "shortest not scientific" $ format shortest (0.01 :: Double) `shouldBe` "0.01" describe "Floating point" $ do it "Fixed" $ format (fixed 4) (12.123456 :: Double) `shouldBe` "12.1235" it "Variable" $ format float (12.123456 :: Double) `shouldBe` "12.123456" it "Shortest" $ format shortest (12.0000 :: Double) `shouldBe` "12" describe "Scientific" $ do it "sci" $ format sci (scientific 60221409 16) `shouldBe` "6.0221409e23" it "scifmt" $ format (scifmt Exponent (Just 3)) (scientific 60221409 16) `shouldBe` "6.022e23" it "scifmt" $ format (scifmt Exponent Nothing) (scientific 60221409 16) `shouldBe` "6.0221409e23" it "scifmt" $ format (scifmt Fixed Nothing) (scientific 60221409 16) `shouldBe` "602214090000000000000000.0" it "scifmt" $ format (scifmt Generic (Just 5)) (scientific 60221409 16) `shouldBe` "6.02214e23" describe "Bytes" $ do it "1KB" $ format (bytes shortest) (1024 :: Int) `shouldBe` "1KB" it "1.15GB" $ format (bytes (fixed 2)) (1234567890 :: Int) `shouldBe` "1.15GB" describe "Buildable a => Buildable [a]" (do it "\"\" :: [Char] (backwards compatibility)" (shouldBe (format build ("" :: String)) "") it "\"hi\" :: [Char] (backwards compatibility)" (shouldBe (format build ("hi" :: String)) "hi") it "[1,2,3] :: [Int]" (shouldBe (format build ([1,2,3] :: [Int])) "[1,2,3]") it "[] :: [Int]" (shouldBe (format build ([] :: [Int])) "[]")) describe "ords" $ do let tests :: [(Int, String)] tests = [ ( 1, "1st") , ( 2, "2nd") , ( 3, "3rd") , ( 4, "4th") , ( 5, "5th") , ( 6, "6th") , ( 7, "7th") , ( 8, "8th") , ( 9, "9th") , (10, "10th") , (11, "11th") , (12, "12th") , (13, "13th") , (14, "14th") , (15, "15th") , (16, "16th") , (17, "17th") , (18, "18th") , (19, "19th") , (20, "20th") , (21, "21st") , (22, "22nd") , (23, "23rd") , (24, "24th") , (25, "25th") , (26, "26th") , (27, "27th") , (28, "28th") , (29, "29th") , (30, "30th") , (31, "31st") , (31, "31st") , (32, "32nd") , (33, "33rd") , (34, "34th") ] forM_ tests $ \(input, output) -> it output $ format ords input `shouldBe` LT.pack output describe "plural" $ do let formatPeople = format (int % " " <> plural "person" "people" % ".") it "formats a person" $ formatPeople (1 :: Int) `shouldBe` "1 person." it "formats a person" $ formatPeople (3 :: Int) `shouldBe` "3 people." describe "diffComponents" $ do it "59s" $ flip shouldBe "00:00:00:59" $ format diffComponents (59 :: Double) it "minute" $ flip shouldBe "00:00:01:00" $ format diffComponents (60 :: Double) it "90s" $ flip shouldBe "00:00:01:30" $ format diffComponents (90 :: Double) it "hour" $ flip shouldBe "00:01:00:00" $ format diffComponents (3600 :: Double) it "day" $ flip shouldBe "01:00:00:00" $ format diffComponents (86400 :: Double) describe "container formatters" $ do it "maybed Nothing" $ format (maybed "Goodbye" text) Nothing `shouldBe` "Goodbye" it "maybed Just" $ format (maybed "Goodbye" text) (Just "Hello") `shouldBe` "Hello" it "optioned Nothing" $ format (optioned text) Nothing `shouldBe` "" it "optioned Just" $ format (optioned text) (Just "Hello") `shouldBe` "Hello" it "eithered Left" $ format (eithered text int) (Left "Error!") `shouldBe` "Error!" it "eithered Right" $ format (eithered text int) (Right 69) `shouldBe` "69" it "lefted Left" $ format (lefted text) (Left "bingo") `shouldBe` "bingo" it "lefted Right" $ format (lefted text) (Right 16) `shouldBe` "" it "righted Left" $ format (righted text) (Left 16) `shouldBe` "" it "righted Right" $ format (righted text) (Right "bingo") `shouldBe` "bingo" describe "list formatters" $ do it "concatenated" $ format (concatenated text) ["one", "two", "three"] `shouldBe` "onetwothree" it "joinedWith" $ format (joinedWith (mconcat . reverse) int) [123, 456, 789] `shouldBe` "789456123" it "intercalated" $ format (intercalated "||" int) [1, 2, 3] `shouldBe` "1||2||3" it "unworded" $ format (unworded int) [1, 2, 3] `shouldBe` "1 2 3" it "unlined" $ format (unlined char) ['a'..'c'] `shouldBe` "a\nb\nc\n" it "spaced" $ format (spaced int) [1, 2, 3] `shouldBe` "1 2 3" it "commaSep" $ format (took 5 (commaSep int)) [1..] `shouldBe` "1,2,3,4,5" it "commaSpaceSep" $ format (took 3 (commaSpaceSep ords)) [1..] `shouldBe` "1st, 2nd, 3rd" it "list" $ format (list stext) ["one", "two", "three"] `shouldBe` "[one, two, three]" it "qlist" $ format (qlist stext) ["one", "two", "three"] `shouldBe` "[\"one\", \"two\", \"three\"]" it "took" $ format (took 7 (list bin)) [1..] `shouldBe` "[1, 10, 11, 100, 101, 110, 111]" it "dropped" $ format (dropped 3 (list int)) [1..6] `shouldBe` "[4, 5, 6]" describe "splitting formatters" $ do it "splat" $ format (splat isSpace commaSpaceSep stext) "This\t is\n\t\t poorly formatted " `shouldBe` "This, , , is, , , , , poorly, formatted, , , " it "splatWith" $ format (splatWith (LT.chunksOf 3) list int) 1234567890 `shouldBe` "[123, 456, 789, 0]" it "splatOn" $ format (splatOn "," unlined text) "one,two,three" `shouldBe` "one\ntwo\nthree\n" it "worded" $ format (worded list text) "one two three " `shouldBe` "[one, two, three]" 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\", \"\"]" describe "altering combinators" $ do it "alteredWith" $ format (alteredWith LT.reverse int) 123456 `shouldBe` "654321" it "charsKeptIf" $ format (charsKeptIf isUpper text) "Data.Char.isUpper" `shouldBe` "DCU" it "charsRemovedIf" $ format (charsRemovedIf isUpper text) "Data.Char.isUpper" `shouldBe` "ata.har.ispper" 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>'." it "uppercased" $ format (uppercased text) "I'm not shouting, you're shouting." `shouldBe` "I'M NOT SHOUTING, YOU'RE SHOUTING." it "lowercased" $ format (lowercased text) "Cd SrC/; Rm -Rf *" `shouldBe` "cd src/; rm -rf *" it "titlecased" $ format (titlecased string) "the life of brian" `shouldBe` "The Life Of Brian" it "ltruncated" $ format (ltruncated 5 text) "hellos" `shouldBe` "he..." it "ltruncated, non-truncated" $ format (ltruncated 5 text) "hello" `shouldBe` "hello" it "rtruncated" $ format (rtruncated 5 text) "hellos" `shouldBe` "...os" it "rtruncated, non-truncated" $ format (rtruncated 5 text) "hello" `shouldBe` "hello" it "ctruncated" $ format (ctruncated 15 4 text) "The quick brown fox jumps over the lazy dog." `shouldBe` "The quick brown...dog." it "ctruncated, non-truncated" $ format (ctruncated 15 4 text) "The quick brown fox" `shouldBe` "The quick brown fox" it "lpadded" $ format (lpadded 7 ' ' int) 1 `shouldBe` " 1" it "lpadded doesn't shorten" $ format (lpadded 7 ' ' int) 123456789 `shouldBe` "123456789" it "rpadded" $ format (rpadded 7 ' ' int) 1 `shouldBe` "1 " it "cpadded" $ format (cpadded 7 ' ' int) 1 `shouldBe` " 1 " it "lfixed short" $ format (lfixed 10 ' ' int) 123 `shouldBe` "123 " it "lfixed at length" $ format (lfixed 10 ' ' int) 1234567890 `shouldBe` "1234567890" it "lfixed long" $ format (lfixed 10 ' ' int) 123456789012345 `shouldBe` "1234567..." it "rfixed short" $ format (rfixed 10 ' ' int) 123 `shouldBe` " 123" it "rfixed at length" $ format (rfixed 10 ' ' int) 1234567890 `shouldBe` "1234567890" it "rfixed long" $ format (rfixed 10 ' ' int) 123456789012345 `shouldBe` "...9012345" it "cfixed short" $ format (cfixed 4 3 ' ' int) 123 `shouldBe` " 123 " it "cfixed at length" $ format (cfixed 4 3 ' ' int) 1234567890 `shouldBe` "1234567890" it "cfixed long" $ format (cfixed 4 3 ' ' int) 123456789012345 `shouldBe` "1234...345" describe "wrapping combinators" $ do it "prefixed" $ format ("The answer is: " % prefixed "wait for it... " int) 42 `shouldBe` "The answer is: wait for it... 42" it "prefixed, combining" $ format (unlined (indented 4 (prefixed "- " int))) [1, 2, 3] `shouldBe` " - 1\n - 2\n - 3\n" it "suffixed" $ format (suffixed "!!!" int) 7 `shouldBe` "7!!!" it "surrounded" $ format (surrounded "***" string) "glue" `shouldBe` "***glue***" it "enclosed" $ format (enclosed "<!--" "-->" text) "an html comment" `shouldBe` "<!--an html comment-->" it "squoted" $ let obj :: Maybe (Maybe Int); obj = Just Nothing in format ("The object is: " % squoted shown % ".") obj `shouldBe` "The object is: 'Just Nothing'." it "dquoted" $ format ("He said it was based on " % dquoted stext % ".") "science" `shouldBe` "He said it was based on \"science\"." it "parenthesised" $ format (took 5 (list (parenthesised int))) [1..] `shouldBe` "[(1), (2), (3), (4), (5)]" it "squared" $ format (squared int) 7 `shouldBe` "[7]" it "braced" $ format ("\\begin" % braced text) "section" `shouldBe` "\\begin{section}" it "angled" $ format (list (angled text)) ["html", "head", "title", "body", "div", "span"] `shouldBe` "[<html>, <head>, <title>, <body>, <div>, <span>]" it "backticked" $ format ("Be sure to run" %+ backticked builder %+ "as root.") ":(){:|:&};:" `shouldBe` "Be sure to run `:(){:|:&};:` as root." describe "indenters" $ do it "indented" $ format (indented 4 int) 7 `shouldBe` " 7" 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" it "reindented" $ format (reindented 2 text) "one\ntwo\nthree" `shouldBe` " one\n two\n three\n" describe "numerical adapters" $ do it "roundedTo" $ format (list (roundedTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[11, 7, 1, 3]" it "truncatedTo" $ format (list (truncatedTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[10, 6, 1, 3]" it "ceilingedTo" $ format (list (ceilingedTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[11, 7, 1, 4]" it "flooredTo" $ format (list (flooredTo int)) [10.66, 6.66, 1.0, 3.4] `shouldBe` "[10, 6, 1, 3]" describe "structure formatting" $ it "accessed" $ format (accessed fst int) (1, "hello") `shouldBe` "1" -- describe "lens formatters" $ do -- it "viewed" $ flip shouldBe "(viewed _1 int) (1, "hello")" $ format describe "fixed-width numbers" $ do it "binPrefix" $ format (binPrefix 16) 4097 `shouldBe` "0b0001000000000001" it "octPrefix" $ format (octPrefix 16) 4097 `shouldBe` "0o0000000000010001" it "hexPrefix" $ format (hexPrefix 16) 4097 `shouldBe` "0x0000000000001001" describe "mappend with added space (<%+>)" $ do let testTime = (read "2022-06-06") :: Day it "combines formatters, adding a space" $ format (year <%+> month <%+> dayOfMonth) testTime `shouldBe` "2022 06 06" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������