packages feed

formatting 7.0.0 → 7.0.0.1

raw patch · 3 files changed

+515/−2 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Formatting.Internal: instance (a Data.Type.Equality.~ r) => Data.String.IsString (Formatting.Internal.Format r a)
+ Formatting.Internal: instance (a GHC.Types.~ r) => Data.String.IsString (Formatting.Internal.Format r a)

Files

CHANGELOG.md view
@@ -1,3 +1,7 @@+7.0.0.1++* Added README.md to extra-source-files so it shows up on Hackage+ 7.0.0  * Introduced `Formatting.Combinators`.
+ README.md view
@@ -0,0 +1,505 @@+# 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                                  |+| ----------------------------------------:| ------------------------  | ----------------------------------- | -------------------------------------|+| `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`                                      | `"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"`                   | `"<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!"+```
formatting.cabal view
@@ -1,17 +1,21 @@ cabal-version:       2.4 name:                formatting-version:             7.0.0+version:             7.0.0.1 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 <https://github.com/AJChapman/formatting/blob/master/README.md> for more info.+homepage:            https://github.com/AJChapman/formatting/blob/master/README.md 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:           2013 Chris Done, Shachaf Ben-Kiki, Martijn van Steenbergen, Mike Meyer, 2011 MailRank, Inc., 2020 Alex Chapman+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  common deps   build-depends: