packages feed

fmt 0.0.0.4 → 0.1.0.0

raw patch · 6 files changed

+1161/−450 lines, 6 filesdep +criteriondep +deepseqdep +formattingdep ~text-formatPVP ok

version bump matches the API change (PVP)

Dependencies added: criterion, deepseq, formatting, interpolate

Dependency ranges changed: text-format

API changes (from Hackage documentation)

- Fmt: (%<) :: (FromBuilder b) => Builder -> Builder -> b
- Fmt: (%<<) :: (FromBuilder b) => Builder -> Builder -> b
- Fmt: (>%%<) :: (Buildable a, FromBuilder b) => a -> Builder -> b
- Fmt: (>%%<<) :: (Show a, FromBuilder b) => a -> Builder -> b
- Fmt: (>%) :: (Buildable a, FromBuilder b) => a -> Builder -> b
- Fmt: (>>%%<) :: (Buildable a, FromBuilder b) => a -> Builder -> b
- Fmt: (>>%%<<) :: (Show a, FromBuilder b) => a -> Builder -> b
- Fmt: (>>%) :: (Show a, FromBuilder b) => a -> Builder -> b
- Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2) => Fmt.TupleF (a1, a2)
- Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3) => Fmt.TupleF (a1, a2, a3)
- Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4) => Fmt.TupleF (a1, a2, a3, a4)
- Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5) => Fmt.TupleF (a1, a2, a3, a4, a5)
- Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5, Data.Text.Buildable.Buildable a6) => Fmt.TupleF (a1, a2, a3, a4, a5, a6)
- Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5, Data.Text.Buildable.Buildable a6, Data.Text.Buildable.Buildable a7) => Fmt.TupleF (a1, a2, a3, a4, a5, a6, a7)
- Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5, Data.Text.Buildable.Buildable a6, Data.Text.Buildable.Buildable a7, Data.Text.Buildable.Buildable a8) => Fmt.TupleF (a1, a2, a3, a4, a5, a6, a7, a8)
+ Fmt: (#|) :: (FromBuilder b) => Builder -> Builder -> b
+ Fmt: (#||) :: (FromBuilder b) => Builder -> Builder -> b
+ Fmt: (|##|) :: (Buildable a, FromBuilder b) => a -> Builder -> b
+ Fmt: (|##||) :: (Show a, FromBuilder b) => a -> Builder -> b
+ Fmt: (|#) :: (Buildable a, FromBuilder b) => a -> Builder -> b
+ Fmt: (||##|) :: (Buildable a, FromBuilder b) => a -> Builder -> b
+ Fmt: (||##||) :: (Show a, FromBuilder b) => a -> Builder -> b
+ Fmt: (||#) :: (Show a, FromBuilder b) => a -> Builder -> b
+ Fmt: genericF :: (Generic a, GBuildable (Rep a)) => a -> Builder
+ Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2) => Fmt.Internal.TupleF (a1, a2)
+ Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3) => Fmt.Internal.TupleF (a1, a2, a3)
+ Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4) => Fmt.Internal.TupleF (a1, a2, a3, a4)
+ Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5) => Fmt.Internal.TupleF (a1, a2, a3, a4, a5)
+ Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5, Data.Text.Buildable.Buildable a6) => Fmt.Internal.TupleF (a1, a2, a3, a4, a5, a6)
+ Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5, Data.Text.Buildable.Buildable a6, Data.Text.Buildable.Buildable a7) => Fmt.Internal.TupleF (a1, a2, a3, a4, a5, a6, a7)
+ Fmt: instance (Data.Text.Buildable.Buildable a1, Data.Text.Buildable.Buildable a2, Data.Text.Buildable.Buildable a3, Data.Text.Buildable.Buildable a4, Data.Text.Buildable.Buildable a5, Data.Text.Buildable.Buildable a6, Data.Text.Buildable.Buildable a7, Data.Text.Buildable.Buildable a8) => Fmt.Internal.TupleF (a1, a2, a3, a4, a5, a6, a7, a8)
+ Fmt: instance (Fmt.Internal.Buildable' a, Fmt.Internal.Buildable' b) => Fmt.Internal.Buildable' (Data.Either.Either a b)
+ Fmt: instance (Fmt.Internal.Buildable' a1, Fmt.Internal.Buildable' a2) => Fmt.Internal.Buildable' (a1, a2)
+ Fmt: instance (Fmt.Internal.Buildable' a1, Fmt.Internal.Buildable' a2, Fmt.Internal.Buildable' a3) => Fmt.Internal.Buildable' (a1, a2, a3)
+ Fmt: instance (Fmt.Internal.Buildable' a1, Fmt.Internal.Buildable' a2, Fmt.Internal.Buildable' a3, Fmt.Internal.Buildable' a4) => Fmt.Internal.Buildable' (a1, a2, a3, a4)
+ Fmt: instance (Fmt.Internal.Buildable' a1, Fmt.Internal.Buildable' a2, Fmt.Internal.Buildable' a3, Fmt.Internal.Buildable' a4, Fmt.Internal.Buildable' a5) => Fmt.Internal.Buildable' (a1, a2, a3, a4, a5)
+ Fmt: instance (Fmt.Internal.Buildable' a1, Fmt.Internal.Buildable' a2, Fmt.Internal.Buildable' a3, Fmt.Internal.Buildable' a4, Fmt.Internal.Buildable' a5, Fmt.Internal.Buildable' a6) => Fmt.Internal.Buildable' (a1, a2, a3, a4, a5, a6)
+ Fmt: instance (Fmt.Internal.Buildable' a1, Fmt.Internal.Buildable' a2, Fmt.Internal.Buildable' a3, Fmt.Internal.Buildable' a4, Fmt.Internal.Buildable' a5, Fmt.Internal.Buildable' a6, Fmt.Internal.Buildable' a7) => Fmt.Internal.Buildable' (a1, a2, a3, a4, a5, a6, a7)
+ Fmt: instance (Fmt.Internal.Buildable' a1, Fmt.Internal.Buildable' a2, Fmt.Internal.Buildable' a3, Fmt.Internal.Buildable' a4, Fmt.Internal.Buildable' a5, Fmt.Internal.Buildable' a6, Fmt.Internal.Buildable' a7, Fmt.Internal.Buildable' a8) => Fmt.Internal.Buildable' (a1, a2, a3, a4, a5, a6, a7, a8)
+ Fmt: instance (Fmt.Internal.Buildable' k, Fmt.Internal.Buildable' v) => Fmt.Internal.Buildable' (Data.Map.Base.Map k v)
+ Fmt: instance (Fmt.Internal.GBuildable a, Fmt.Internal.GBuildable b) => Fmt.Internal.GBuildable (a GHC.Generics.:+: b)
+ Fmt: instance (Fmt.Internal.GBuildable a, GHC.Generics.Selector c) => Fmt.Internal.GetFields (GHC.Generics.M1 GHC.Generics.S c a)
+ Fmt: instance (Fmt.Internal.GetFields a, Fmt.Internal.GetFields b) => Fmt.Internal.GetFields (a GHC.Generics.:*: b)
+ Fmt: instance (Fmt.Internal.GetFields a, GHC.Generics.Constructor c) => Fmt.Internal.GBuildable (GHC.Generics.M1 GHC.Generics.C c a)
+ Fmt: instance Data.Text.Buildable.Buildable a => Fmt.Internal.Buildable' a
+ Fmt: instance Fmt.Internal.Buildable' ()
+ Fmt: instance Fmt.Internal.Buildable' (a -> b)
+ Fmt: instance Fmt.Internal.Buildable' Data.IntSet.Base.IntSet
+ Fmt: instance Fmt.Internal.Buildable' [GHC.Types.Char]
+ Fmt: instance Fmt.Internal.Buildable' a => Fmt.Internal.Buildable' (Data.List.NonEmpty.NonEmpty a)
+ Fmt: instance Fmt.Internal.Buildable' a => Fmt.Internal.Buildable' (Data.Sequence.Seq a)
+ Fmt: instance Fmt.Internal.Buildable' a => Fmt.Internal.Buildable' (GHC.Base.Maybe a)
+ Fmt: instance Fmt.Internal.Buildable' a => Fmt.Internal.Buildable' [a]
+ Fmt: instance Fmt.Internal.Buildable' c => Fmt.Internal.GBuildable (GHC.Generics.K1 i c)
+ Fmt: instance Fmt.Internal.Buildable' v => Fmt.Internal.Buildable' (Data.IntMap.Base.IntMap v)
+ Fmt: instance Fmt.Internal.Buildable' v => Fmt.Internal.Buildable' (Data.Set.Base.Set v)
+ Fmt: instance Fmt.Internal.GBuildable a => Fmt.Internal.GBuildable (GHC.Generics.M1 GHC.Generics.D d a)
+ Fmt: instance Fmt.Internal.GBuildable a => Fmt.Internal.GetFields (GHC.Generics.M1 GHC.Generics.C c a)
+ Fmt: instance Fmt.Internal.GBuildable a => Fmt.Internal.GetFields (GHC.Generics.M1 GHC.Generics.D c a)
+ Fmt: instance Fmt.Internal.GetFields GHC.Generics.U1
+ Fmt.Internal: build' :: Buildable' a => a -> Builder
+ Fmt.Internal: class Buildable' a
+ Fmt.Internal: class GBuildable f
+ Fmt.Internal: class GetFields f
+ Fmt.Internal: class TupleF a
+ Fmt.Internal: gbuild :: GBuildable f => f a -> Builder
+ Fmt.Internal: getFields :: GetFields f => f a -> [(String, Builder)]
+ Fmt.Internal: tupleF :: TupleF a => a -> Builder
- Fmt: build :: Buildable p => p -> Builder
+ Fmt: build :: p -> Builder
- Fmt: infixr 1 >>%%<
+ Fmt: infixr 1 ||##|

Files

CHANGELOG.md view
@@ -1,3 +1,15 @@+# 0.1.0.0++* Added `genericF` for formatting arbitrary data.++* Changed `%<` and `>%` to `#|` and `|#` because they turn out to be easier to type.++* Added a migration guide from `formatting`.++* Changed output of `eitherF`.++* Added bechmarks.+ # 0.0.0.4  * Added `format` from `text-format`, because in some cases it's nicer than brackets.
+ bench/Main.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}++-- | Benchmarks for @fmt@ library.++module Main where++import           Control.DeepSeq         (NFData)+import           Data.Monoid             ((<>))+import           Data.String.Interpolate (i)+import           Data.Text               (Text)+import qualified Data.Text               as T+import qualified Data.Text.Format        as TF+import           Data.Text.Format.Params as TF+import qualified Data.Text.Lazy          as LT+import           Fmt                     (( #| ), (|#))+import           Formatting              (Format, formatToString, int, sformat, stext,+                                          string, (%))+import           Text.Printf             (printf)++import           Criterion               (Benchmark, bench, bgroup, nf)+import           Criterion.Main          (defaultMain)++----------------------------------------------------------------------------+-- Format utility functions+----------------------------------------------------------------------------++format' :: TF.Params ps => TF.Format -> ps -> Text+format' f = LT.toStrict . TF.format f++formatS :: TF.Params ps => TF.Format -> ps -> String+formatS f = LT.unpack . TF.format f++-- Shorter alias for @formatToString@.+fs :: Format String a -> a+fs = formatToString++tshow :: Show a => a -> Text+tshow x = T.pack (show x)++----------------------------------------------------------------------------+-- Benchmarks utility functions+----------------------------------------------------------------------------++bGenericStringGroup :: NFData s => String -> a -> [(String, a -> s)] -> Benchmark+bGenericStringGroup sTag benchObj =+    bgroup sTag . map (\(tag, howToFmt) -> bench tag (nf howToFmt benchObj))+{-# INLINE bGenericStringGroup #-}++bTextGroup :: a -> [(String, a -> Text)] -> Benchmark+bTextGroup = bGenericStringGroup "text"+{-# INLINE bTextGroup #-}++bStringGroup :: a -> [(String, a -> String)] -> Benchmark+bStringGroup = bGenericStringGroup "string"+{-# INLINE bStringGroup #-}++-- Function for convenience instead of using manual @(,)@.+taggedB :: String -> (a -> b) -> (String, a -> b)+taggedB = (,)++----------------------------------------------------------------------------+-- Benchmakrs themselves+----------------------------------------------------------------------------++main :: IO ()+main = defaultMain+  [ bgroup "simple"+    [ bTextGroup (1 :: Int, 2 :: Int)+      [ taggedB "fmt" $+          \(a,b) -> "hello "#|a|#" world "#|b|#""+      , taggedB "formatting" $+          \(a,b) -> sformat ("hello "%int%" world "%int) a b+      , taggedB "text-format" $+          format' "hello {} world {}"+      , taggedB "interpolate" $+          \(a,b) -> T.pack [i|hello #{a} world #{b}|]+      , taggedB "show" $+          \(a,b) -> "hello " <> tshow a <> " world " <> tshow b+      , taggedB "printf" $+          \(a,b) -> T.pack $ printf "hello %d world %d" a b+      ]+    , bStringGroup (1 :: Int, 2 :: Int)+      [ taggedB "fmt" $+          \(a,b) -> "hello "#|a|#" world "#|b|#""+      , taggedB "formatting" $+          \(a,b) -> fs ("hello "%int%" world "%int) a b+      , taggedB "text-format" $+          formatS "hello {} world {}"+      , taggedB "interpolate" $+          \(a,b) -> [i|hello #{a} world #{b}|]+      , taggedB "show" $+          \(a,b) -> "hello " ++ show a ++ " world " ++ show b+      , taggedB "printf" $+          \(a,b) -> printf "hello %d world %d" a b+      ]+    ]++  , bgroup "readme"+    [ bTextGroup (9 :: Int, "Beijing" :: Text)+      [ taggedB "fmt" $+          \(n,city) -> "There are "#|n|#" million bicycles in "#|city|#"."+      , taggedB "formatting" $+          \(n,city) -> sformat ("There are "%int%" million bicycles in "%stext%".") n city+      , taggedB "text-format" $+          format' "There are {} million bicycles in {}."+      , taggedB "interpolate" $+          \(n,city) -> T.pack [i|There are #{n} million bicycles in #{city}.|]+      , taggedB "show" $+          \(n,city) -> "There are " <> tshow n <> " million bicycles in " <> city <> "."+      , taggedB "printf" $+          \(n,city) -> T.pack $ printf "There are %d million bicycles in %s." n city+      ]+    , bStringGroup (9 :: Int, "Beijing" :: String)+      [ taggedB "fmt" $+          \(n,city) -> "There are "#|n|#" million bicycles in "#|city|#"."+      , taggedB "formatting" $+          \(n,city) -> fs ("There are "%int%" million bicycles in "%string%".") n city+      , taggedB "text-format" $+          formatS "There are {} million bicycles in {}."+      , taggedB "interpolate" $+          \(n,city) -> [i|There are #{n} million bicycles in #{city}.|]+      , taggedB "show" $+          \(n,city) -> "There are " ++ show n ++ " million bicycles in " ++ city ++ "."+      , taggedB "printf" $+          \(n,city) -> printf "There are %d million bicycles in %s." n city+      ]+    ]+  ]
fmt.cabal view
@@ -1,15 +1,49 @@ name:                fmt-version:             0.0.0.4-synopsis:            Nice formatting library+version:             0.1.0.0+synopsis:            A new formatting library description:-  Nice formatting library+  A new formatting library that tries to be simple to understand while still+  being powerful and providing more convenience features than other libraries+  (like functions for pretty-printing maps and lists, or a function for+  printing arbitrary datatypes using generics).+  .+  A comparison with other libraries:+  .+  * @printf@ (from @Text.Printf@) takes a formatting string and uses some+    type tricks to accept the rest of the arguments polyvariadically. It's+    very concise, but there are some drawbacks – it can't produce @Text@+    (you'd have to @T.pack@ it every time) and it doesn't warn you at+    compile-time if you pass wrong arguments or not enough of them.+  .+  * <https://hackage.haskell.org/package/text-format text-format> takes a+    formatting string with angle brackets denoting places where arguments+    would be substituted (the arguments themselves are provided via a+    tuple). If you want to apply formatting to some of the arguments, you+    have to use one of the provided formatters. Like @printf@, it can fail at+    runtime, but at least the formatters are first-class (and you can add new+    ones).+  .+  * <https://hackage.haskell.org/package/formatting formatting> takes a+    formatting template consisting of pieces of strings interleaved with+    formatters; this ensures that arguments always match their placeholders.+    @formatting@ provides lots of formatters and generally seems to be the+    most popular formatting library here. Unfortunately, at least in my+    experience writing new formatters can be awkward and people sometimes+    have troubles understanding how @formatting@ works.+  .+  * <https://hackage.haskell.org/package/fmt fmt> (i.e. this library)+    provides formatters that are ordinary functions, and a bunch of operators+    for concatenating formatted strings; those operators also do automatic+    conversion. There are some convenience formatters which aren't present in+    @formatting@ (like ones for formatting maps, lists, converting to base64,+    etc). Some find the operator syntax annoying, while others like it. homepage:            http://github.com/aelve/fmt bug-reports:         http://github.com/aelve/fmt/issues license:             BSD3 license-file:        LICENSE author:              Artyom maintainer:          yom@artyom.me--- copyright:           +-- copyright: category:            Text tested-with:         GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.1 build-type:          Simple@@ -27,6 +61,7 @@                        base16-bytestring,                        base64-bytestring,                        bytestring,+                       containers,                        microlens >= 0.3,                        text,                        text-format >= 0.3@@ -47,6 +82,26 @@                      , vector   ghc-options:         -Wall -fno-warn-unused-do-bind   hs-source-dirs:      tests+  default-language:    Haskell2010+  if impl(ghc < 7.10)+    buildable: False++benchmark benches+  type:                exitcode-stdio-1.0+  hs-source-dirs:      bench+  main-is:             Main.hs+  build-depends:       base >=4.6 && <5+                     , bytestring+                     , containers+                     , criterion+                     , deepseq+                     , fmt+                     , formatting+                     , interpolate+                     , text+                     , text-format+                     , vector+  ghc-options:         -Wall   default-language:    Haskell2010   if impl(ghc < 7.10)     buildable: False
lib/Fmt.hs view
@@ -18,6 +18,8 @@       <https://github.com/ion1> -} +{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-}@@ -25,6 +27,19 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE CPP #-} +#if __GLASGOW_HASKELL__ < 710+{-# LANGUAGE OverlappingInstances #-}+#  define _OVERLAPPING_+#  define _OVERLAPPABLE_+#  define _OVERLAPS_+#else+#  define _OVERLAPPING_ {-# OVERLAPPING #-}+#  define _OVERLAPPABLE_ {-# OVERLAPPABLE #-}+#  define _OVERLAPS_ {-# OVERLAPS #-}+#endif++{-# OPTIONS_GHC -fno-warn-orphans #-}+ module Fmt (   -- * Overloaded strings@@ -33,20 +48,26 @@   -- * Examples   -- $examples +  -- * Migration guide from @formatting@+  -- $migration+   -- * Basic formatting   -- $brackets    -- ** Ordinary brackets-  (%<),-  (>%),+  -- $god1+  (#|),+  (|#),   -- ** 'Show' brackets-  (%<<),-  (>>%),+  -- $god2+  (#||),+  (||#),   -- ** Combinations-  (>%%<),-  (>>%%<<),-  (>%%<<),-  (>>%%<),+  -- $god3+  (|##|),+  (||##||),+  (|##||),+  (||##|),    -- * Old-style formatting   format,@@ -113,6 +134,9 @@   -- ** Conditional formatting   whenF,   unlessF,++  -- ** Generic formatting+  genericF, ) where @@ -120,6 +144,15 @@ import Data.List import Data.Monoid import Lens.Micro+-- Containers+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Set (Set)+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import Data.IntSet (IntSet)+import qualified Data.IntSet as IntSet+import Data.Sequence (Seq) -- Text import qualified Data.Text.Lazy as TL -- 'Buildable' and text-format@@ -134,11 +167,17 @@ import GHC.Exts (IsList, Item) import qualified GHC.Exts as IsList (toList) #endif+-- Generics+import GHC.Generics  #if __GLASGOW_HASKELL__ < 710 import Data.Foldable (Foldable) #endif +#if MIN_VERSION_base(4,9,0)+import Data.List.NonEmpty (NonEmpty)+#endif+ import Fmt.Internal  @@ -163,12 +202,12 @@ Insert some variables into a string:  >>> let (a, b, n) = ("foo", "bar", 25)->>> ("Here are some words: "%<a>%", "%<b>%"\nAlso a number: "%<n>%"") :: String+>>> ("Here are some words: "#|a|#", "#|b|#"\nAlso a number: "#|n|#"") :: String "Here are some words: foo, bar\nAlso a number: 25"  Print it: ->>> fmtLn ("Here are some words: "%<a>%", "%<b>%"\nAlso a number: "%<n>%"")+>>> fmtLn ("Here are some words: "#|a|#", "#|b|#"\nAlso a number: "#|n|#"") Here are some words: foo, bar Also a number: 25 @@ -176,16 +215,16 @@  >>> let xs = ["John", "Bob"] ->>> fmtLn ("Using show: "%<<xs>>%"\nUsing listF: "%<listF xs>%"")+>>> fmtLn ("Using show: "#||xs||#"\nUsing listF: "#|listF xs|#"") Using show: ["John","Bob"] Using listF: [John, Bob] ->>> fmt ("YAML-like:\n"%<blockListF xs>%"")+>>> fmt ("YAML-like:\n"#|blockListF xs|#"") YAML-like: - John - Bob ->>> fmt ("JSON-like: "%<jsonListF xs>%"")+>>> fmt ("JSON-like: "#|jsonListF xs|#"") JSON-like: [   John , Bob@@ -193,28 +232,91 @@  -} +{- $migration++Instead of using @%@, surround variables with '#|' and '|#'. You don't have+to use @sformat@ or anything else, and also where you were using @build@,+@int@, @text@, etc in @formatting@, you don't have to use anything in @fmt@:++@+formatting    __sformat ("Foo: "%build%", bar: "%int) foo bar__+       fmt    __"Foo: "\#|foo|\#", bar: "\#|bar|\#""__+@++The resulting formatted string is polymorphic and can be used as 'String',+'Text', 'Builder' or even 'IO' (i.e. the string will be printed to the+screen). However, when printing it is recommended to use 'fmt' or 'fmtLn' for+clarity.++@fmt@ provides lots of formatters (which are simply functions that produce+'Builder'):++@+formatting    __sformat ("Got another byte ("%hex%")") x__+       fmt    __"Got another byte ("\#|hexF x|\#")"__+@++Instead of the @shown@ formatter, either just use 'show' or double brackets:++@+formatting    __sformat ("This uses Show: "%shown%") foo__+    fmt #1    __"This uses Show: "\#|show foo|\#""__+    fmt #2    __"This uses Show: "\#||foo||\#""__+@++Many formatters from @formatting@ have the same names in @fmt@, but with+added “F”: 'hexF', 'exptF', etc. Some have been renamed, though:++@+__Cutting:__+  fitLeft  -\> 'prefixF'+  fitRight -\> 'suffixF'++__Padding:__+  left   -\> 'padLeftF'+  right  -\> 'padRightF'+  center -\> 'padBothF'++__Stuff with numbers:__+  ords   -\> 'ordinalF'+  commas -\> 'commaizeF'+@++Also, some formatters from @formatting@ haven't been added to @fmt@+yet. Specifically:++* @plural@ and @asInt@ (but instead of @asInt@ you can use 'fromEnum')+* @prefixBin@, @prefixOrd@, @prefixHex@, and @bytes@+* formatters that use @Scientific@ (@sci@ and @scifmt@)+* formatters that deal with time (anything from @Formatting.Time@)++They will be added later. (On the other hand, @fmt@ provides some useful+formatters not available in @formatting@, such as 'listF', 'mapF', 'tupleF'+and so on.)+-}+ ---------------------------------------------------------------------------- -- Operators with 'Buildable' ----------------------------------------------------------------------------  {- $brackets -To format strings, put variables between ('%<') and ('>%'):+To format strings, put variables between ('#|') and ('|#'):  >>> let name = "Alice"->>> "Meet "%<name>%"!" :: String+>>> "Meet "#|name|#"!" :: String "Meet Alice!"  Of course, 'Text' is supported as well: ->>> "Meet "%<name>%"!" :: Text+>>> "Meet "#|name|#"!" :: Text "Meet Alice!"  You don't actually need any type signatures; however, if you're toying with this library in GHCi, it's recommended to either add a type signature or use 'fmtLn': ->>> fmtLn ("Meet "%<name>%"!")+>>> fmtLn ("Meet "#|name|#"!") Meet Alice!  Otherwise the type of the formatted string would be resolved to @IO ()@ and@@ -224,110 +326,152 @@ @ main = do   [fin, fout] \<- words \<$\> getArgs-  __"Reading data from "%\<fin\>%"\\n"__+  __"Reading data from "\#|fin|\#"\\n"__   xs \<- readFile fin-  __"Writing processed data to "%\<fout\>%"\\n"__+  __"Writing processed data to "\#|fout|\#"\\n"__   writeFile fout (show (process xs)) @  Anyway, let's proceed. Anything 'Buildable', including numbers, booleans,-characters and dates, can be put between ('%<') and ('>%'):+characters and dates, can be put between ('#|') and ('|#'):  >>> let starCount = "173"->>> fmtLn ("Meet "%<name>%"! She's got "%<starCount>%" stars on Github.")+>>> fmtLn ("Meet "#|name|#"! She's got "#|starCount|#" stars on Github.") "Meet Alice! She's got 173 stars on Github." -Since the only thing ('%<') and ('>%') do is concatenate strings and do+Since the only thing ('#|') and ('|#') do is concatenate strings and do conversion, you can use any functions you want inside them. In this case, 'length': ->>> fmtLn (""%<name>%"'s name has "%<length name>%" letters")+>>> fmtLn (""#|name|#"'s name has "#|length name|#" letters") Alice's name has 5 letters  If something isn't 'Buildable', just use 'show' on it:  >>> let pos = (3, 5)->>> fmtLn ("Character's position: "%<show pos>%"")+>>> fmtLn ("Character's position: "#|show pos|#"") Character's position: (3,5)  Or one of many formatters provided by this library – for instance, for tuples of various sizes there's 'tupleF': ->>> fmtLn ("Character's position: "%<tupleF pos>%"")+>>> fmtLn ("Character's position: "#|tupleF pos|#"") Character's position: (3, 5) -Finally, for convenience there's the ('>%%<') operator, which can be used if+Finally, for convenience there's the ('|##|') operator, which can be used if you've got one variable following the other:  >>> let (a, op, b, res) = (2, "*", 2, 4)->>> fmtLn (""%<a>%%<op>%%<b>%" = "%<res>%"")+>>> fmtLn (""#|a|##|op|##|b|#" = "#|res|#"") 2*2 = 4  Also, since in some codebases there are /lots/ of types which aren't-'Buildable', there are operators ('%<<') and ('>>%'), which use 'show'+'Buildable', there are operators ('#||') and ('||#'), which use 'show' instead of 'build':  @-(""%\<show foo\>%%\<show bar\>%"")    ===    (""%\<\<foo\>\>%%\<\<bar\>\>%"")+__(""\#|show foo|\#\#|show bar|\#"")__  ===  __(""\#||foo||\#\#||bar||\#"")__ @ -} -(%<) :: (FromBuilder b) => Builder -> Builder -> b-(%<) str rest = fromBuilder (str <> rest)+-- $god1+-- Operators for the operators god! -(>%) :: (Buildable a, FromBuilder b) => a -> Builder -> b-(>%) a rest = fromBuilder (build a <> rest)+-- | Concatenate, then convert+(#|) :: (FromBuilder b) => Builder -> Builder -> b+(#|) str rest = fromBuilder (str <> rest) -(>%%<) :: (Buildable a, FromBuilder b) => a -> Builder -> b-(>%%<) a rest = fromBuilder (build a <> rest)+-- | 'build' and concatenate, then convert+(|#) :: (Buildable a, FromBuilder b) => a -> Builder -> b+(|#) a rest = fromBuilder (build a <> rest) -infixr 1 %<-infixr 1 >%-infixr 1 >%%<+infixr 1 #|+infixr 1 |#  ---------------------------------------------------------------------------- -- Operators with 'Show' ---------------------------------------------------------------------------- -(%<<) :: (FromBuilder b) => Builder -> Builder -> b-(%<<) str rest = str %< rest-{-# INLINE (%<<) #-}+-- $god2+-- More operators for the operators god! -(>>%) :: (Show a, FromBuilder b) => a -> Builder -> b-(>>%) a rest = show a >% rest-{-# INLINE (>>%) #-}+-- | Concatenate, then convert+(#||) :: (FromBuilder b) => Builder -> Builder -> b+(#||) str rest = str #| rest+{-# INLINE (#||) #-} -(>>%%<<) :: (Show a, FromBuilder b) => a -> Builder -> b-(>>%%<<) a rest = show a >% rest-{-# INLINE (>>%%<<) #-}+-- | 'show' and concatenate, then convert+(||#) :: (Show a, FromBuilder b) => a -> Builder -> b+(||#) a rest = show a |# rest+{-# INLINE (||#) #-} -infixr 1 %<<-infixr 1 >>%-infixr 1 >>%%<<+infixr 1 #||+infixr 1 ||#  ---------------------------------------------------------------------------- -- Combinations ---------------------------------------------------------------------------- -(>>%%<) :: (Buildable a, FromBuilder b) => a -> Builder -> b-(>>%%<) a rest = a >%%< rest-{-# INLINE (>>%%<) #-}+{- $god3 -(>%%<<) :: (Show a, FromBuilder b) => a -> Builder -> b-(>%%<<) a rest = a >>%%<< rest-{-# INLINE (>%%<<) #-}+Z̸͠A̵̕͟͠L̡̀́͠G̶̛O͝ ̴͏̀ I͞S̸̸̢͠  ̢̛͘͢C̷͟͡Ó̧̨̧͞M̡͘͟͞I̷͜N̷̕G̷̀̕ -infixr 1 >>%%<-infixr 1 >%%<<+(Though you can just use @""@ between @\#| |\#@ instead of using these+operators, and Show-brackets don't have to be used at all because there's+'show' available.)+-} +(|##|) :: (Buildable a, FromBuilder b) => a -> Builder -> b+(|##|) a rest = fromBuilder (build a <> rest)+{-# INLINE (|##|) #-}++(||##||) :: (Show a, FromBuilder b) => a -> Builder -> b+(||##||) a rest = show a |# rest+{-# INLINE (||##||) #-}++(||##|) :: (Buildable a, FromBuilder b) => a -> Builder -> b+(||##|) a rest = a |##| rest+{-# INLINE (||##|) #-}++(|##||) :: (Show a, FromBuilder b) => a -> Builder -> b+(|##||) a rest = a ||##|| rest+{-# INLINE (|##||) #-}++infixr 1 |##|+infixr 1 ||##||+infixr 1 ||##|+infixr 1 |##||+ ---------------------------------------------------------------------------- -- Old-style formatting ---------------------------------------------------------------------------- +{- | An old-style formatting function taken from @text-format@ (see+"Data.Text.Format"). Unlike 'Data.Text.Format.format' from+"Data.Text.Format", it can produce 'String' and strict 'Text' as well (and+print to console too).++To provide substitution arguments, use a tuple:++>>> format "{} + {} = {}" (2, 2, 4)+"2 + 2 = 4"++You can use arbitrary formatters:++>>> format "0x{} + 0x{} = 0x{}" (hexF 130, hexF 270, hexF (130+270))+"2 + 2 = 4"++To provide just one argument, use a list instead of a tuple:++>>> format "Hello {}!" ["world"]+"Hello world!"+-} format :: (FromBuilder b, TF.Params ps) => TF.Format -> ps -> b format f ps = fromBuilder (TF.build f ps) {-# INLINE format #-} +{- | Like 'format', but adds a newline.+-} formatLn :: (FromBuilder b, TF.Params ps) => TF.Format -> ps -> b formatLn f ps = fromBuilder (TF.build f ps <> "\n") {-# INLINE formatLn #-}@@ -338,8 +482,8 @@  {- | 'fmt' converts things to 'String', 'Text' or 'Builder'. -Most of the time you won't need it, as strings produced with ('%<') and-('>%') can already be used as 'String', 'Text', etc. However, combinators+Most of the time you won't need it, as strings produced with ('#|') and+('|#') can already be used as 'String', 'Text', etc. However, combinators like 'listF' can only produce 'Builder' (for better type inference), and you need to use 'fmt' on them. @@ -436,7 +580,7 @@ It automatically handles multiline list elements:  @->>> fmt $ blockListF ["hello\nworld", "foo\nbar\nquix"]+>>> __fmt $ blockListF ["hello\\nworld", "foo\\nbar\\nquix"]__ - hello   world @@ -650,27 +794,6 @@ -- Tuple formatters ---------------------------------------------------------------------------- -class TupleF a where-  {- |-Format a tuple (of up to 8 elements):-->>> tupleF (1,2,"hi")-"(1, 2, hi)"--If any of the elements takes several lines, an alternate format is used:--@->>> fmt $ tupleF ("test","foo\nbar","more test")-( test-,-  foo-  bar-,-  more test )-@-  -}-  tupleF :: a -> Builder- instance (Buildable a1, Buildable a2)   => TupleF (a1, a2) where   tupleF (a1, a2) = tupleLikeF@@ -748,8 +871,8 @@ -- ADT formatters ---------------------------------------------------------------------------- -{- |-Like 'build' for 'Maybe', but displays 'Nothing' as @<Nothing>@ instead of an empty string.+{- | Like 'build' for 'Maybe', but displays 'Nothing' as @\<Nothing\>@ instead+of an empty string.  'build': @@ -772,10 +895,11 @@ Format an 'Either':  >>> eitherF (Right 1)-"<Right>: 1"+"<Right: 1>" -} eitherF :: (Buildable a, Buildable b) => Either a b -> Builder-eitherF = either (\x -> "<Left>: " <> build x) (\x -> "<Right>: " <> build x)+eitherF = either (\x -> "<Left: " <> build x <> ">")+                 (\x -> "<Right: " <> build x <> ">")  ---------------------------------------------------------------------------- -- Other formatters@@ -940,13 +1064,13 @@ For small numbers, it uses scientific notation for everything smaller than 1e-6: -> listF' (precF 3) [1e-5,1e-6,1e-7]+>>> listF' (precF 3) [1e-5,1e-6,1e-7] "[0.0000100, 0.00000100, 1.00e-7]"  For large numbers, it uses scientific notation for everything larger than 1eN, where N is the precision: -> listF' (precF 4) [1e3,5e3,1e4]+>>> listF' (precF 4) [1e3,5e3,1e4] "[1000, 5000, 1.000e4]" -} precF :: Real a => Int -> a -> Builder@@ -969,14 +1093,14 @@ Display something only if the condition is 'True' (empty string otherwise).  @->>> "Hello!" <> whenF showDetails (", details: "%<foobar>%"")+>>> __"Hello!" <> whenF showDetails (", details: "\#|foobar|\#"")__ @  Note that it can only take a 'Builder' (because otherwise it would be-unusable with ('%<')-formatted strings which can resolve to any 'FromBuilder'). Thus, use 'fmt' if you need just one value:+unusable with ('#|')-formatted strings which can resolve to any 'FromBuilder'). Thus, use 'fmt' if you need just one value:  @->>> "Maybe here's a number: "%<whenF cond (fmt n)>%""+>>> __"Maybe here's a number: "\#|whenF cond (fmt n)|\#""__ @ -} whenF :: Bool -> Builder -> Builder@@ -1015,61 +1139,217 @@     spaces = TL.replicate (fromIntegral n) (TL.singleton ' ')  ------------------------------------------------------------------------------- TODOs+-- Generic formatting ---------------------------------------------------------------------------- -{- add these:+{- | Format an arbitrary value without requiring a 'Buildable' instance: -* something that would cut a string by adding ellipsis to the center-* 'time' that would use hackage.haskell.org/package/time/docs/Data-Time-Format.html#t:FormatTime-* something that would show time and date in a standard way-* something to format a floating-point number without any scientific notation+@+data Foo = Foo { x :: Bool, y :: [Int] }+  deriving Generic+@++>>> fmt (genericF (Foo True [1,2,3]))+Foo:+  x: True+  y: [1, 2, 3]++It works for non-record constructors too:++@+data Bar = Bar Bool [Int]+  deriving Generic+@++>>> fmtLn (genericF (Bar True [1,2,3]))+<Bar: True, [1, 2, 3]>++Any fields inside the type must either be 'Buildable' or one of the following+types:++* a function+* a tuple (up to 8-tuples)+* list, 'NonEmpty', 'Seq'+* 'Map', 'IntMap', 'Set', 'IntSet'+* 'Maybe', 'Either'++The exact format of 'genericF' might change in future versions, so don't rely+on it. It's merely a convenience function. -}+genericF :: (Generic a, GBuildable (Rep a)) => a -> Builder+genericF = gbuild . from -{- list/map:+instance GBuildable a => GBuildable (M1 D d a) where+  gbuild (M1 x) = gbuild x +instance (GetFields a, Constructor c) => GBuildable (M1 C c a) where+  -- A note on fixity:+  --   * Ordinarily e.g. "Foo" is prefix and e.g. ":|" is infix+  --   * However, "Foo" can be infix when defined as "a `Foo` b"+  --   * And ":|" can be prefix when defined as "(:|) a b"+  gbuild c@(M1 x) = case conFixity c of+    Infix _ _+      | [a, b] <- fields -> format "({} {} {})" (a, infixName, b)+      -- this case should never happen, but still+      | otherwise        -> format "<{}: {}>"+                              ( prefixName+                              , mconcat (intersperse ", " fields) )+    Prefix+      | isTuple -> tupleLikeF fields+      | conIsRecord c -> nameF (build prefixName) (blockMapF fieldsWithNames)+      | null (getFields x) -> build prefixName+      -- I believe that there will be only one field in this case+      | null (conName c) -> mconcat (intersperse ", " fields)+      | otherwise -> format "<{}: {}>"+                       ( prefixName+                       , mconcat (intersperse ", " fields) )+    where+      (prefixName, infixName)+        | ":" `isPrefixOf` conName c = ("(" ++ conName c ++ ")", conName c)+        | otherwise                  = (conName c, "`" ++ conName c ++ "`")+      fields          = map snd (getFields x)+      fieldsWithNames = getFields x+      isTuple         = "(," `isPrefixOf` prefixName++instance Buildable' c => GBuildable (K1 i c) where+  gbuild (K1 a) = build' a++instance (GBuildable a, GBuildable b) => GBuildable (a :+: b) where+  gbuild (L1 x) = gbuild x+  gbuild (R1 x) = gbuild x++instance (GetFields a, GetFields b) => GetFields (a :*: b) where+  getFields (a :*: b) = getFields a ++ getFields b++instance (GBuildable a, Selector c) => GetFields (M1 S c a) where+  getFields s@(M1 a) = [(selName s, gbuild a)]++instance GBuildable a => GetFields (M1 D c a) where+  getFields (M1 a) = [("", gbuild a)]++instance GBuildable a => GetFields (M1 C c a) where+  getFields (M1 a) = [("", gbuild a)]++instance GetFields U1 where+  getFields _ = []++----------------------------------------------------------------------------+-- A more powerful Buildable used for genericF+----------------------------------------------------------------------------++instance Buildable' () where+  build' _ = "()"++instance (Buildable' a1, Buildable' a2)+  => Buildable' (a1, a2) where+  build' (a1, a2) = tupleLikeF+    [build' a1, build' a2]++instance (Buildable' a1, Buildable' a2, Buildable' a3)+  => Buildable' (a1, a2, a3) where+  build' (a1, a2, a3) = tupleLikeF+    [build' a1, build' a2, build' a3]++instance (Buildable' a1, Buildable' a2, Buildable' a3, Buildable' a4)+  => Buildable' (a1, a2, a3, a4) where+  build' (a1, a2, a3, a4) = tupleLikeF+    [build' a1, build' a2, build' a3, build' a4]++instance (Buildable' a1, Buildable' a2, Buildable' a3, Buildable' a4,+          Buildable' a5)+  => Buildable' (a1, a2, a3, a4, a5) where+  build' (a1, a2, a3, a4, a5) = tupleLikeF+    [build' a1, build' a2, build' a3, build' a4,+     build' a5]++instance (Buildable' a1, Buildable' a2, Buildable' a3, Buildable' a4,+          Buildable' a5, Buildable' a6)+  => Buildable' (a1, a2, a3, a4, a5, a6) where+  build' (a1, a2, a3, a4, a5, a6) = tupleLikeF+    [build' a1, build' a2, build' a3, build' a4,+     build' a5, build' a6]++instance (Buildable' a1, Buildable' a2, Buildable' a3, Buildable' a4,+          Buildable' a5, Buildable' a6, Buildable' a7)+  => Buildable' (a1, a2, a3, a4, a5, a6, a7) where+  build' (a1, a2, a3, a4, a5, a6, a7) = tupleLikeF+    [build' a1, build' a2, build' a3, build' a4,+     build' a5, build' a6, build' a7]++instance (Buildable' a1, Buildable' a2, Buildable' a3, Buildable' a4,+          Buildable' a5, Buildable' a6, Buildable' a7, Buildable' a8)+  => Buildable' (a1, a2, a3, a4, a5, a6, a7, a8) where+  build' (a1, a2, a3, a4, a5, a6, a7, a8) = tupleLikeF+    [build' a1, build' a2, build' a3, build' a4,+     build' a5, build' a6, build' a7, build' a8]++instance _OVERLAPPING_ Buildable' [Char] where+  build' = build++instance Buildable' a => Buildable' [a] where+  build' = listF' build'++#if MIN_VERSION_base(4,9,0)+instance Buildable' a => Buildable' (NonEmpty a) where+  build' = listF' build'+#endif++instance Buildable' a => Buildable' (Seq a) where+  build' = listF' build'++instance (Buildable' k, Buildable' v) => Buildable' (Map k v) where+  build' = mapF' build' build' . Map.toList++instance (Buildable' v) => Buildable' (Set v) where+  build' = listF' build'++instance (Buildable' v) => Buildable' (IntMap v) where+  build' = mapF' build' build' . IntMap.toList++instance Buildable' IntSet where+  build' = listF' build' . IntSet.toList++instance (Buildable' a) => Buildable' (Maybe a) where+  build' Nothing  = maybeF (Nothing         :: Maybe Builder)+  build' (Just a) = maybeF (Just (build' a) :: Maybe Builder)++instance (Buildable' a, Buildable' b) => Buildable' (Either a b) where+  build' (Left  a) = eitherF (Left  (build' a) :: Either Builder Builder)+  build' (Right a) = eitherF (Right (build' a) :: Either Builder Builder)++instance Buildable' (a -> b) where+  build' _ = "<function>"++instance _OVERLAPPABLE_ Buildable a => Buildable' a where+  build' = build++----------------------------------------------------------------------------+-- TODOs+----------------------------------------------------------------------------++{- list/map+~~~~~~~~~~~~~~~~~~~~ * maybe add something like blockMapF_ and blockListF_ that would add   a blank line automatically? or `---` and `:::` or something? * should also add something to _not_ add a blank line between list   entries (e.g. when they are 'name'd and can be clearly differentiated)-* should also add something that would truncate lists in the middle-  (and maybe not in the middle as well)-* the problem is that the user might want to combine them so I guess-  we can't make a separate combinator for each -}  {- docs-+~~~~~~~~~~~~~~~~~~~~ * write explicitly that 'build' can be used and is useful sometimes-* provide a formatting→fmt transition table * mention that fmt doesn't do the neat thing that formatting does with (<>)   (or maybe it does? there's a monoid instance for functions after all,   though I might also have to write a IsString instance for (a -> Builder))-* write that if %< >% are hated or if it's inconvenient in some cases,+* write that if #| |# are hated or if it's inconvenient in some cases,   you can just use provided formatters and <> (add Fmt.DIY for that?)   (e.g. "pub:" <> base16F foo)-* write that it can be used in parallel with formatting?-* mention printf in cabal description so that it would be findable-* clarify philosophy (“take a free spot in design space; write the-  best possible library around it, not just a proof of concept”)+* write that it can be used in parallel with formatting? can it, actually? * clarify what exactly is hard about writing `formatting` formatters -}  {- others--* something to format a record nicely (with generics, probably)-* something like https://hackage.haskell.org/package/groom-* something for wrapping lists (not indenting, just hard-wrapping)-* reexport (<>)? don't know whether to use Semigroup or Monoid, though-* colors?-* should it be called 'listBlock' or 'blockList'?-* add NL or _NL for newline? or (<\>) or (<>\)? and also (>%\)?-* have to decide on whether it would be >%< or >%%< or maybe >|<-* actually, what about |< and >|? also <% and %> are good+~~~~~~~~~~~~~~~~~~~~ * what effect does it have on compilation time? what effect do   other formatting libraries have on compilation time?-* use 4 spaces instead of 2?-* change tuples to correspond to jsonList-* be consistent about newlines after tuples/maps/lists-* find some way to use IO inside %<>% brackets -}
lib/Fmt/Internal.hs view
@@ -18,7 +18,13 @@   FromBuilder(..),   FormatAsHex(..),   FormatAsBase64(..),+  TupleF(..), +  -- * Classes used for 'genericF'+  GBuildable(..),+  GetFields(..),+  Buildable'(..),+   -- * Helpers   groupInt,   atBase,@@ -59,6 +65,7 @@ ----------------------------------------------------------------------------  class FromBuilder a where+  -- | Convert a 'Builder' to something else.   fromBuilder :: Builder -> a  instance FromBuilder Builder where@@ -91,6 +98,8 @@  >>> hexF 3635 "e33"+>>> hexF ("\0\50\63\80" :: BS.ByteString)+"00323f50"   -}   hexF :: a -> Builder @@ -138,6 +147,47 @@   base64UrlF = fromLazyText . TL.decodeLatin1 . B64UL.encode  ----------------------------------------------------------------------------+-- Tuples+----------------------------------------------------------------------------++class TupleF a where+  {- |+Format a tuple (of up to 8 elements):++>>> tupleF (1,2,"hi")+"(1, 2, hi)"++If any of the elements takes several lines, an alternate format is used:++@+>>> __fmt $ tupleF ("test","foo\\nbar","more test")__+( test+,+  foo+  bar+,+  more test )+@+  -}+  tupleF :: a -> Builder++----------------------------------------------------------------------------+-- Classes used for 'genericF'+----------------------------------------------------------------------------++class GBuildable f where+  gbuild :: f a -> Builder++class GetFields f where+  -- | Get fields, together with their names if available+  getFields :: f a -> [(String, Builder)]++-- | A more powerful 'Buildable' used for 'genericF'. Can build functions,+-- tuples, lists, maps, etc., as well as combinations thereof.+class Buildable' a where+  build' :: a -> Builder++---------------------------------------------------------------------------- -- Helpers ---------------------------------------------------------------------------- @@ -177,7 +227,10 @@   | i >= 10 && i < 36 = chr (ord 'a' + i - 10)   | otherwise = error ("intToDigit': Invalid int " ++ show i) --- assumes that the prefix doesn't contain newlines+{- | Add a prefix to the first line, and indent all lines but the first one.++The output will always end with a newline, even when the input doesn't.+-} indent' :: Int -> T.Text -> Builder -> Builder indent' n pref a = case TL.lines (toLazyText a) of   []     -> fromText pref <> "\n"
tests/Main.hs view
@@ -1,7 +1,6 @@-{-# LANGUAGE-OverloadedStrings,-QuasiQuotes-  #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE DeriveGeneric #-}   module Main where@@ -21,6 +20,8 @@ import Data.Map (Map) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL+-- Generics+import GHC.Generics -- Tests import Test.Hspec @@ -28,81 +29,246 @@ import Fmt  +----------------------------------------------------------------------------+-- Constants for testing (to avoid “ambiguous type” errors)+----------------------------------------------------------------------------++n :: Integer+n = 25++s :: String+s = "!"++----------------------------------------------------------------------------+-- List of tests+----------------------------------------------------------------------------+ main :: IO () main = hspec $ do-  let n = 25 :: Integer-      s = "!" :: String+  test_operators+  test_outputTypes+  describe "formatters" $ do+    test_indent+    test_baseConversion+    test_floatingPoint+    test_hex+    test_ADTs+    test_conditionals+    test_padding+    test_lists+    test_maps+    test_tuples+    test_generic +----------------------------------------------------------------------------+-- Testing that operators work (when used as intended)+----------------------------------------------------------------------------++test_operators :: Spec+test_operators = do   it "simple examples" $ do-    ("a"%<n>%"b") ==%> "a25b"-    ("a"%<n>%"b"%<s>%"") ==%> "a25b!"-    (""%<n>%%<s>%"") ==%> "25!"-    (""%<negate n>%%<s>%"") ==%> "-25!"-    (""%<Just n>%%<s>%"") ==%> "25!"+    ("a"#|n|#"b") ==%> "a25b"+    ("a"#|n|#"b"#|s|#"") ==%> "a25b!"+    (""#|n|##|s|#"") ==%> "25!"+    (""#|negate n|##|s|#"") ==%> "-25!"+    (""#|Just n|##|s|#"") ==%> "25!"    describe "examples with Show/mixed" $ do     it "copy of Buildable examples" $ do-      ("a"%<<n>>%"b") ==%> "a25b"-      ("a"%<<n>>%%<<n>>%"b") ==%> "a2525b"+      ("a"#||n||#"b") ==%> "a25b"+      ("a"#||n||##||n||#"b") ==%> "a2525b"       -- These are mixed, i.e. both Buildable and Show versions are used-      ("a"%<<n>>%"b"%<s>%"") ==%> "a25b!"-      (""%<<n>>%%<s>%"") ==%> "25!"-      (""%<<negate n>>%%<s>%"") ==%> "-25!"+      ("a"#||n||#"b"#|s|#"") ==%> "a25b!"+      (""#||n||##|s|#"") ==%> "25!"+      (""#||negate n||##|s|#"") ==%> "-25!"     it "examples that don't work with Buildable" $ do-      (""%<<Just n>>%"") ==%> "Just 25"-      (""%<<(n,n)>>%"") ==%> "(25,25)"+      (""#||Just n||#"") ==%> "Just 25"+      (""#||(n,n)||#"") ==%> "(25,25)"    it "plays nice with other operators" $ do     -- If precedence is bad these won't compile-    (""%<n-1>%%<n+1>%"") ==%> "2426"-    (id $ ""%<n-1>%%<n+1>%"") ==%> "2426"+    (""#|n-1|##|n+1|#"") ==%> "2426"+    (id $ ""#|n-1|##|n+1|#"") ==%> "2426"    it "works with <>" $ do-    ("number: "%<n>%"\n"<>-     "string: "%<s>%"") ==%> "number: 25\nstring: !"+    ("number: "#|n|#"\n"<>+     "string: "#|s|#"") ==%> "number: 25\nstring: !" -  describe "output as" $ do-    it "String" $-      ("a"%<n>%"b" :: String) `shouldBe` "a25b"-    it "Text" $-      ("a"%<n>%"b" :: Text) `shouldBe` "a25b"-    it "Lazy Text" $-      ("a"%<n>%"b" :: TL.Text) `shouldBe` "a25b"-    it "Builder" $-      ("a"%<n>%"b" :: Builder) `shouldBe` "a25b"+----------------------------------------------------------------------------+-- Testing that different output types work+---------------------------------------------------------------------------- +test_outputTypes :: Spec+test_outputTypes = describe "output as" $ do+  it "String" $+    ("a"#|n|#"b" :: String) `shouldBe` "a25b"+  it "Text" $+    ("a"#|n|#"b" :: Text) `shouldBe` "a25b"+  it "Lazy Text" $+    ("a"#|n|#"b" :: TL.Text) `shouldBe` "a25b"+  it "Builder" $+    ("a"#|n|#"b" :: Builder) `shouldBe` "a25b" -  describe "formatters" $ do-    describe "'indent'" $ do-      it "simple examples" $ do-        indent 0 "hi" ==#> "hi\n"-        indent 0 "\nhi\n\n" ==#> "\nhi\n\n"-        indent 2 "hi" ==#> "  hi\n"-        indent 2 "hi\n" ==#> "  hi\n"-        indent 2 "" ==#> "  \n"-        indent 2 "hi\nbye" ==#> "  hi\n  bye\n"-        indent 2 "hi\nbye\n" ==#> "  hi\n  bye\n"-      it "formatting a block" $ do-        ("Some numbers:\n"<>-         indent 2 (-           "odd: "%<n>%"\n"<>-           "even: "%<n+1>%"")) ==#> "Some numbers:\n  odd: 25\n  even: 26\n"+----------------------------------------------------------------------------+-- Tests for various simple formatters+---------------------------------------------------------------------------- -    describe "'listF'" $ do-      it "simple examples" $ do-        listF ([] :: [Int]) ==#> "[]"-        listF [n] ==#> "[25]"-        listF [n,n+1] ==#> "[25, 26]"-        listF [s,s<>s,"",s<>s<>s] ==#> "[!, !!, , !!!]"-      it "different Foldables" $ do-        listF ([1,2,3] :: [Int]) ==#> "[1, 2, 3]"-        listF (V.fromList [1,2,3] :: Vector Int) ==#> "[1, 2, 3]"-        listF (M.fromList [(1,2),(3,4),(5,6)] :: Map Int Int) ==#> "[2, 4, 6]"+test_indent :: Spec+test_indent = describe "'indent'" $ do+  it "simple examples" $ do+    indent 0 "hi" ==#> "hi\n"+    indent 0 "\nhi\n\n" ==#> "\nhi\n\n"+    indent 2 "hi" ==#> "  hi\n"+    indent 2 "hi\n" ==#> "  hi\n"+    indent 2 "" ==#> "  \n"+    indent 2 "hi\nbye" ==#> "  hi\n  bye\n"+    indent 2 "hi\nbye\n" ==#> "  hi\n  bye\n"+  it "formatting a block" $ do+    ("Some numbers:\n"<>+     indent 2 (+       "odd: "#|n|#"\n"<>+       "even: "#|n+1|#"")) ==#> "Some numbers:\n  odd: 25\n  even: 26\n" -    describe "'blockListF'" $ do-      it "empty list" $ do+test_baseConversion :: Spec+test_baseConversion = describe "conversion to bases" $ do+  it "octF" $ do+    octF n ==#> "31"+  it "binF" $ do+    binF n ==#> "11001"+  it "baseF" $ do+    baseF 36 (n^n) ==#> "54kbbzw21jhueg5jb0ggr4p"+  it "-baseF" $ do+    baseF 36 (-(n^n)) ==#> "-54kbbzw21jhueg5jb0ggr4p"++test_floatingPoint :: Spec+test_floatingPoint = describe "floating-point" $ do+  let f1_3 = 1.2999999999999998 :: Double+  it "floatF" $ do+    floatF f1_3 ==#> "1.2999999999999998"+  it "exptF" $ do+    exptF 2 f1_3 ==#> "1.30e0"+  it "fixedF" $ do+    fixedF 2 f1_3 ==#> "1.30"+  it "precF" $ do+    precF 2 f1_3 ==#> "1.3"++test_hex :: Spec+test_hex = describe "'hexF'" $ do+  it "Int" $ do+    hexF n ==#> "19"+  it "-Int" $ do+    hexF (-n) ==#> "-19"+  it "strict ByteString" $ do+    hexF (BS.pack [15,250]) ==#> "0ffa"+  it "lazy ByteString" $ do+    hexF (BSL.pack [15,250]) ==#> "0ffa"++test_ADTs :: Spec+test_ADTs = describe "ADTs" $ do+  it "maybeF" $ do+    maybeF (Nothing :: Maybe Int) ==#> "<Nothing>"+    maybeF (Just 3 :: Maybe Int) ==#> "3"+  it "eitherF" $ do+    eitherF (Left 1 :: Either Int Int) ==#> "<Left: 1>"+    eitherF (Right 1 :: Either Int Int) ==#> "<Right: 1>"++test_conditionals :: Spec+test_conditionals = describe "conditionals" $ do+  it "whenF" $ do+    whenF True "hi" ==#> "hi"+    whenF False "hi" ==#> ""+  it "unlessF" $ do+    unlessF True "hi" ==#> ""+    unlessF False "hi" ==#> "hi"++----------------------------------------------------------------------------+-- Tests for padding+----------------------------------------------------------------------------++test_padding :: Spec+test_padding = describe "padding" $ do+  it "prefixF" $ do+    prefixF (-1) ("hello" :: Text) ==#> ""+    prefixF   0  ("hello" :: Text) ==#> ""+    prefixF   1  ("hello" :: Text) ==#> "h"+    prefixF   2  ("hello" :: Text) ==#> "he"+    prefixF   3  ("hello" :: Text) ==#> "hel"+    prefixF   5  ("hello" :: Text) ==#> "hello"+    prefixF 1000 ("hello" :: Text) ==#> "hello"+    prefixF 1000 (""      :: Text) ==#> ""+  it "suffixF" $ do+    suffixF (-1) ("hello" :: Text) ==#> ""+    suffixF   0  ("hello" :: Text) ==#> ""+    suffixF   1  ("hello" :: Text) ==#> "o"+    suffixF   2  ("hello" :: Text) ==#> "lo"+    suffixF   3  ("hello" :: Text) ==#> "llo"+    suffixF   5  ("hello" :: Text) ==#> "hello"+    suffixF 1000 ("hello" :: Text) ==#> "hello"+    suffixF 1000 (""      :: Text) ==#> ""+  it "padLeftF" $ do+    padLeftF (-1) '!' ("hello" :: Text) ==#> "hello"+    padLeftF   0  '!' ("hello" :: Text) ==#> "hello"+    padLeftF   1  '!' ("hello" :: Text) ==#> "hello"+    padLeftF   5  '!' ("hello" :: Text) ==#> "hello"+    padLeftF   6  '!' ("hello" :: Text) ==#> "!hello"+    padLeftF   7  '!' ("hello" :: Text) ==#> "!!hello"+    padLeftF   7  '!' (""      :: Text) ==#> "!!!!!!!"+  it "padRightF" $ do+    padRightF (-1) '!' ("hello" :: Text) ==#> "hello"+    padRightF   0  '!' ("hello" :: Text) ==#> "hello"+    padRightF   1  '!' ("hello" :: Text) ==#> "hello"+    padRightF   5  '!' ("hello" :: Text) ==#> "hello"+    padRightF   6  '!' ("hello" :: Text) ==#> "hello!"+    padRightF   7  '!' ("hello" :: Text) ==#> "hello!!"+    padRightF   7  '!' (""      :: Text) ==#> "!!!!!!!"+  it "padBothF" $ do+    padBothF (-1) '!' ("hello" :: Text) ==#> "hello"+    padBothF   0  '!' ("hello" :: Text) ==#> "hello"+    padBothF   1  '!' ("hello" :: Text) ==#> "hello"+    padBothF   5  '!' ("hello" :: Text) ==#> "hello"+    padBothF   6  '!' ("hello" :: Text) ==#> "!hello"+    padBothF   7  '!' ("hello" :: Text) ==#> "!hello!"+    padBothF   7  '!' ("hell"  :: Text) ==#> "!!hell!"+    padBothF   7  '!' ("hel"   :: Text) ==#> "!!hel!!"+    padBothF   8  '!' ("hell"  :: Text) ==#> "!!hell!!"+    padBothF   8  '!' ("hel"   :: Text) ==#> "!!!hel!!"+    padBothF   8  '!' (""      :: Text) ==#> "!!!!!!!!"++----------------------------------------------------------------------------+-- Tests for lists+----------------------------------------------------------------------------++test_lists :: Spec+test_lists = describe "lists" $ do+  test_listF+  test_blockListF+  test_jsonListF++test_listF :: Spec+test_listF = describe "'listF'" $ do+  it "simple examples" $ do+    listF ([] :: [Int]) ==#> "[]"+    listF [n] ==#> "[25]"+    listF [n,n+1] ==#> "[25, 26]"+    listF [s,s<>s,"",s<>s<>s] ==#> "[!, !!, , !!!]"+  it "different Foldables" $ do+    listF ([1,2,3] :: [Int]) ==#> "[1, 2, 3]"+    listF (V.fromList [1,2,3] :: Vector Int) ==#> "[1, 2, 3]"+    listF (M.fromList [(1,2),(3,4),(5,6)] :: Map Int Int) ==#> "[2, 4, 6]"++test_blockListF :: Spec+test_blockListF = describe "'blockListF'" $ do+  emptyList+  nullElements+  singleLineElements+  multiLineElements+  mixed++  where+    emptyList = it "empty list" $ do         blockListF ([] :: [Int]) ==#> "[]\n"-      it "null elements" $ do++    nullElements = it "null elements" $ do         blockListF ([""] :: [Text]) ==#> [text|           -           |]@@ -115,7 +281,8 @@           - a           -           |]-      it "single-line elements" $ do++    singleLineElements = it "single-line elements" $ do         blockListF (["a"] :: [Text]) ==#> [text|           - a           |]@@ -128,7 +295,8 @@           - b           - ccc           |]-      it "multi-line elements" $ do++    multiLineElements = it "multi-line elements" $ do         blockListF (["a\nx"] :: [Text]) ==#> [text|           - a             x@@ -148,7 +316,8 @@           - c           __           |]-      it "mix of single-line and multi-line" $ do++    mixed = it "mix of single-line and multi-line" $ do         blockListF (["a\nx","b"] :: [Text]) ==#> [text|           - a             x@@ -171,10 +340,20 @@           - c           __           |]-    describe "'jsonListF'" $ do-      it "empty list" $ do++test_jsonListF :: Spec+test_jsonListF = describe "'jsonListF'" $ do+  emptyList+  nullElements+  singleLineElements+  multiLineElements+  mixed++  where+    emptyList = it "empty list" $ do         jsonListF ([] :: [Int]) ==#> "[]\n"-      it "null elements" $ do++    nullElements = it "null elements" $ do         jsonListF ([""] :: [Text]) ==#> [text|           [ @@ -193,7 +372,8 @@           ,           ]           |]-      it "single-line elements" $ do++    singleLineElements = it "single-line elements" $ do         jsonListF (["a"] :: [Text]) ==#> [text|           [             a@@ -212,7 +392,8 @@           , ccc           ]           |]-      it "multi-line elements" $ do++    multiLineElements = it "multi-line elements" $ do         jsonListF (["a\nx"] :: [Text]) ==#> [text|           [             a@@ -236,7 +417,8 @@           __           ]           |]-      it "mix of single-line and multi-line" $ do++    mixed = it "mix of single-line and multi-line" $ do         jsonListF (["a\nx","b"] :: [Text]) ==#> [text|           [             a@@ -262,269 +444,269 @@           ]           |] -    describe "'mapF'" $ do-      it "simple examples" $ do-        mapF ([] :: [(Int, Int)]) ==#> "{}"-        mapF [(n,n+1)] ==#> "{25: 26}"-        mapF [(s,n)] ==#> "{!: 25}"-        mapF [('a',True),('b',False),('c',True)] ==#>-          "{a: True, b: False, c: True}"-      it "different map types" $ do-        let m = [('a',True),('b',False),('d',False),('c',True)]-        mapF m ==#> "{a: True, b: False, d: False, c: True}"-        mapF (M.fromList m) ==#> "{a: True, b: False, c: True, d: False}"+----------------------------------------------------------------------------+-- Tests for maps+---------------------------------------------------------------------------- -    describe "'blockMapF'" $ do-      it "empty map" $ do-        blockMapF ([] :: [(Int, Int)]) ==#> "{}\n"-      it "complex example" $ do-        blockMapF ([("hi", ""),-                    ("foo"," a\n  b"),-                    ("bar","a"),-                    ("baz","a\ng")] :: [(Text, Text)]) ==#> [text|-          hi:-          foo:-             a-              b-          bar: a-          baz:-            a-            g-          |]+test_maps :: Spec+test_maps = describe "maps" $ do+  test_mapF+  test_blockMapF+  test_jsonMapF -    describe "'jsonMapF'" $ do-      it "empty map" $ do-        jsonMapF ([] :: [(Int, Int)]) ==#> "{}\n"-      it "complex example" $ do-        jsonMapF ([("hi", ""),-                   ("foo"," a\n  b"),-                   ("bar","a"),-                   ("baz","a\ng")] :: [(Text, Text)]) ==#> [text|-          {-            hi:-          , foo:-               a-                b-          , bar: a-          , baz:-              a-              g-          }-          |]+test_mapF :: Spec+test_mapF = describe "'mapF'" $ do+  it "simple examples" $ do+    mapF ([] :: [(Int, Int)]) ==#> "{}"+    mapF [(n,n+1)] ==#> "{25: 26}"+    mapF [(s,n)] ==#> "{!: 25}"+    mapF [('a',True),('b',False),('c',True)] ==#>+      "{a: True, b: False, c: True}"+  it "different map types" $ do+    let m = [('a',True),('b',False),('d',False),('c',True)]+    mapF m ==#> "{a: True, b: False, d: False, c: True}"+    mapF (M.fromList m) ==#> "{a: True, b: False, c: True, d: False}" -    describe "tuples" $ do-      it "tupleF" $ do-        -- we don't need complicated tests here, they're all tested in-        -- 'tupleLikeF' tests-        tupleF (n, s) ==#> "(25, !)"-        tupleF (n, s, n, s) ==#> "(25, !, 25, !)"-        tupleF (n, s, n, s, 'a', 'b', 'c', 'd') ==#>-          "(25, !, 25, !, a, b, c, d)"-      describe "tupleLikeF" $ do-        describe "one-line" $ do-          it "()" $ do-            tupleLikeF [] ==#> "()"-          it "('')" $ do-            tupleLikeF [""] ==#> "()"-          it "(a)" $ do-            tupleLikeF ["a"] ==#> "(a)"-          it "(a,b)" $ do-            tupleLikeF ["a", "b"] ==#> "(a, b)"-          it "(a,'')" $ do-            tupleLikeF ["a", ""] ==#> "(a, )"-          it "('',b)" $ do-            tupleLikeF ["", "b"] ==#> "(, b)"-          it "('','')" $ do-            tupleLikeF ["", ""] ==#> "(, )"-          it "(a,b,c)" $ do-            tupleLikeF ["a", "ba", "caba"] ==#> "(a, ba, caba)"-        it "weird case" $ do-          -- not sure whether I should fix it or not-          tupleLikeF ["a\n"] ==#> "(a\n)"-        describe "multiline" $ do-          describe "all non-empty" $ do-            it "1 element (2 lines)" $ do-              tupleLikeF ["a\nx"] ==#> [text|-                ( a-                  x )-                |]-              tupleLikeF ["a\n x"] ==#> [text|-                ( a-                   x )-                |]-              tupleLikeF [" a\nx\n"] ==#> [text|-                (  a-                  x )-                |]-            it "1 element (3 lines)" $ do-              tupleLikeF ["a\nb\nc"] ==#> [text|-                ( a-                  b-                  c )-                |]-            it "2 elements (1 line + 2 lines)" $ do-              tupleLikeF ["a", "b\nc"] ==#> [text|-                ( a-                ,-                  b-                  c )-                |]-            it "2 elements (2 lines + 1 line)" $ do-              tupleLikeF ["a\nb", "c"] ==#> [text|-                ( a-                  b-                ,-                  c )-                |]-            it "3 elements (each has 2 lines)" $ do-              tupleLikeF ["a\nb", "c\nd", "e\nf"] ==#> [text|-                ( a-                  b-                ,-                  c-                  d-                ,-                  e-                  f )-                |]-          describe "some empty" $ do-            it "2 elements (0 + 2)" $ do-              tupleLikeF ["", "a\nb"] ==#> [text|-                (-                ,-                  a-                  b )-                |]-            it "2 elements (2 + 0)" $ do-              tupleLikeF ["a\nb", ""] ==#> [text|-                ( a-                  b-                ,-                  )-                |]-            it "3 elements (0 + 2 + 0)" $ do-              tupleLikeF ["", "a\nb", ""] ==#> [text|-                (-                ,-                  a-                  b-                ,-                  )-                |]-            it "3 elements (2 + 0 + 2)" $ do-              tupleLikeF ["a\nb", "", "c\nd"] ==#> [text|-                ( a-                  b-                ,-                ,-                  c-                  d )-                |]-            it "4 elements (2 + 0 + 0 + 2)" $ do-              tupleLikeF ["a\nb", "", "", "c\nd"] ==#> [text|-                ( a-                  b-                ,-                ,-                ,-                  c-                  d )-                |]+test_blockMapF :: Spec+test_blockMapF = describe "'blockMapF'" $ do+  it "empty map" $ do+    blockMapF ([] :: [(Int, Int)]) ==#> "{}\n"+  it "complex example" $ do+    blockMapF ([("hi", ""),+                ("foo"," a\n  b"),+                ("bar","a"),+                ("baz","a\ng")] :: [(Text, Text)]) ==#> [text|+      hi:+      foo:+         a+          b+      bar: a+      baz:+        a+        g+      |] -    describe "ADTs" $ do-      it "maybeF" $ do-        maybeF (Nothing :: Maybe Int) ==#> "<Nothing>"-        maybeF (Just 3 :: Maybe Int) ==#> "3"-      it "eitherF" $ do-        eitherF (Left 1 :: Either Int Int) ==#> "<Left>: 1"-        eitherF (Right 1 :: Either Int Int) ==#> "<Right>: 1"+test_jsonMapF :: Spec+test_jsonMapF = describe "'jsonMapF'" $ do+  it "empty map" $ do+    jsonMapF ([] :: [(Int, Int)]) ==#> "{}\n"+  it "complex example" $ do+    jsonMapF ([("hi", ""),+               ("foo"," a\n  b"),+               ("bar","a"),+               ("baz","a\ng")] :: [(Text, Text)]) ==#> [text|+      {+        hi:+      , foo:+           a+            b+      , bar: a+      , baz:+          a+          g+      }+      |] -    describe "padding" $ do-      it "prefixF" $ do-        prefixF (-1) ("hello" :: Text) ==#> ""-        prefixF   0  ("hello" :: Text) ==#> ""-        prefixF   1  ("hello" :: Text) ==#> "h"-        prefixF   2  ("hello" :: Text) ==#> "he"-        prefixF   3  ("hello" :: Text) ==#> "hel"-        prefixF   5  ("hello" :: Text) ==#> "hello"-        prefixF 1000 ("hello" :: Text) ==#> "hello"-        prefixF 1000 (""      :: Text) ==#> ""-      it "suffixF" $ do-        suffixF (-1) ("hello" :: Text) ==#> ""-        suffixF   0  ("hello" :: Text) ==#> ""-        suffixF   1  ("hello" :: Text) ==#> "o"-        suffixF   2  ("hello" :: Text) ==#> "lo"-        suffixF   3  ("hello" :: Text) ==#> "llo"-        suffixF   5  ("hello" :: Text) ==#> "hello"-        suffixF 1000 ("hello" :: Text) ==#> "hello"-        suffixF 1000 (""      :: Text) ==#> ""-      it "padLeftF" $ do-        padLeftF (-1) '!' ("hello" :: Text) ==#> "hello"-        padLeftF   0  '!' ("hello" :: Text) ==#> "hello"-        padLeftF   1  '!' ("hello" :: Text) ==#> "hello"-        padLeftF   5  '!' ("hello" :: Text) ==#> "hello"-        padLeftF   6  '!' ("hello" :: Text) ==#> "!hello"-        padLeftF   7  '!' ("hello" :: Text) ==#> "!!hello"-        padLeftF   7  '!' (""      :: Text) ==#> "!!!!!!!"-      it "padRightF" $ do-        padRightF (-1) '!' ("hello" :: Text) ==#> "hello"-        padRightF   0  '!' ("hello" :: Text) ==#> "hello"-        padRightF   1  '!' ("hello" :: Text) ==#> "hello"-        padRightF   5  '!' ("hello" :: Text) ==#> "hello"-        padRightF   6  '!' ("hello" :: Text) ==#> "hello!"-        padRightF   7  '!' ("hello" :: Text) ==#> "hello!!"-        padRightF   7  '!' (""      :: Text) ==#> "!!!!!!!"-      it "padBothF" $ do-        padBothF (-1) '!' ("hello" :: Text) ==#> "hello"-        padBothF   0  '!' ("hello" :: Text) ==#> "hello"-        padBothF   1  '!' ("hello" :: Text) ==#> "hello"-        padBothF   5  '!' ("hello" :: Text) ==#> "hello"-        padBothF   6  '!' ("hello" :: Text) ==#> "!hello"-        padBothF   7  '!' ("hello" :: Text) ==#> "!hello!"-        padBothF   7  '!' ("hell"  :: Text) ==#> "!!hell!"-        padBothF   7  '!' ("hel"   :: Text) ==#> "!!hel!!"-        padBothF   8  '!' ("hell"  :: Text) ==#> "!!hell!!"-        padBothF   8  '!' ("hel"   :: Text) ==#> "!!!hel!!"-        padBothF   8  '!' (""      :: Text) ==#> "!!!!!!!!"+----------------------------------------------------------------------------+-- Tests for tuples+---------------------------------------------------------------------------- -    describe "integer" $ do-      it "octF" $ do-        octF n ==#> "31"-      it "binF" $ do-        binF n ==#> "11001"-      it "baseF" $ do-        baseF 36 (n^n) ==#> "54kbbzw21jhueg5jb0ggr4p"-      it "-baseF" $ do-        baseF 36 (-(n^n)) ==#> "-54kbbzw21jhueg5jb0ggr4p"+test_tuples :: Spec+test_tuples = describe "tuples" $ do+  test_tupleSimple+  describe "tupleLikeF" $ do+    test_tupleOneLine+    test_tupleMultiline -    describe "floating-point" $ do-      let f1_3 = 1.2999999999999998 :: Double-      it "floatF" $ do-        floatF f1_3 ==#> "1.2999999999999998"-      it "exptF" $ do-        exptF 2 f1_3 ==#> "1.30e0"-      it "fixedF" $ do-        fixedF 2 f1_3 ==#> "1.30"-      it "precF" $ do-        precF 2 f1_3 ==#> "1.3"+test_tupleSimple :: Spec+test_tupleSimple = it "tupleF" $ do+  -- we don't need complicated tests here, they're all tested in+  -- 'tupleLikeF' tests+  tupleF (n, s) ==#> "(25, !)"+  tupleF (n, s, n, s) ==#> "(25, !, 25, !)"+  tupleF (n, s, n, s, 'a', 'b', 'c', 'd') ==#>+    "(25, !, 25, !, a, b, c, d)" -    describe "conditionals" $ do-      it "whenF" $ do-        whenF True "hi" ==#> "hi"-        whenF False "hi" ==#> ""-      it "unlessF" $ do-        unlessF True "hi" ==#> ""-        unlessF False "hi" ==#> "hi"+test_tupleOneLine :: Spec+test_tupleOneLine = describe "one-line" $ do+  it "()" $ do+    tupleLikeF [] ==#> "()"+  it "('')" $ do+    tupleLikeF [""] ==#> "()"+  it "(a)" $ do+    tupleLikeF ["a"] ==#> "(a)"+  it "(a,b)" $ do+    tupleLikeF ["a", "b"] ==#> "(a, b)"+  it "(a,'')" $ do+    tupleLikeF ["a", ""] ==#> "(a, )"+  it "('',b)" $ do+    tupleLikeF ["", "b"] ==#> "(, b)"+  it "('','')" $ do+    tupleLikeF ["", ""] ==#> "(, )"+  it "(a,b,c)" $ do+    tupleLikeF ["a", "ba", "caba"] ==#> "(a, ba, caba)" -    describe "'hexF'" $ do-      it "Int" $ do-        hexF n ==#> "19"-      it "-Int" $ do-        hexF (-n) ==#> "-19"-      it "strict ByteString" $ do-        hexF (BS.pack [15,250]) ==#> "0ffa"-      it "lazy ByteString" $ do-        hexF (BSL.pack [15,250]) ==#> "0ffa"+test_tupleMultiline :: Spec+test_tupleMultiline = describe "multiline" $ do+  allNonEmpty+  someEmpty+  it "weird case" $ do+    -- not sure whether I should fix it or not+    tupleLikeF ["a\n"] ==#> "(a\n)"++  where+    allNonEmpty = describe "all non-empty" $ do+      it "1 element (2 lines)" $ do+          tupleLikeF ["a\nx"] ==#> [text|+            ( a+              x )+            |]+          tupleLikeF ["a\n x"] ==#> [text|+            ( a+               x )+            |]+          tupleLikeF [" a\nx\n"] ==#> [text|+            (  a+              x )+            |]+      it "1 element (3 lines)" $ do+          tupleLikeF ["a\nb\nc"] ==#> [text|+            ( a+              b+              c )+            |]+      it "2 elements (1 line + 2 lines)" $ do+          tupleLikeF ["a", "b\nc"] ==#> [text|+            ( a+            ,+              b+              c )+            |]+      it "2 elements (2 lines + 1 line)" $ do+          tupleLikeF ["a\nb", "c"] ==#> [text|+            ( a+              b+            ,+              c )+            |]+      it "3 elements (each has 2 lines)" $ do+          tupleLikeF ["a\nb", "c\nd", "e\nf"] ==#> [text|+            ( a+              b+            ,+              c+              d+            ,+              e+              f )+            |]++    someEmpty = describe "some empty" $ do+      it "2 elements (0 + 2)" $ do+          tupleLikeF ["", "a\nb"] ==#> [text|+            (+            ,+              a+              b )+            |]+      it "2 elements (2 + 0)" $ do+          tupleLikeF ["a\nb", ""] ==#> [text|+            ( a+              b+            ,+              )+            |]+      it "3 elements (0 + 2 + 0)" $ do+          tupleLikeF ["", "a\nb", ""] ==#> [text|+            (+            ,+              a+              b+            ,+              )+            |]+      it "3 elements (2 + 0 + 2)" $ do+          tupleLikeF ["a\nb", "", "c\nd"] ==#> [text|+            ( a+              b+            ,+            ,+              c+              d )+            |]+      it "4 elements (2 + 0 + 0 + 2)" $ do+          tupleLikeF ["a\nb", "", "", "c\nd"] ==#> [text|+            ( a+              b+            ,+            ,+            ,+              c+              d )+            |]++----------------------------------------------------------------------------+-- Tests for 'genericF'+----------------------------------------------------------------------------++data Foo a = Foo a deriving Generic+data Bar a = Bar a a deriving Generic+data Qux a = Qux {q1 :: Int, q2 :: a, q3 :: Text} deriving Generic++data Op = (:|:) Int+        | (:||:) Int Int+        | (:|||:) Int Int Int+        | Int :-: Int+        | Int `O` Int+  deriving Generic++test_generic :: Spec+test_generic = describe "'genericF'" $ do+  it "Maybe" $ do+    genericF (Nothing :: Maybe Int) ==#> "Nothing"+    genericF (Just 25 :: Maybe Int) ==#> "<Just: 25>"+  it "Either" $ do+    genericF (Left 25 :: Either Int Bool) ==#> "<Left: 25>"+    genericF (Right True :: Either Int Bool) ==#> "<Right: True>"+  it "tuples" $ do+    genericF (n, s) ==#> "(25, !)"+    genericF (n, s, -n, s ++ s) ==#> "(25, !, -25, !!)"+  describe "custom types" $ do+    it "ordinary constructors" $ do+      genericF (Foo n) ==#> "<Foo: 25>"+      genericF (Bar (n-1) (n+1)) ==#> "<Bar: 24, 26>"+    it "records" $ do+      genericF (Qux 1 True "hi") ==#> [text|+        Qux:+          q1: 1+          q2: True+          q3: hi+        |]+    it "operators and infix constructors" $ do+      genericF ((:|:) 1) ==#> "<(:|:): 1>"+      genericF ((:||:) 1 2) ==#> "<(:||:): 1, 2>"+      genericF ((:|||:) 1 2 3) ==#> "<(:|||:): 1, 2, 3>"+      genericF ((:-:) 1 2) ==#> "(1 :-: 2)"+      genericF (O 1 2) ==#> "(1 `O` 2)"+    describe "types with non-Buildables inside" $ do+      it "functions" $ do+        genericF (Foo not) ==#> "<Foo: <function>>"+      it "lists" $ do+        genericF (Foo [n, n+1]) ==#> "<Foo: [25, 26]>"+      it "maps" $ do+        genericF (Foo (M.singleton n s)) ==#> "<Foo: {25: !}>"+      it "tuples" $ do+        genericF (Foo (n, s)) ==#> "<Foo: (25, !)>"+        genericF (Foo (n, s, n+1)) ==#> "<Foo: (25, !, 26)>"+      it "tuple with a string inside" $ do+        -- strings have to be handled differently from lists+        -- so we test this case separately+        genericF (Foo (n, s)) ==#> "<Foo: (25, !)>"+      it "Either" $ do+        genericF (Foo (Left 25 :: Either Int Int)) ==#> "<Foo: <Left: 25>>"+      it "Maybe with a non-buildable" $ do+        genericF (Foo (Just [n])) ==#> "<Foo: [25]>"+        genericF (Foo (Nothing :: Maybe ())) ==#> "<Foo: <Nothing>>"  ---------------------------------------------------------------------------- -- Utilities