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 +12/−0
- bench/Main.hs +129/−0
- fmt.cabal +59/−4
- lib/Fmt.hs +403/−123
- lib/Fmt/Internal.hs +54/−1
- tests/Main.hs +504/−322
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