packages feed

fmt 0.5.0.0 → 0.6

raw patch · 15 files changed

+1733/−1644 lines, 15 filesdep +call-stackdep +doctestdep +semigroupsdep −base16-bytestringdep −text-formatdep ~basedep ~formattingPVP ok

version bump matches the API change (PVP)

Dependencies added: call-stack, doctest, semigroups

Dependencies removed: base16-bytestring, text-format

Dependency ranges changed: base, formatting

API changes (from Hackage documentation)

- 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: precF :: Real a => Int -> a -> Builder
- Fmt: tupleLikeF :: [Builder] -> Builder
- Fmt.Internal: atBase :: Integral a => Int -> a -> String
- Fmt.Internal: build' :: Buildable' a => a -> Builder
- Fmt.Internal: class Buildable' a
- Fmt.Internal: class FormatType r
- Fmt.Internal: class FromBuilder a
- Fmt.Internal: class GBuildable f
- Fmt.Internal: class GetFields f
- Fmt.Internal: class TupleF a
- Fmt.Internal: fixedF :: Real a => Int -> a -> Builder
- Fmt.Internal: format' :: FormatType r => Format -> [Builder] -> r
- Fmt.Internal: fromBuilder :: FromBuilder a => Builder -> a
- Fmt.Internal: gbuild :: GBuildable f => f a -> Builder
- Fmt.Internal: getFields :: GetFields f => f a -> [(String, Builder)]
- Fmt.Internal: groupInt :: (Buildable a, Integral a) => Int -> Char -> a -> Builder
- Fmt.Internal: indentF' :: Int -> Text -> Builder -> Builder
- Fmt.Internal: instance (Data.Text.Buildable.Buildable a, Fmt.Internal.FormatType r) => Fmt.Internal.FormatType (a -> r)
- Fmt.Internal: instance Fmt.Internal.FromBuilder Data.Text.Internal.Builder.Builder
- Fmt.Internal: instance Fmt.Internal.FromBuilder Data.Text.Internal.Lazy.Text
- Fmt.Internal: instance Fmt.Internal.FromBuilder Data.Text.Internal.Text
- Fmt.Internal: instance Fmt.Internal.FromBuilder r => Fmt.Internal.FormatType r
- Fmt.Internal: instance a ~ () => Fmt.Internal.FromBuilder (GHC.Types.IO a)
- Fmt.Internal: instance a ~ GHC.Types.Char => Fmt.Internal.FromBuilder [a]
- Fmt.Internal: intToDigit' :: Int -> Char
- Fmt.Internal: ordinalF :: (Buildable a, Integral a) => a -> Builder
- Fmt.Internal: showSigned' :: Real a => (a -> ShowS) -> a -> ShowS
- Fmt.Internal: tupleF :: TupleF a => a -> Builder
+ Fmt.Internal.Core: (+|) :: (FromBuilder b) => Builder -> Builder -> b
+ Fmt.Internal.Core: (+||) :: (FromBuilder b) => Builder -> Builder -> b
+ Fmt.Internal.Core: (|+) :: (Buildable a, FromBuilder b) => a -> Builder -> b
+ Fmt.Internal.Core: (|++|) :: (Buildable a, FromBuilder b) => a -> Builder -> b
+ Fmt.Internal.Core: (|++||) :: (Show a, FromBuilder b) => a -> Builder -> b
+ Fmt.Internal.Core: (||+) :: (Show a, FromBuilder b) => a -> Builder -> b
+ Fmt.Internal.Core: (||++|) :: (Buildable a, FromBuilder b) => a -> Builder -> b
+ Fmt.Internal.Core: (||++||) :: (Show a, FromBuilder b) => a -> Builder -> b
+ Fmt.Internal.Core: class FromBuilder a
+ Fmt.Internal.Core: fmt :: FromBuilder b => Builder -> b
+ Fmt.Internal.Core: fmtLn :: FromBuilder b => Builder -> b
+ Fmt.Internal.Core: fromBuilder :: FromBuilder a => Builder -> a
+ Fmt.Internal.Core: infixr 1 |++||
+ Fmt.Internal.Core: instance Fmt.Internal.Core.FromBuilder Data.Text.Internal.Builder.Builder
+ Fmt.Internal.Core: instance Fmt.Internal.Core.FromBuilder Data.Text.Internal.Lazy.Text
+ Fmt.Internal.Core: instance Fmt.Internal.Core.FromBuilder Data.Text.Internal.Text
+ Fmt.Internal.Core: instance a ~ () => Fmt.Internal.Core.FromBuilder (GHC.Types.IO a)
+ Fmt.Internal.Core: instance a ~ GHC.Types.Char => Fmt.Internal.Core.FromBuilder [a]
+ Fmt.Internal.Formatters: blockListF :: forall f a. (Foldable f, Buildable a) => f a -> Builder
+ Fmt.Internal.Formatters: blockListF' :: forall f a. Foldable f => Text -> (a -> Builder) -> f a -> Builder
+ Fmt.Internal.Formatters: blockMapF :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder
+ Fmt.Internal.Formatters: blockMapF' :: (IsList t, Item t ~ (k, v)) => (k -> Builder) -> (v -> Builder) -> t -> Builder
+ Fmt.Internal.Formatters: eitherF :: (Buildable a, Buildable b) => Either a b -> Builder
+ Fmt.Internal.Formatters: indentF :: Int -> Builder -> Builder
+ Fmt.Internal.Formatters: indentF' :: Int -> Text -> Builder -> Builder
+ Fmt.Internal.Formatters: jsonListF :: forall f a. (Foldable f, Buildable a) => f a -> Builder
+ Fmt.Internal.Formatters: jsonListF' :: forall f a. (Foldable f) => (a -> Builder) -> f a -> Builder
+ Fmt.Internal.Formatters: jsonMapF :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder
+ Fmt.Internal.Formatters: jsonMapF' :: forall t k v. (IsList t, Item t ~ (k, v)) => (k -> Builder) -> (v -> Builder) -> t -> Builder
+ Fmt.Internal.Formatters: listF :: (Foldable f, Buildable a) => f a -> Builder
+ Fmt.Internal.Formatters: listF' :: (Foldable f) => (a -> Builder) -> f a -> Builder
+ Fmt.Internal.Formatters: mapF :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder
+ Fmt.Internal.Formatters: mapF' :: (IsList t, Item t ~ (k, v)) => (k -> Builder) -> (v -> Builder) -> t -> Builder
+ Fmt.Internal.Formatters: maybeF :: Buildable a => Maybe a -> Builder
+ Fmt.Internal.Formatters: nameF :: Builder -> Builder -> Builder
+ Fmt.Internal.Formatters: padBothF :: Buildable a => Int -> Char -> a -> Builder
+ Fmt.Internal.Formatters: padLeftF :: Buildable a => Int -> Char -> a -> Builder
+ Fmt.Internal.Formatters: padRightF :: Buildable a => Int -> Char -> a -> Builder
+ Fmt.Internal.Formatters: prefixF :: Buildable a => Int -> a -> Builder
+ Fmt.Internal.Formatters: suffixF :: Buildable a => Int -> a -> Builder
+ Fmt.Internal.Formatters: unlessF :: Bool -> Builder -> Builder
+ Fmt.Internal.Formatters: unlinesF :: (Foldable f, Buildable a) => f a -> Builder
+ Fmt.Internal.Formatters: unwordsF :: (Foldable f, Buildable a) => f a -> Builder
+ Fmt.Internal.Formatters: whenF :: Bool -> Builder -> Builder
+ Fmt.Internal.Generic: build' :: Buildable' a => a -> Builder
+ Fmt.Internal.Generic: class Buildable' a
+ Fmt.Internal.Generic: class GBuildable f
+ Fmt.Internal.Generic: class GetFields f
+ Fmt.Internal.Generic: gbuild :: GBuildable f => f a -> Builder
+ Fmt.Internal.Generic: genericF :: (Generic a, GBuildable (Rep a)) => a -> Builder
+ Fmt.Internal.Generic: getFields :: GetFields f => f a -> [(String, Builder)]
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a, Fmt.Internal.Generic.Buildable' b) => Fmt.Internal.Generic.Buildable' (Data.Either.Either a b)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a1, Fmt.Internal.Generic.Buildable' a2) => Fmt.Internal.Generic.Buildable' (a1, a2)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a1, Fmt.Internal.Generic.Buildable' a2, Fmt.Internal.Generic.Buildable' a3) => Fmt.Internal.Generic.Buildable' (a1, a2, a3)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a1, Fmt.Internal.Generic.Buildable' a2, Fmt.Internal.Generic.Buildable' a3, Fmt.Internal.Generic.Buildable' a4) => Fmt.Internal.Generic.Buildable' (a1, a2, a3, a4)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a1, Fmt.Internal.Generic.Buildable' a2, Fmt.Internal.Generic.Buildable' a3, Fmt.Internal.Generic.Buildable' a4, Fmt.Internal.Generic.Buildable' a5) => Fmt.Internal.Generic.Buildable' (a1, a2, a3, a4, a5)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a1, Fmt.Internal.Generic.Buildable' a2, Fmt.Internal.Generic.Buildable' a3, Fmt.Internal.Generic.Buildable' a4, Fmt.Internal.Generic.Buildable' a5, Fmt.Internal.Generic.Buildable' a6) => Fmt.Internal.Generic.Buildable' (a1, a2, a3, a4, a5, a6)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a1, Fmt.Internal.Generic.Buildable' a2, Fmt.Internal.Generic.Buildable' a3, Fmt.Internal.Generic.Buildable' a4, Fmt.Internal.Generic.Buildable' a5, Fmt.Internal.Generic.Buildable' a6, Fmt.Internal.Generic.Buildable' a7) => Fmt.Internal.Generic.Buildable' (a1, a2, a3, a4, a5, a6, a7)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' a1, Fmt.Internal.Generic.Buildable' a2, Fmt.Internal.Generic.Buildable' a3, Fmt.Internal.Generic.Buildable' a4, Fmt.Internal.Generic.Buildable' a5, Fmt.Internal.Generic.Buildable' a6, Fmt.Internal.Generic.Buildable' a7, Fmt.Internal.Generic.Buildable' a8) => Fmt.Internal.Generic.Buildable' (a1, a2, a3, a4, a5, a6, a7, a8)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.Buildable' k, Fmt.Internal.Generic.Buildable' v) => Fmt.Internal.Generic.Buildable' (Data.Map.Internal.Map k v)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.GBuildable a, Fmt.Internal.Generic.GBuildable b) => Fmt.Internal.Generic.GBuildable (a GHC.Generics.:+: b)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.GBuildable a, GHC.Generics.Selector c) => Fmt.Internal.Generic.GetFields (GHC.Generics.M1 GHC.Generics.S c a)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.GetFields a, Fmt.Internal.Generic.GetFields b) => Fmt.Internal.Generic.GetFields (a GHC.Generics.:*: b)
+ Fmt.Internal.Generic: instance (Fmt.Internal.Generic.GetFields a, GHC.Generics.Constructor c) => Fmt.Internal.Generic.GBuildable (GHC.Generics.M1 GHC.Generics.C c a)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' ()
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' (a -> b)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' Data.IntSet.Internal.IntSet
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' [GHC.Types.Char]
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' a => Fmt.Internal.Generic.Buildable' (Data.List.NonEmpty.NonEmpty a)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' a => Fmt.Internal.Generic.Buildable' (Data.Sequence.Internal.Seq a)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' a => Fmt.Internal.Generic.Buildable' (GHC.Base.Maybe a)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' a => Fmt.Internal.Generic.Buildable' [a]
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' c => Fmt.Internal.Generic.GBuildable (GHC.Generics.K1 i c)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' v => Fmt.Internal.Generic.Buildable' (Data.IntMap.Internal.IntMap v)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.Buildable' v => Fmt.Internal.Generic.Buildable' (Data.Set.Internal.Set v)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.GBuildable a => Fmt.Internal.Generic.GBuildable (GHC.Generics.M1 GHC.Generics.D d a)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.GBuildable a => Fmt.Internal.Generic.GetFields (GHC.Generics.M1 GHC.Generics.C c a)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.GBuildable a => Fmt.Internal.Generic.GetFields (GHC.Generics.M1 GHC.Generics.D c a)
+ Fmt.Internal.Generic: instance Fmt.Internal.Generic.GetFields GHC.Generics.U1
+ Fmt.Internal.Generic: instance Formatting.Buildable.Buildable a => Fmt.Internal.Generic.Buildable' a
+ Fmt.Internal.Numeric: atBase :: Integral a => Int -> a -> String
+ Fmt.Internal.Numeric: baseF :: Integral a => Int -> a -> Builder
+ Fmt.Internal.Numeric: binF :: Integral a => a -> Builder
+ Fmt.Internal.Numeric: commaizeF :: (Buildable a, Integral a) => a -> Builder
+ Fmt.Internal.Numeric: exptF :: Real a => Int -> a -> Builder
+ Fmt.Internal.Numeric: fixedF :: Real a => Int -> a -> Builder
+ Fmt.Internal.Numeric: floatF :: Real a => a -> Builder
+ Fmt.Internal.Numeric: groupInt :: (Buildable a, Integral a) => Int -> Char -> a -> Builder
+ Fmt.Internal.Numeric: intToDigit' :: Int -> Char
+ Fmt.Internal.Numeric: octF :: Integral a => a -> Builder
+ Fmt.Internal.Numeric: ordinalF :: (Buildable a, Integral a) => a -> Builder
+ Fmt.Internal.Numeric: showSigned' :: Real a => (a -> ShowS) -> a -> ShowS
+ Fmt.Internal.Template: Format :: Text -> Format
+ Fmt.Internal.Template: [fromFormat] :: Format -> Text
+ Fmt.Internal.Template: class FormatType r
+ Fmt.Internal.Template: crack :: Format -> [Builder]
+ Fmt.Internal.Template: format :: FormatType r => Format -> r
+ Fmt.Internal.Template: format' :: FormatType r => Format -> [Builder] -> r
+ Fmt.Internal.Template: formatLn :: FormatType r => Format -> r
+ Fmt.Internal.Template: instance (Formatting.Buildable.Buildable a, Fmt.Internal.Template.FormatType r) => Fmt.Internal.Template.FormatType (a -> r)
+ Fmt.Internal.Template: instance Data.Semigroup.Semigroup Fmt.Internal.Template.Format
+ Fmt.Internal.Template: instance Data.String.IsString Fmt.Internal.Template.Format
+ Fmt.Internal.Template: instance Fmt.Internal.Core.FromBuilder r => Fmt.Internal.Template.FormatType r
+ Fmt.Internal.Template: instance GHC.Base.Monoid Fmt.Internal.Template.Format
+ Fmt.Internal.Template: instance GHC.Classes.Eq Fmt.Internal.Template.Format
+ Fmt.Internal.Template: instance GHC.Classes.Ord Fmt.Internal.Template.Format
+ Fmt.Internal.Template: instance GHC.Show.Show Fmt.Internal.Template.Format
+ Fmt.Internal.Template: newtype Format
+ Fmt.Internal.Template: renderFormat :: Format -> [Builder] -> Builder
+ Fmt.Internal.Template: zipParams :: [Builder] -> [Builder] -> Builder
+ Fmt.Internal.Tuple: class TupleF a
+ Fmt.Internal.Tuple: instance (Formatting.Buildable.Buildable a1, Formatting.Buildable.Buildable a2) => Fmt.Internal.Tuple.TupleF (a1, a2)
+ Fmt.Internal.Tuple: instance (Formatting.Buildable.Buildable a1, Formatting.Buildable.Buildable a2, Formatting.Buildable.Buildable a3) => Fmt.Internal.Tuple.TupleF (a1, a2, a3)
+ Fmt.Internal.Tuple: instance (Formatting.Buildable.Buildable a1, Formatting.Buildable.Buildable a2, Formatting.Buildable.Buildable a3, Formatting.Buildable.Buildable a4) => Fmt.Internal.Tuple.TupleF (a1, a2, a3, a4)
+ Fmt.Internal.Tuple: instance (Formatting.Buildable.Buildable a1, Formatting.Buildable.Buildable a2, Formatting.Buildable.Buildable a3, Formatting.Buildable.Buildable a4, Formatting.Buildable.Buildable a5) => Fmt.Internal.Tuple.TupleF (a1, a2, a3, a4, a5)
+ Fmt.Internal.Tuple: instance (Formatting.Buildable.Buildable a1, Formatting.Buildable.Buildable a2, Formatting.Buildable.Buildable a3, Formatting.Buildable.Buildable a4, Formatting.Buildable.Buildable a5, Formatting.Buildable.Buildable a6) => Fmt.Internal.Tuple.TupleF (a1, a2, a3, a4, a5, a6)
+ Fmt.Internal.Tuple: instance (Formatting.Buildable.Buildable a1, Formatting.Buildable.Buildable a2, Formatting.Buildable.Buildable a3, Formatting.Buildable.Buildable a4, Formatting.Buildable.Buildable a5, Formatting.Buildable.Buildable a6, Formatting.Buildable.Buildable a7) => Fmt.Internal.Tuple.TupleF (a1, a2, a3, a4, a5, a6, a7)
+ Fmt.Internal.Tuple: instance (Formatting.Buildable.Buildable a1, Formatting.Buildable.Buildable a2, Formatting.Buildable.Buildable a3, Formatting.Buildable.Buildable a4, Formatting.Buildable.Buildable a5, Formatting.Buildable.Buildable a6, Formatting.Buildable.Buildable a7, Formatting.Buildable.Buildable a8) => Fmt.Internal.Tuple.TupleF (a1, a2, a3, a4, a5, a6, a7, a8)
+ Fmt.Internal.Tuple: instance Fmt.Internal.Tuple.TupleF [Data.Text.Internal.Builder.Builder]
+ Fmt.Internal.Tuple: instance Formatting.Buildable.Buildable a => Fmt.Internal.Tuple.TupleF [a]
+ Fmt.Internal.Tuple: tupleF :: TupleF a => a -> Builder
- Fmt: data Format :: *
+ Fmt: data Format

Files

CHANGELOG.md view
@@ -1,3 +1,24 @@+# 0.6++* Switched to `Buildable` from `formatting` (since `text-format` is+  unmaintained).++* Removed the `double-conversion` dependency (which was sometimes causing+  compilation issues). As the result, `exptF`, `fixedF` and `floatF` have+  become slower.++* The `precF` formatter was removed completely because its semantics was too+  confusing. You can use `Numeric.showGFloat` to achieve a similar effect.++* `floatF` now always prints a point, even if the number is integral.++* `tupleLikeF` has been removed. `TupleF` now has an additional instance+  that lets `tupleF` be used to format lists.++* The `base16-bytestring` dependency was removed.++* Compatibility with GHC 7.6 and 7.8 was dropped.+ # 0.5.0.0  * From this version on, `blockListF` never puts blank lines between items.
bench/Main.hs view
@@ -10,10 +10,7 @@ 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           Fmt                     ((+|), (|+), format) import           Formatting              (Format, formatToString, int, sformat, stext,                                           string, (%)) import           Text.Printf             (printf)@@ -25,12 +22,6 @@ -- 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@@ -69,10 +60,10 @@     [ bTextGroup (1 :: Int, 2 :: Int)       [ taggedB "fmt" $           \(a,b) -> "hello "+|a|+" world "+|b|+""+      , taggedB "fmt old-style" $+          \(a,b) -> format "hello {} world {}" a b :: Text       , 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" $@@ -83,10 +74,10 @@     , bStringGroup (1 :: Int, 2 :: Int)       [ taggedB "fmt" $           \(a,b) -> "hello "+|a|+" world "+|b|+""+      , taggedB "fmt old-style" $+          \(a,b) -> format "hello {} world {}" a b :: String       , 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" $@@ -100,10 +91,10 @@     [ bTextGroup (9 :: Int, "Beijing" :: Text)       [ taggedB "fmt" $           \(n,city) -> "There are "+|n|+" million bicycles in "+|city|+"."+      , taggedB "fmt old-style" $+          \(n,city) -> format "There are {} million bicycles in {}." n city :: Text       , 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" $@@ -114,10 +105,10 @@     , bStringGroup (9 :: Int, "Beijing" :: String)       [ taggedB "fmt" $           \(n,city) -> "There are "+|n|+" million bicycles in "+|city|+"."+      , taggedB "fmt old-style" $+          \(n,city) -> format "There are {} million bicycles in {}." n city :: String       , 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" $
fmt.cabal view
@@ -1,5 +1,5 @@ name:                fmt-version:             0.5.0.0+version:             0.6 synopsis:            A new formatting library description:   A new formatting library that tries to be simple to understand while still@@ -16,7 +16,7 @@     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+    formatting string with curly braces 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@@ -46,9 +46,10 @@ maintainer:          yom@artyom.me -- copyright: category:            Text-tested-with:         GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.1, GHC == 8.2.1+tested-with:         GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.1 build-type:          Simple extra-source-files:  CHANGELOG.md+                     tests/doctest-config.json cabal-version:       >=1.10  source-repository head@@ -57,39 +58,61 @@  library   exposed-modules:     Fmt-                       Fmt.Internal                        Fmt.Time+                       Fmt.Internal+                       Fmt.Internal.Core+                       Fmt.Internal.Formatters+                       Fmt.Internal.Template+                       Fmt.Internal.Tuple+                       Fmt.Internal.Numeric+                       Fmt.Internal.Generic+   build-depends:       base >=4.6 && <5,-                       base16-bytestring,                        base64-bytestring,                        bytestring,                        containers,+                       formatting >= 6.3.4,                        microlens >= 0.3,                        text,-                       text-format >= 0.3,                        time,                        time-locale-compat+   ghc-options:         -Wall -fno-warn-unused-do-bind   if impl(ghc > 8.0)     ghc-options:       -Wno-redundant-constraints   hs-source-dirs:      lib   default-language:    Haskell2010+  if !impl(ghc >= 8.0)+    build-depends: semigroups == 0.18.*  test-suite tests   main-is:             Main.hs   type:                exitcode-stdio-1.0   build-depends:       base >=4.6 && <5                      , bytestring+                     , call-stack                      , containers                      , fmt-                     , hspec >= 2.2 && < 2.5+                     , hspec >= 2.2 && < 2.6                      , neat-interpolation                      , text                      , vector+   ghc-options:         -Wall -fno-warn-unused-do-bind+   hs-source-dirs:      tests   default-language:    Haskell2010-  if impl(ghc < 7.10)++test-suite doctests+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  ghc-options:         -threaded+  hs-source-dirs:      tests+  main-is:             doctests.hs+  build-depends:       base >=4.6 && <5+                     , doctest+  build-tool-depends:  doctest-discover:doctest-discover+  if impl(ghc < 8.0)     buildable: False  benchmark benches@@ -105,9 +128,6 @@                      , formatting                      , interpolate                      , text-                     , text-format                      , vector   ghc-options:         -Wall   default-language:    Haskell2010-  if impl(ghc < 7.10)-    buildable: False
lib/Fmt.hs view
@@ -18,1306 +18,329 @@       <https://github.com/ion1> -} -{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# 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-  -- $overloadedstrings--  -- * Examples-  -- $examples--  -- * Migration guide from @formatting@-  -- $migration--  -- * Basic formatting-  -- $brackets--  -- ** Ordinary brackets-  -- $god1-  (+|),-  (|+),-  -- ** 'Show' brackets-  -- $god2-  (+||),-  (||+),-  -- ** Combinations-  -- $god3-  (|++|),-  (||++||),-  (|++||),-  (||++|),--  -- * Old-style formatting-  format,-  formatLn,-  TF.Format,--  -- * Helper functions-  fmt,-  fmtLn,--  Builder,-  Buildable(..),--  -- * Formatters--  -- ** Time-  module Fmt.Time,--  -- ** Text-  indentF, indentF',-  nameF,-  unwordsF,-  unlinesF,--  -- ** Lists-  listF, listF',-  blockListF, blockListF',-  jsonListF, jsonListF',--  -- ** Maps-  mapF, mapF',-  blockMapF, blockMapF',-  jsonMapF, jsonMapF',--  -- ** Tuples-  tupleF,-  tupleLikeF,--  -- ** ADTs-  maybeF,-  eitherF,--  -- ** Padding/trimming-  prefixF,-  suffixF,-  padLeftF,-  padRightF,-  padBothF,--  -- ** Hex-  hexF,--  -- ** Bytestrings-  base64F,-  base64UrlF,--  -- ** Integers-  ordinalF,-  commaizeF,-  -- *** Base conversion-  octF,-  binF,-  baseF,--  -- ** Floating-point-  floatF,-  exptF,-  precF,-  fixedF,--  -- ** Conditional formatting-  whenF,-  unlessF,--  -- ** Generic formatting-  genericF,-)-where---- Generic useful things-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 as T-import qualified Data.Text.Lazy as TL--- 'Buildable' and text-format-import Data.Text.Buildable-import qualified Data.Text.Format as TF--- Text 'Builder'-import Data.Text.Lazy.Builder hiding (fromString)--- 'Foldable' and 'IsList' for list/map formatters-import Data.Foldable (toList)-#if __GLASGOW_HASKELL__ >= 708-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-import Fmt.Time---{- $overloadedstrings--You need @OverloadedStrings@ enabled to use this library. There are three-ways to do it:--  * __In GHCi:__ do @:set -XOverloadedStrings@.--  * __In a module:__ add @\{\-\# LANGUAGE OverloadedStrings \#\-\}@-    to the beginning of your module.--  * __In a project:__ add @OverloadedStrings@ to the @default-extensions@-    section of your @.cabal@ file.--}--{- $examples--Here's a bunch of examples because some people learn better by looking at-examples.--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: foo, bar\nAlso a number: 25"--Print it:-->>> fmtLn ("Here are some words: "+|a|+", "+|b|+"\nAlso a number: "+|n|+"")-Here are some words: foo, bar-Also a number: 25--Format a list in various ways:-->>> let xs = ["John", "Bob"]-->>> fmtLn ("Using show: "+||xs||+"\nUsing listF: "+|listF xs|+"")-Using show: ["John","Bob"]-Using listF: [John, Bob]-->>> fmt ("YAML-like:\n"+|blockListF xs|+"")-YAML-like:-- John-- Bob-->>> fmt ("JSON-like: "+|jsonListF xs|+"")-JSON-like: [-  John-, Bob-]---}--{- $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@)--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 ('|+'):-->>> let name = "Alice"->>> "Meet "+|name|+"!" :: String-"Meet Alice!"--Of course, 'Text' is supported as well:-->>> "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|+"!")-Meet Alice!--Otherwise the type of the formatted string would be resolved to @IO ()@ and-printed without a newline, which is not very convenient when you're in GHCi.-On the other hand, it's useful for quick-and-dirty scripts:--@-main = do-  [fin, fout] \<- words \<$\> getArgs-  __"Reading data from "+|fin|+"\\n"__-  xs \<- readFile fin-  __"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 ('|+'):-->>> let starCount = "173"->>> 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-conversion, you can use any functions you want inside them. In this case,-'length':-->>> 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|+"")-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|+"")-Character's position: (3, 5)--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|+"")-2*2 = 4--Also, since in some codebases there are /lots/ of types which aren't-'Buildable', there are operators ('+||') and ('||+'), which use 'show'-instead of 'build':--@-__(""+|show foo|++|show bar|+"")__  ===  __(""+||foo||++||bar||+"")__-@--}---- $god1--- Operators for the operators god!---- | Concatenate, then convert-(+|) :: (FromBuilder b) => Builder -> Builder -> b-(+|) str rest = fromBuilder (str <> rest)---- | 'build' and concatenate, then convert-(|+) :: (Buildable a, FromBuilder b) => a -> Builder -> b-(|+) a rest = fromBuilder (build a <> rest)--infixr 1 +|-infixr 1 |+--------------------------------------------------------------------------------- Operators with 'Show'--------------------------------------------------------------------------------- $god2--- More operators for the operators god!---- | Concatenate, then convert-(+||) :: (FromBuilder b) => Builder -> Builder -> b-(+||) str rest = str +| rest-{-# INLINE (+||) #-}---- | 'show' and concatenate, then convert-(||+) :: (Show a, FromBuilder b) => a -> Builder -> b-(||+) a rest = show a |+ rest-{-# INLINE (||+) #-}--infixr 1 +||-infixr 1 ||+--------------------------------------------------------------------------------- Combinations-------------------------------------------------------------------------------{- $god3--Z̸͠A̵̕͟͠L̡̀́͠G̶̛O͝ ̴͏̀ I͞S̸̸̢͠  ̢̛͘͢C̷͟͡Ó̧̨̧͞M̡͘͟͞I̷͜N̷̕G̷̀̕--(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). Also it's polyvariadic:-->>> format "{} + {} = {}" 2 2 4-"2 + 2 = 4"--You can use arbitrary formatters:-->>> format "0x{} + 0x{} = 0x{}" (hexF 130) (hexF 270) (hexF (130+270))-"0x82 + 0x10e = 0x190"--}-format :: FormatType r => TF.Format -> r-format f = format' f []-{-# INLINE format #-}--{- | Like 'format', but adds a newline.--}-formatLn :: FormatType r => TF.Format -> r-formatLn f = format' (f <> "\n") []-{-# INLINE formatLn #-}--------------------------------------------------------------------------------- Helper functions-------------------------------------------------------------------------------{- | '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-like 'listF' can only produce 'Builder' (for better type inference), and you-need to use 'fmt' on them.--Also, 'fmt' can do printing:-->>> fmt "Hello world!\n"-Hello world!--}-fmt :: FromBuilder b => Builder -> b-fmt = fromBuilder-{-# INLINE fmt #-}--{- | Like 'fmt', but appends a newline.--}-fmtLn :: FromBuilder b => Builder -> b-fmtLn = fromBuilder . (<> "\n")-{-# INLINE fmtLn #-}--------------------------------------------------------------------------------- Text formatters-------------------------------------------------------------------------------{- |-Indent a block of text.-->>> fmt $ "This is a list:\n" <> indentF 4 (blockListF [1,2,3])-This is a list:-    - 1-    - 2-    - 3--The output will always end with a newline, even when the input doesn't.--}-indentF :: Int -> Builder -> Builder-indentF n a = case TL.lines (toLazyText a) of-    [] -> fromLazyText (spaces <> "\n")-    xs -> fromLazyText $ TL.unlines (map (spaces <>) xs)-  where-    spaces = TL.replicate (fromIntegral n) (TL.singleton ' ')--{- | Attach a name to anything:-->>> fmt $ nameF "clients" $ blockListF ["Alice", "Bob", "Zalgo"]-clients:-  - Alice-  - Bob-  - Zalgo--}-nameF :: Builder -> Builder -> Builder-nameF k v = case TL.lines (toLazyText v) of-    []  -> k <> ":\n"-    [l] -> k <> ": " <> fromLazyText l <> "\n"-    ls  -> k <> ":\n" <>-           mconcat ["  " <> fromLazyText s <> "\n" | s <- ls]--{- | Put words between elements.-->>> fmt $ unwordsF ["hello", "world"]-hello world--Of course, it works on anything 'Buildable':-->>> fmt $ unwordsF [1, 2]-1 2--}-unwordsF :: (Foldable f, Buildable a) => f a -> Builder-unwordsF = mconcat . intersperse " " . map build . toList--{-# SPECIALIZE unwordsF :: Buildable a => [a] -> Builder #-}--{- | Arrange elements on separate lines.-->>> fmt $ unlines ["hello", "world"]-hello-world--}-unlinesF :: (Foldable f, Buildable a) => f a -> Builder-unlinesF = mconcat . map (nl . build) . toList-  where-    nl x | "\n" `TL.isSuffixOf` toLazyText x = x-         | otherwise = x <> "\n"--{-# SPECIALIZE unlinesF :: Buildable a => [a] -> Builder #-}--------------------------------------------------------------------------------- List formatters-------------------------------------------------------------------------------{- | A simple comma-separated list formatter.-->>> listF ["hello", "world"]-"[hello, world]"--For multiline output, use 'jsonListF'.--}-listF :: (Foldable f, Buildable a) => f a -> Builder-listF = listF' build-{-# INLINE listF #-}--{- | A version of 'listF' that lets you supply your own building function for-list elements.--For instance, to format a list of lists you'd have to do this (since there's-no 'Buildable' instance for lists):-->>> listF' listF [[1,2,3],[4,5,6]]-"[[1, 2, 3], [4, 5, 6]]"--}-listF' :: (Foldable f) => (a -> Builder) -> f a -> Builder-listF' fbuild xs = mconcat $-  "[" :-  intersperse ", " (map fbuild (toList xs)) ++-  ["]"]--{-# SPECIALIZE listF' :: (a -> Builder) -> [a] -> Builder #-}--{- Note [Builder appending]-~~~~~~~~~~~~~~~~~~~~~~~~~~~--The documentation for 'Builder' says that it's preferrable to associate-'Builder' appends to the right (i.e. @a <> (b <> c)@). The maximum possible-association-to-the-right is achieved when we avoid appending builders until-the last second (i.e. in the latter scenario):--    -- (a1 <> x) <> (a2 <> x) <> ...-    mconcat [a <> x | a <- as]--    -- a1 <> x <> a2 <> x <> ...-    mconcat $ concat [[a, x] | a <- as]--However, benchmarks have shown that the former way is actually faster.--}--{- | A multiline formatter for lists.-->>> fmt $ blockListF [1,2,3]-- 1-- 2-- 3--Multi-line elements are indented correctly:--@->>> __fmt $ blockListF ["hello\\nworld", "foo\\nbar\\nquix"]__-- hello-  world-- foo-  bar-  quix-@--}-blockListF :: forall f a. (Foldable f, Buildable a) => f a -> Builder-blockListF = blockListF' "-" build-{-# INLINE blockListF #-}--{- | A version of 'blockListF' that lets you supply your own building function-for list elements (instead of 'build') and choose the bullet character-(instead of @"-"@).--}-blockListF'-  :: forall f a. Foldable f-  => T.Text                     -- ^ Bullet-  -> (a -> Builder)             -- ^ Builder for elements-  -> f a                        -- ^ Structure with elements-  -> Builder-blockListF' bullet fbuild xs = if null items then "[]\n" else mconcat items-  where-    items = map buildItem (toList xs)-    spaces = mconcat $ replicate (T.length bullet + 1) (singleton ' ')-    buildItem x = case TL.lines (toLazyText (fbuild x)) of-      []     -> bullet |+ "\n"-      (l:ls) -> bullet |+ " " +| l |+ "\n" <>-                mconcat [spaces <> fromLazyText s <> "\n" | s <- ls]--{-# SPECIALIZE blockListF' :: T.Text -> (a -> Builder) -> [a] -> Builder #-}--{- | A JSON-style formatter for lists.-->>> fmt $ jsonListF [1,2,3]-[-  1-, 2-, 3-]--Like 'blockListF', it handles multiline elements well:-->>> fmt $ jsonListF ["hello\nworld", "foo\nbar\nquix"]-[-  hello-  world-, foo-  bar-  quix-]--}-jsonListF :: forall f a. (Foldable f, Buildable a) => f a -> Builder-jsonListF = jsonListF' build-{-# INLINE jsonListF #-}--{- | A version of 'jsonListF' that lets you supply your own building function-for list elements.--}-jsonListF' :: forall f a. (Foldable f) => (a -> Builder) -> f a -> Builder-jsonListF' fbuild xs-  | null items = "[]\n"-  | otherwise  = "[\n" <> mconcat items <> "]\n"-  where-    items = zipWith buildItem (True : repeat False) (toList xs)-    -- Item builder-    buildItem :: Bool -> a -> Builder-    buildItem isFirst x =-      case map fromLazyText (TL.lines (toLazyText (fbuild x))) of-        [] | isFirst   -> "\n"-           | otherwise -> ",\n"-        ls ->-            mconcat . map (<> "\n") $-              ls & _head %~ (if isFirst then ("  " <>) else (", " <>))-                 & _tail.each %~ ("  " <>)--{-# SPECIALIZE jsonListF' :: (a -> Builder) -> [a] -> Builder #-}--------------------------------------------------------------------------------- Map formatters-------------------------------------------------------------------------------#if __GLASGOW_HASKELL__ >= 708-#  define MAPTOLIST IsList.toList-#else-#  define MAPTOLIST id-#endif--{- | A simple JSON-like map formatter; works for Map, HashMap, etc, as well as-ordinary lists of pairs.-->>> mapF [("a", 1), ("b", 4)]-"{a: 1, b: 4}"--For multiline output, use 'jsonMapF'.--}-mapF ::-#if __GLASGOW_HASKELL__ >= 708-    (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder-#else-    (Buildable k, Buildable v) => [(k, v)] -> Builder-#endif-mapF = mapF' build build-{-# INLINE mapF #-}--{- | A version of 'mapF' that lets you supply your own building function for-keys and values.--}-mapF' ::-#if __GLASGOW_HASKELL__ >= 708-    (IsList t, Item t ~ (k, v)) =>-    (k -> Builder) -> (v -> Builder) -> t -> Builder-#else-    (k -> Builder) -> (v -> Builder) -> [(k, v)] -> Builder-#endif-mapF' fbuild_k fbuild_v xs =-  "{" <> mconcat (intersperse ", " (map buildPair (MAPTOLIST xs))) <> "}"-  where-    buildPair (k, v) = fbuild_k k <> ": " <> fbuild_v v--{- | A YAML-like map formatter:-->>> fmt $ blockMapF [("Odds", blockListF [1,3]), ("Evens", blockListF [2,4])]-Odds:-  - 1-  - 3-Evens:-  - 2-  - 4--}-blockMapF ::-#if __GLASGOW_HASKELL__ >= 708-    (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder-#else-    (Buildable k, Buildable v) => [(k, v)] -> Builder-#endif-blockMapF = blockMapF' build build-{-# INLINE blockMapF #-}--{- | A version of 'blockMapF' that lets you supply your own building function-for keys and values.--}-blockMapF' ::-#if __GLASGOW_HASKELL__ >= 708-    (IsList t, Item t ~ (k, v)) =>-    (k -> Builder) -> (v -> Builder) -> t -> Builder-#else-    (k -> Builder) -> (v -> Builder) -> [(k, v)] -> Builder-#endif-blockMapF' fbuild_k fbuild_v xs-  | null items = "{}\n"-  | otherwise  = mconcat items-  where-    items = map (\(k, v) -> nameF (fbuild_k k) (fbuild_v v)) (MAPTOLIST xs)--{- | A JSON-like map formatter (unlike 'mapF', always multiline):-->>> fmt $ jsonMapF [("Odds", jsonListF [1,3]), ("Evens", jsonListF [2,4])]-{-  Odds:-    [-      1-    , 3-    ]-, Evens:-    [-      2-    , 4-    ]-}--}-jsonMapF ::-#if __GLASGOW_HASKELL__ >= 708-    (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder-#else-    (Buildable k, Buildable v) => [(k, v)] -> Builder-#endif-jsonMapF = jsonMapF' build build-{-# INLINE jsonMapF #-}--{- | A version of 'jsonMapF' that lets you supply your own building function-for keys and values.--}-jsonMapF' ::-#if __GLASGOW_HASKELL__ >= 708-    forall t k v.-    (IsList t, Item t ~ (k, v)) =>-    (k -> Builder) -> (v -> Builder) -> t -> Builder-#else-    forall k v.-    (k -> Builder) -> (v -> Builder) -> [(k, v)] -> Builder-#endif-jsonMapF' fbuild_k fbuild_v xs-  | null items = "{}\n"-  | otherwise  = "{\n" <> mconcat items <> "}\n"-  where-    items = zipWith buildItem (True : repeat False) (MAPTOLIST xs)-    -- Item builder-    buildItem :: Bool -> (k, v) -> Builder-    buildItem isFirst (k, v) = do-      let kb = (if isFirst then "  " else ", ") <> fbuild_k k-      case map fromLazyText (TL.lines (toLazyText (fbuild_v v))) of-        []  -> kb <> ":\n"-        [l] -> kb <> ": " <> l <> "\n"-        ls  -> kb <> ":\n" <>-               mconcat ["    " <> s <> "\n" | s <- ls]--------------------------------------------------------------------------------- Tuple formatters-------------------------------------------------------------------------------instance (Buildable a1, Buildable a2)-  => TupleF (a1, a2) where-  tupleF (a1, a2) = tupleLikeF-    [build a1, build a2]--instance (Buildable a1, Buildable a2, Buildable a3)-  => TupleF (a1, a2, a3) where-  tupleF (a1, a2, a3) = tupleLikeF-    [build a1, build a2, build a3]--instance (Buildable a1, Buildable a2, Buildable a3, Buildable a4)-  => TupleF (a1, a2, a3, a4) where-  tupleF (a1, a2, a3, a4) = tupleLikeF-    [build a1, build a2, build a3, build a4]--instance (Buildable a1, Buildable a2, Buildable a3, Buildable a4,-          Buildable a5)-  => TupleF (a1, a2, a3, a4, a5) where-  tupleF (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)-  => TupleF (a1, a2, a3, a4, a5, a6) where-  tupleF (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)-  => TupleF (a1, a2, a3, a4, a5, a6, a7) where-  tupleF (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)-  => TupleF (a1, a2, a3, a4, a5, a6, a7, a8) where-  tupleF (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]--{- |-Format a list like a tuple. (This function is used to define 'tupleF'.)--}-tupleLikeF :: [Builder] -> Builder-tupleLikeF xs-  | True `elem` mls = mconcat (intersperse ",\n" items)-  | otherwise = "(" <> mconcat (intersperse ", " xs) <> ")"-  where-    (mls, items) = unzip $ zipWith3 buildItem-                             xs (set _head True falses) (set _last True falses)-    -- A list of 'False's which has the same length as 'xs'-    falses = map (const False) xs-    -- Returns 'True' if the item is multiline-    buildItem :: Builder-              -> Bool              -- ^ Is the item the first?-              -> Bool              -- ^ Is the item the last?-              -> (Bool, Builder)-    buildItem x isFirst isLast =-      case map fromLazyText (TL.lines (toLazyText x)) of-        [] | isFirst && isLast -> (False, "()\n")-           | isFirst           -> (False, "(\n")-           |            isLast -> (False, "  )\n")-           | otherwise         -> (False, "")-        ls ->-           (not (null (tail ls)),-            mconcat . map (<> "\n") $-              ls & _head %~ (if isFirst then ("( " <>) else ("  " <>))-                 & _tail.each %~ ("  " <>)-                 & _last %~ (if isLast then (<> " )") else id))--------------------------------------------------------------------------------- ADT formatters-------------------------------------------------------------------------------{- | Like 'build' for 'Maybe', but displays 'Nothing' as @\<Nothing\>@ instead-of an empty string.--'build':-->>> build (Nothing :: Maybe Int)-""->>> build (Just 1 :: Maybe Int)-"1"--'maybeF':-->>> maybeF (Nothing :: Maybe Int)-"<Nothing>"->>> maybeF (Just 1 :: Maybe Int)-"1"--}-maybeF :: Buildable a => Maybe a -> Builder-maybeF = maybe "<Nothing>" build--{- |-Format an 'Either':-->>> eitherF (Right 1)-"<Right: 1>"--}-eitherF :: (Buildable a, Buildable b) => Either a b -> Builder-eitherF = either (\x -> "<Left: " <> build x <> ">")-                 (\x -> "<Right: " <> build x <> ">")--------------------------------------------------------------------------------- Other formatters-------------------------------------------------------------------------------{- |-Take the first N characters:-->>> prefixF 3 "hello"-"hel"--}-prefixF :: Buildable a => Int -> a -> Builder-prefixF size =-  fromLazyText . TL.take (fromIntegral size) . toLazyText . build--{- |-Take the last N characters:-->>> suffixF 3 "hello"-"llo"--}-suffixF :: Buildable a => Int -> a -> Builder-suffixF size =-  fromLazyText .-  (\t -> TL.drop (TL.length t - fromIntegral size) t) .-  toLazyText . build--{- |-@padLeftF n c@ pads the string with character @c@ from the left side until it-becomes @n@ characters wide (and does nothing if the string is already that-long, or longer):-->>> padLeftF 5 '0' 12-"00012"->>> padLeftF 5 '0' 123456-"123456"--}-padLeftF :: Buildable a => Int -> Char -> a -> Builder-padLeftF = TF.left--{- |-@padRightF n c@ pads the string with character @c@ from the right side until-it becomes @n@ characters wide (and does nothing if the string is already-that long, or longer):-->>> padRightF 5 ' ' "foo"-"foo  "->>> padRightF 5 ' ' "foobar"-"foobar"--}-padRightF :: Buildable a => Int -> Char -> a -> Builder-padRightF = TF.right--{- |-@padBothF n c@ pads the string with character @c@ from both sides until-it becomes @n@ characters wide (and does nothing if the string is already-that long, or longer):-->>> padBothF 5 '=' "foo"-"=foo="->>> padBothF 5 '=' "foobar"-"foobar"--When padding can't be distributed equally, the left side is preferred:-->>> padBoth 8 '=' "foo"-"===foo=="--}-padBothF :: Buildable a => Int -> Char -> a -> Builder-padBothF i c =-  fromLazyText . TL.center (fromIntegral i) c . toLazyText . build--{- |-Break digits in a number:-->>> commaizeF 15830000-"15,830,000"--}-commaizeF :: (Buildable a, Integral a) => a -> Builder-commaizeF = groupInt 3 ','--{- |-Format a number as octal:-->>> listF' octF [7,8,9,10]-"[7, 10, 11, 12]"--}-octF :: Integral a => a -> Builder-octF = baseF 8--{- |-Format a number as binary:-->>> listF' binF [7,8,9,10]-"[111, 1000, 1001, 1010]"--}-binF :: Integral a => a -> Builder-binF = baseF 2--{- |-Format a number in arbitrary base (up to 36):-->>> baseF 3 10000-"111201101"->>> baseF 7 10000-"41104"->>> baseF 36 10000-"7ps"--}-baseF :: Integral a => Int -> a -> Builder-baseF numBase = build . atBase numBase--{- |-Format a floating-point number:-->>> floatF 3.1415-"3.1415"--Numbers bigger than 1e21 or smaller than 1e-6 will be displayed using-scientific notation:-->>> listF' floatF [1e-6,9e-7]-"[0.000001, 9e-7]"->>> listF' floatF [9e20,1e21]-"[900000000000000000000, 1e21]"--}-floatF :: Real a => a -> Builder-floatF = TF.shortest--{- |-Format a floating-point number using scientific notation, with given amount-of precision:-->>> listF' (exptF 5) [pi,0.1,10]-"[3.14159e0, 1.00000e-1, 1.00000e1]"--}-exptF :: Real a => Int -> a -> Builder-exptF = TF.expt--{- |-Format a floating-point number with given amount of precision.--For small numbers, it uses scientific notation for everything smaller than-1e-6:-->>> 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]-"[1000, 5000, 1.000e4]"--}-precF :: Real a => Int -> a -> Builder-precF = TF.prec--------------------------------------------------------------------------------- Conditional formatters-------------------------------------------------------------------------------{- |-Display something only if the condition is 'True' (empty string otherwise).--@->>> __"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:--@->>> __"Maybe here's a number: "+|whenF cond (fmt n)|+""__-@--}-whenF :: Bool -> Builder -> Builder-whenF True  x = x-whenF False _ = mempty-{-# INLINE whenF #-}--{- |-Display something only if the condition is 'False' (empty string otherwise).--}-unlessF :: Bool -> Builder -> Builder-unlessF False x = x-unlessF True  _ = mempty-{-# INLINE unlessF #-}--------------------------------------------------------------------------------- Generic formatting-------------------------------------------------------------------------------{- | Format an arbitrary value without requiring a 'Buildable' instance:--@-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--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+module Fmt+(+  -- * Overloaded strings+  -- $overloadedstrings++  -- * Examples+  -- $examples++  -- * Migration guide from @formatting@+  -- $migration++  -- * Basic formatting+  -- $brackets++  -- ** Ordinary brackets+  -- $god1+  (+|),+  (|+),+  -- ** 'Show' brackets+  -- $god2+  (+||),+  (||+),+  -- ** Combinations+  -- $god3+  (|++|),+  (||++||),+  (|++||),+  (||++|),++  -- * Old-style formatting+  format,+  formatLn,+  Format,++  -- * Helper functions+  fmt,+  fmtLn,++  Builder,+  Buildable(..),++  -- * Formatters++  -- ** Time+  module Fmt.Time,++  -- ** Text+  indentF, indentF',+  nameF,+  unwordsF,+  unlinesF,++  -- ** Lists+  listF, listF',+  blockListF, blockListF',+  jsonListF, jsonListF',++  -- ** Maps+  mapF, mapF',+  blockMapF, blockMapF',+  jsonMapF, jsonMapF',++  -- ** Tuples+  tupleF,++  -- ** ADTs+  maybeF,+  eitherF,++  -- ** Padding/trimming+  prefixF,+  suffixF,+  padLeftF,+  padRightF,+  padBothF,++  -- ** Hex+  hexF,++  -- ** Bytestrings+  base64F,+  base64UrlF,++  -- ** Integers+  ordinalF,+  commaizeF,+  -- *** Base conversion+  octF,+  binF,+  baseF,++  -- ** Floating-point+  floatF,+  exptF,+  fixedF,++  -- ** Conditional formatting+  whenF,+  unlessF,++  -- ** Generic formatting+  genericF,+)+where+++import Formatting.Buildable (Buildable(..))+import Data.Text.Lazy.Builder++import Fmt.Internal+import Fmt.Time+++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> :set -XDeriveGeneric+-- >>> import Data.Text (Text)++{- $overloadedstrings++You need @OverloadedStrings@ enabled to use this library. There are three+ways to do it:++  * __In GHCi:__ do @:set -XOverloadedStrings@.++  * __In a module:__ add @\{\-\# LANGUAGE OverloadedStrings \#\-\}@+    to the beginning of your module.++  * __In a project:__ add @OverloadedStrings@ to the @default-extensions@+    section of your @.cabal@ file.+-}++{- $examples++Here's a bunch of examples because some people learn better by looking at+examples.++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: foo, bar\nAlso a number: 25"++Print it:++>>> fmtLn ("Here are some words: "+|a|+", "+|b|+"\nAlso a number: "+|n|+"")+Here are some words: foo, bar+Also a number: 25++Format a list in various ways:++>>> let xs = ["John", "Bob"]++>>> fmtLn ("Using show: "+||xs||+"\nUsing listF: "+|listF xs|+"")+Using show: ["John","Bob"]+Using listF: [John, Bob]++>>> fmt ("YAML-like:\n"+|blockListF xs|+"")+YAML-like:+- John+- Bob++>>> fmt ("JSON-like: "+|jsonListF xs|+"")+JSON-like: [+  John+, Bob+]++-}++{- $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@)++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.)+-}++----------------------------------------------------------------------------+-- Documentation for operators+----------------------------------------------------------------------------++{- $brackets++To format strings, put variables between ('+|') and ('|+'):++>>> let name = "Alice" :: String+>>> "Meet "+|name|+"!" :: String+"Meet Alice!"++Of course, 'Text' is supported as well:++>>> "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|+"!")+Meet Alice!++Otherwise the type of the formatted string would be resolved to @IO ()@ and+printed without a newline, which is not very convenient when you're in GHCi.+On the other hand, it's useful for quick-and-dirty scripts:++@+main = do+  [fin, fout] \<- words \<$\> getArgs+  __"Reading data from "+|fin|+"\\n"__+  xs \<- readFile fin+  __"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 ('|+'):++>>> let starCount = "173"+>>> 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+conversion, you can use any functions you want inside them. In this case,+'length':++>>> 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|+"")+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|+"")+Character's position: (3, 5)++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|+"")+2*2 = 4++Also, since in some codebases there are /lots/ of types which aren't+'Buildable', there are operators ('+||') and ('||+'), which use 'show'+instead of 'build':++prop> (""+|show foo|++|show bar|+"") == (""+||foo||++||bar||+"")+-}++-- $god1+-- Operators for the operators god!++-- $god2+-- More operators for the operators god!++{- $god3++Z̸͠A̵̕͟͠L̡̀́͠G̶̛O͝ ̴͏̀ I͞S̸̸̢͠  ̢̛͘͢C̷͟͡Ó̧̨̧͞M̡͘͟͞I̷͜N̷̕G̷̀̕++(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.)+-}  ---------------------------------------------------------------------------- -- TODOs
lib/Fmt/Internal.hs view
@@ -1,109 +1,54 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE CPP #-} --- for FormatAsHex and FormatType-#if __GLASGOW_HASKELL__ < 710-{-# LANGUAGE OverlappingInstances #-}-#  define _OVERLAPPING_-#  define _OVERLAPPABLE_-#  define _OVERLAPS_-#else-#  define _OVERLAPPING_ {-# OVERLAPPING #-}-#  define _OVERLAPPABLE_ {-# OVERLAPPABLE #-}-#  define _OVERLAPS_ {-# OVERLAPS #-}-#endif  {- | A module providing access to internals (in case you really need them). Can change at any time, though probably won't.--It also provides some functions that are used in 'Fmt.Time' (so that-'Fmt.Time' wouldn't need to import 'Fmt'). -} module Fmt.Internal (   -- * Classes-  FromBuilder(..),   FormatAsHex(..),   FormatAsBase64(..),-  TupleF(..), -  -- * Classes used for 'genericF'-  GBuildable(..),-  GetFields(..),-  Buildable'(..),--  -- * Polyvariadic 'format'-  FormatType(..),-  -  -- * Helpers-  groupInt,-  atBase,-  showSigned',-  intToDigit',-  indentF',--  -- * Functions used in 'Fmt.Time'-  fixedF,-  ordinalF,+  -- * Reexports+  module Fmt.Internal.Core,+  module Fmt.Internal.Formatters,+  module Fmt.Internal.Template,+  module Fmt.Internal.Tuple,+  module Fmt.Internal.Numeric,+  module Fmt.Internal.Generic, ) where --- Generic useful things-import Data.Monoid-import Numeric-import Data.Char+ -- Text-import qualified Data.Text as T import qualified Data.Text.Encoding as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.IO as TL import qualified Data.Text.Lazy.Encoding as TL--- 'Buildable' and text-format-import Data.Text.Buildable-import qualified Data.Text.Format as TF+-- 'Buildable' and raw 'Builder' formatters+import qualified Formatting.Internal.Raw as F -- Text 'Builder'-import Data.Text.Lazy.Builder hiding (fromString)+import           Data.Text.Lazy.Builder hiding (fromString) -- Bytestring import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL -- Formatting bytestrings-import qualified Data.ByteString.Base16          as B16-import qualified Data.ByteString.Base16.Lazy     as B16L+import qualified Data.ByteString.Builder         as BB import qualified Data.ByteString.Base64          as B64 import qualified Data.ByteString.Base64.Lazy     as B64L import qualified Data.ByteString.Base64.URL      as B64U import qualified Data.ByteString.Base64.URL.Lazy as B64UL -------------------------------------------------------------------------------- FromBuilder-------------------------------------------------------------------------------class FromBuilder a where-  -- | Convert a 'Builder' to something else.-  fromBuilder :: Builder -> a--instance FromBuilder Builder where-  fromBuilder = id-  {-# INLINE fromBuilder #-}--instance (a ~ Char) => FromBuilder [a] where-  fromBuilder = TL.unpack . toLazyText-  {-# INLINE fromBuilder #-}--instance FromBuilder T.Text where-  fromBuilder = TL.toStrict . toLazyText-  {-# INLINE fromBuilder #-}--instance FromBuilder TL.Text where-  fromBuilder = toLazyText-  {-# INLINE fromBuilder #-}+import Fmt.Internal.Core+import Fmt.Internal.Formatters+import Fmt.Internal.Template+import Fmt.Internal.Tuple+import Fmt.Internal.Numeric+import Fmt.Internal.Generic -instance (a ~ ()) => FromBuilder (IO a) where-  fromBuilder = TL.putStr . toLazyText-  {-# INLINE fromBuilder #-}+-- $setup+-- >>> import Fmt  ---------------------------------------------------------------------------- -- Hex@@ -121,13 +66,13 @@   hexF :: a -> Builder  instance FormatAsHex BS.ByteString where-  hexF = fromText . T.decodeLatin1 . B16.encode+  hexF = fromLazyText . TL.decodeLatin1 . BB.toLazyByteString . BB.byteStringHex  instance FormatAsHex BSL.ByteString where-  hexF = fromLazyText . TL.decodeLatin1 . B16L.encode+  hexF = fromLazyText . TL.decodeLatin1 . BB.toLazyByteString . BB.lazyByteStringHex -instance _OVERLAPPABLE_ Integral a => FormatAsHex a where-  hexF = TF.hex+instance {-# OVERLAPPABLE #-} Integral a => FormatAsHex a where+  hexF = F.hex  ---------------------------------------------------------------------------- -- Base64@@ -157,142 +102,3 @@ instance FormatAsBase64 BSL.ByteString where   base64F    = fromLazyText . TL.decodeLatin1 . B64L.encode   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--------------------------------------------------------------------------------- Classes used for polyvariadic 'format'--------------------------------------------------------------------------------- | Something like 'Text.Printf.PrintfType' in "Text.Printf".-class FormatType r where-  format' :: TF.Format -> [Builder] -> r--instance (Buildable a, FormatType r) => FormatType (a -> r) where-  format' f xs = \x -> format' f (build x : xs)--instance _OVERLAPPABLE_ FromBuilder r => FormatType r where-  format' f xs = fromBuilder $ TF.build f (reverse xs)--------------------------------------------------------------------------------- Helpers-------------------------------------------------------------------------------groupInt :: (Buildable a, Integral a) => Int -> Char -> a -> Builder-groupInt 0 _ n = build n-groupInt i c n =-    fromLazyText . TL.reverse .-    foldr merge "" .-    TL.zip (zeros <> cycle' zeros') .-    TL.reverse .-    toLazyText . build-      $ n-  where-    zeros = TL.replicate (fromIntegral i) (TL.singleton '0')-    zeros' = TL.singleton c <> TL.tail zeros-    merge (f, c') rest-      | f == c = TL.singleton c <> TL.singleton c' <> rest-      | otherwise = TL.singleton c' <> rest-    cycle' xs = xs <> cycle' xs-    -- Suppress the warning about redundant Integral constraint-    _ = toInteger n--atBase :: Integral a => Int -> a -> String-atBase b _ | b < 2 || b > 36 = error ("baseF: Invalid base " ++ show b)-atBase b n =-  showSigned' (showIntAtBase (toInteger b) intToDigit') (toInteger n) ""-{-# INLINE atBase #-}--showSigned' :: Real a => (a -> ShowS) -> a -> ShowS-showSigned' f n-  | n < 0     = showChar '-' . f (negate n)-  | otherwise = f n--intToDigit' :: Int -> Char-intToDigit' i-  | i >= 0  && i < 10 = chr (ord '0' + i)-  | i >= 10 && i < 36 = chr (ord 'a' + i - 10)-  | otherwise = error ("intToDigit': Invalid int " ++ show i)--{- | 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.--}-indentF' :: Int -> T.Text -> Builder -> Builder-indentF' n pref a = case TL.lines (toLazyText a) of-  []     -> fromText pref <> "\n"-  (x:xs) -> fromLazyText $-            TL.unlines $ (TL.fromStrict pref <> x) : map (spaces <>) xs-  where-    spaces = TL.replicate (fromIntegral n) (TL.singleton ' ')--------------------------------------------------------------------------------- Functions used in Fmt.Time-------------------------------------------------------------------------------{- |-Format a floating-point number without scientific notation:-->>> listF' (fixedF 5) [pi,0.1,10]-"[3.14159, 0.10000, 10.00000]"--}-fixedF :: Real a => Int -> a -> Builder-fixedF = TF.fixed--{- |-Add an ordinal suffix to a number:-->>> ordinalF 15-"15th"->>> ordinalF 22-"22nd"--}-ordinalF :: (Buildable a, Integral a) => a -> Builder-ordinalF n-  | tens > 3 && tens < 21 = build n <> "th"-  | otherwise = build n <> case n `mod` 10 of-                             1 -> "st"-                             2 -> "nd"-                             3 -> "rd"-                             _ -> "th"-  where-    tens = n `mod` 100
+ lib/Fmt/Internal/Core.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+++module Fmt.Internal.Core where++#if __GLASGOW_HASKELL__ < 804+import           Data.Monoid ((<>))+#endif+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.IO as TL+import           Data.Text.Lazy.Builder hiding (fromString)+import           Formatting.Buildable (Buildable(..))+++----------------------------------------------------------------------------+-- Class+----------------------------------------------------------------------------++class FromBuilder a where+  -- | Convert a 'Builder' to something else.+  fromBuilder :: Builder -> a++instance FromBuilder Builder where+  fromBuilder = id+  {-# INLINE fromBuilder #-}++instance (a ~ Char) => FromBuilder [a] where+  fromBuilder = TL.unpack . toLazyText+  {-# INLINE fromBuilder #-}++instance FromBuilder T.Text where+  fromBuilder = TL.toStrict . toLazyText+  {-# INLINE fromBuilder #-}++instance FromBuilder TL.Text where+  fromBuilder = toLazyText+  {-# INLINE fromBuilder #-}++instance (a ~ ()) => FromBuilder (IO a) where+  fromBuilder = TL.putStr . toLazyText+  {-# INLINE fromBuilder #-}++----------------------------------------------------------------------------+-- Operators+----------------------------------------------------------------------------++-- | Concatenate, then convert.+(+|) :: (FromBuilder b) => Builder -> Builder -> b+(+|) str rest = fromBuilder (str <> rest)++-- | 'build' and concatenate, then convert.+(|+) :: (Buildable a, FromBuilder b) => a -> Builder -> b+(|+) a rest = fromBuilder (build a <> rest)++infixr 1 +|+infixr 1 |+++-- | Concatenate, then convert.+(+||) :: (FromBuilder b) => Builder -> Builder -> b+(+||) str rest = str +| rest+{-# INLINE (+||) #-}++-- | 'show' and concatenate, then convert.+(||+) :: (Show a, FromBuilder b) => a -> Builder -> b+(||+) a rest = show a |+ rest+{-# INLINE (||+) #-}++infixr 1 +||+infixr 1 ||+++(|++|) :: (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 |++||++----------------------------------------------------------------------------+-- Functions+----------------------------------------------------------------------------++{- | '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+like 'listF' can only produce 'Builder' (for better type inference), and you+need to use 'fmt' on them.++Also, 'fmt' can do printing:++>>> fmt "Hello world!\n"+Hello world!+-}+fmt :: FromBuilder b => Builder -> b+fmt = fromBuilder+{-# INLINE fmt #-}++{- | Like 'fmt', but appends a newline.+-}+fmtLn :: FromBuilder b => Builder -> b+fmtLn = fromBuilder . (<> "\n")+{-# INLINE fmtLn #-}
+ lib/Fmt/Internal/Formatters.hs view
@@ -0,0 +1,471 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+++module Fmt.Internal.Formatters where+++-- Generic useful things+import Data.List+import Lens.Micro+#if __GLASGOW_HASKELL__ < 804+import Data.Monoid ((<>))+#endif+-- Text+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import Data.Text (Text)+-- 'Buildable' and text-format stuff+import Formatting.Buildable+import qualified Formatting.Internal.Raw as F+-- Text 'Builder'+import Data.Text.Lazy.Builder hiding (fromString)+-- 'Foldable' and 'IsList' for list/map formatters+import Data.Foldable (toList)+import GHC.Exts (IsList, Item)+import qualified GHC.Exts as IsList (toList)++import Fmt.Internal.Core+++----------------------------------------------------------------------------+-- Text formatters+----------------------------------------------------------------------------++{- |+Indent a block of text.++>>> fmt $ "This is a list:\n" <> indentF 4 (blockListF [1,2,3])+This is a list:+    - 1+    - 2+    - 3++The output will always end with a newline, even when the input doesn't.+-}+indentF :: Int -> Builder -> Builder+indentF n a = case TL.lines (toLazyText a) of+    [] -> fromLazyText (spaces <> "\n")+    xs -> fromLazyText $ TL.unlines (map (spaces <>) xs)+  where+    spaces = TL.replicate (fromIntegral n) (TL.singleton ' ')++{- | 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.+-}+indentF' :: Int -> T.Text -> Builder -> Builder+indentF' n pref a = case TL.lines (toLazyText a) of+  []     -> fromText pref <> "\n"+  (x:xs) -> fromLazyText $+            TL.unlines $ (TL.fromStrict pref <> x) : map (spaces <>) xs+  where+    spaces = TL.replicate (fromIntegral n) (TL.singleton ' ')++{- | Attach a name to anything:++>>> fmt $ nameF "clients" $ blockListF ["Alice", "Bob", "Zalgo"]+clients:+  - Alice+  - Bob+  - Zalgo+-}+nameF :: Builder -> Builder -> Builder+nameF k v = case TL.lines (toLazyText v) of+    []  -> k <> ":\n"+    [l] -> k <> ": " <> fromLazyText l <> "\n"+    ls  -> k <> ":\n" <>+           mconcat ["  " <> fromLazyText s <> "\n" | s <- ls]++{- | Put words between elements.++>>> fmt $ unwordsF ["hello", "world"]+hello world++Of course, it works on anything 'Buildable':++>>> fmt $ unwordsF [1, 2]+1 2+-}+unwordsF :: (Foldable f, Buildable a) => f a -> Builder+unwordsF = mconcat . intersperse " " . map build . toList++{-# SPECIALIZE unwordsF :: Buildable a => [a] -> Builder #-}++{- | Arrange elements on separate lines.++>>> fmt $ unlinesF ["hello", "world"]+hello+world+-}+unlinesF :: (Foldable f, Buildable a) => f a -> Builder+unlinesF = mconcat . map (nl . build) . toList+  where+    nl x | "\n" `TL.isSuffixOf` toLazyText x = x+         | otherwise = x <> "\n"++{-# SPECIALIZE unlinesF :: Buildable a => [a] -> Builder #-}++----------------------------------------------------------------------------+-- List formatters+----------------------------------------------------------------------------++{- | A simple comma-separated list formatter.++>>> listF ["hello", "world"]+"[hello, world]"++For multiline output, use 'jsonListF'.+-}+listF :: (Foldable f, Buildable a) => f a -> Builder+listF = listF' build+{-# INLINE listF #-}++{- | A version of 'listF' that lets you supply your own building function for+list elements.++For instance, to format a list of lists you'd have to do this (since there's+no 'Buildable' instance for lists):++>>> listF' listF [[1,2,3],[4,5,6]]+"[[1, 2, 3], [4, 5, 6]]"+-}+listF' :: (Foldable f) => (a -> Builder) -> f a -> Builder+listF' fbuild xs = mconcat $+  "[" :+  intersperse ", " (map fbuild (toList xs)) +++  ["]"]++{-# SPECIALIZE listF' :: (a -> Builder) -> [a] -> Builder #-}++{- Note [Builder appending]+~~~~~~~~~~~~~~~~~~~~~~~~~~~++The documentation for 'Builder' says that it's preferrable to associate+'Builder' appends to the right (i.e. @a <> (b <> c)@). The maximum possible+association-to-the-right is achieved when we avoid appending builders until+the last second (i.e. in the latter scenario):++    -- (a1 <> x) <> (a2 <> x) <> ...+    mconcat [a <> x | a <- as]++    -- a1 <> x <> a2 <> x <> ...+    mconcat $ concat [[a, x] | a <- as]++However, benchmarks have shown that the former way is actually faster.+-}++{- | A multiline formatter for lists.++>>> fmt $ blockListF [1,2,3]+- 1+- 2+- 3++Multi-line elements are indented correctly:++>>> fmt $ blockListF ["hello\nworld", "foo\nbar\nquix"]+- hello+  world+- foo+  bar+  quix++-}+blockListF :: forall f a. (Foldable f, Buildable a) => f a -> Builder+blockListF = blockListF' "-" build+{-# INLINE blockListF #-}++{- | A version of 'blockListF' that lets you supply your own building function+for list elements (instead of 'build') and choose the bullet character+(instead of @"-"@).+-}+blockListF'+  :: forall f a. Foldable f+  => Text                       -- ^ Bullet+  -> (a -> Builder)             -- ^ Builder for elements+  -> f a                        -- ^ Structure with elements+  -> Builder+blockListF' bullet fbuild xs = if null items then "[]\n" else mconcat items+  where+    items = map buildItem (toList xs)+    spaces = mconcat $ replicate (T.length bullet + 1) (singleton ' ')+    buildItem x = case TL.lines (toLazyText (fbuild x)) of+      []     -> bullet |+ "\n"+      (l:ls) -> bullet |+ " " +| l |+ "\n" <>+                mconcat [spaces <> fromLazyText s <> "\n" | s <- ls]++{-# SPECIALIZE blockListF' :: Text -> (a -> Builder) -> [a] -> Builder #-}++{- | A JSON-style formatter for lists.++>>> fmt $ jsonListF [1,2,3]+[+  1+, 2+, 3+]++Like 'blockListF', it handles multiline elements well:++>>> fmt $ jsonListF ["hello\nworld", "foo\nbar\nquix"]+[+  hello+  world+, foo+  bar+  quix+]+-}+jsonListF :: forall f a. (Foldable f, Buildable a) => f a -> Builder+jsonListF = jsonListF' build+{-# INLINE jsonListF #-}++{- | A version of 'jsonListF' that lets you supply your own building function+for list elements.+-}+jsonListF' :: forall f a. (Foldable f) => (a -> Builder) -> f a -> Builder+jsonListF' fbuild xs+  | null items = "[]\n"+  | otherwise  = "[\n" <> mconcat items <> "]\n"+  where+    items = zipWith buildItem (True : repeat False) (toList xs)+    -- Item builder+    buildItem :: Bool -> a -> Builder+    buildItem isFirst x =+      case map fromLazyText (TL.lines (toLazyText (fbuild x))) of+        [] | isFirst   -> "\n"+           | otherwise -> ",\n"+        ls ->+            mconcat . map (<> "\n") $+              ls & _head %~ (if isFirst then ("  " <>) else (", " <>))+                 & _tail.each %~ ("  " <>)++{-# SPECIALIZE jsonListF' :: (a -> Builder) -> [a] -> Builder #-}++----------------------------------------------------------------------------+-- Map formatters+----------------------------------------------------------------------------++{- | A simple JSON-like map formatter; works for Map, HashMap, etc, as well as+ordinary lists of pairs.++>>> mapF [("a", 1), ("b", 4)]+"{a: 1, b: 4}"++For multiline output, use 'jsonMapF'.+-}+mapF :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder+mapF = mapF' build build+{-# INLINE mapF #-}++{- | A version of 'mapF' that lets you supply your own building function for+keys and values.+-}+mapF'+  :: (IsList t, Item t ~ (k, v))+  => (k -> Builder) -> (v -> Builder) -> t -> Builder+mapF' fbuild_k fbuild_v xs =+  "{" <> mconcat (intersperse ", " (map buildPair (IsList.toList xs))) <> "}"+  where+    buildPair (k, v) = fbuild_k k <> ": " <> fbuild_v v++{- | A YAML-like map formatter:++>>> fmt $ blockMapF [("Odds", blockListF [1,3]), ("Evens", blockListF [2,4])]+Odds:+  - 1+  - 3+Evens:+  - 2+  - 4+-}+blockMapF :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder+blockMapF = blockMapF' build build+{-# INLINE blockMapF #-}++{- | A version of 'blockMapF' that lets you supply your own building function+for keys and values.+-}+blockMapF'+  :: (IsList t, Item t ~ (k, v))+  => (k -> Builder) -> (v -> Builder) -> t -> Builder+blockMapF' fbuild_k fbuild_v xs+  | null items = "{}\n"+  | otherwise  = mconcat items+  where+    items = map (\(k, v) -> nameF (fbuild_k k) (fbuild_v v)) (IsList.toList xs)++{- | A JSON-like map formatter (unlike 'mapF', always multiline):++>>> fmt $ jsonMapF [("Odds", jsonListF [1,3]), ("Evens", jsonListF [2,4])]+{+  Odds:+    [+      1+    , 3+    ]+, Evens:+    [+      2+    , 4+    ]+}+-}+jsonMapF :: (IsList t, Item t ~ (k, v), Buildable k, Buildable v) => t -> Builder+jsonMapF = jsonMapF' build build+{-# INLINE jsonMapF #-}++{- | A version of 'jsonMapF' that lets you supply your own building function+for keys and values.+-}+jsonMapF'+  :: forall t k v.+     (IsList t, Item t ~ (k, v))+  => (k -> Builder) -> (v -> Builder) -> t -> Builder+jsonMapF' fbuild_k fbuild_v xs+  | null items = "{}\n"+  | otherwise  = "{\n" <> mconcat items <> "}\n"+  where+    items = zipWith buildItem (True : repeat False) (IsList.toList xs)+    -- Item builder+    buildItem :: Bool -> (k, v) -> Builder+    buildItem isFirst (k, v) = do+      let kb = (if isFirst then "  " else ", ") <> fbuild_k k+      case map fromLazyText (TL.lines (toLazyText (fbuild_v v))) of+        []  -> kb <> ":\n"+        [l] -> kb <> ": " <> l <> "\n"+        ls  -> kb <> ":\n" <>+               mconcat ["    " <> s <> "\n" | s <- ls]++----------------------------------------------------------------------------+-- ADT formatters+----------------------------------------------------------------------------++{- | Like 'build' for 'Maybe', but displays 'Nothing' as @\<Nothing\>@ instead+of an empty string.++'build':++>>> build (Nothing :: Maybe Int)+""+>>> build (Just 1 :: Maybe Int)+"1"++'maybeF':++>>> maybeF (Nothing :: Maybe Int)+"<Nothing>"+>>> maybeF (Just 1 :: Maybe Int)+"1"+-}+maybeF :: Buildable a => Maybe a -> Builder+maybeF = maybe "<Nothing>" build++{- |+Format an 'Either':++>>> eitherF (Right 1 :: Either Bool Int)+"<Right: 1>"+-}+eitherF :: (Buildable a, Buildable b) => Either a b -> Builder+eitherF = either (\x -> "<Left: " <> build x <> ">")+                 (\x -> "<Right: " <> build x <> ">")++----------------------------------------------------------------------------+-- Other formatters+----------------------------------------------------------------------------++{- |+Take the first N characters:++>>> prefixF 3 "hello"+"hel"+-}+prefixF :: Buildable a => Int -> a -> Builder+prefixF size =+  fromLazyText . TL.take (fromIntegral size) . toLazyText . build++{- |+Take the last N characters:++>>> suffixF 3 "hello"+"llo"+-}+suffixF :: Buildable a => Int -> a -> Builder+suffixF size =+  fromLazyText .+  (\t -> TL.drop (TL.length t - fromIntegral size) t) .+  toLazyText . build++{- |+@padLeftF n c@ pads the string with character @c@ from the left side until it+becomes @n@ characters wide (and does nothing if the string is already that+long, or longer):++>>> padLeftF 5 '0' 12+"00012"+>>> padLeftF 5 '0' 123456+"123456"+-}+padLeftF :: Buildable a => Int -> Char -> a -> Builder+padLeftF = F.left++{- |+@padRightF n c@ pads the string with character @c@ from the right side until+it becomes @n@ characters wide (and does nothing if the string is already+that long, or longer):++>>> padRightF 5 ' ' "foo"+"foo  "+>>> padRightF 5 ' ' "foobar"+"foobar"+-}+padRightF :: Buildable a => Int -> Char -> a -> Builder+padRightF = F.right++{- |+@padBothF n c@ pads the string with character @c@ from both sides until+it becomes @n@ characters wide (and does nothing if the string is already+that long, or longer):++>>> padBothF 5 '=' "foo"+"=foo="+>>> padBothF 5 '=' "foobar"+"foobar"++When padding can't be distributed equally, the left side is preferred:++>>> padBothF 8 '=' "foo"+"===foo=="+-}+padBothF :: Buildable a => Int -> Char -> a -> Builder+padBothF i c =+  fromLazyText . TL.center (fromIntegral i) c . toLazyText . build++----------------------------------------------------------------------------+-- Conditional formatters+----------------------------------------------------------------------------++{- | Display something only if the condition is 'True' (empty string+otherwise).++Note that it can only take a 'Builder' (because otherwise it would be+unusable with ('+|')-formatted strings which can resolve to any+'FromBuilder'). You can use 'build' to convert any value to a 'Builder'.+-}+whenF :: Bool -> Builder -> Builder+whenF True  x = x+whenF False _ = mempty+{-# INLINE whenF #-}++{- | Display something only if the condition is 'False' (empty string+otherwise).+-}+unlessF :: Bool -> Builder -> Builder+unlessF False x = x+unlessF True  _ = mempty+{-# INLINE unlessF #-}
+ lib/Fmt/Internal/Generic.hs view
@@ -0,0 +1,230 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+++module Fmt.Internal.Generic where+++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)+#if MIN_VERSION_base(4,9,0)+import Data.List.NonEmpty (NonEmpty)+#endif++import Data.List+import Data.Text.Lazy.Builder hiding (fromString)+import GHC.Generics+import Formatting.Buildable++import Fmt.Internal.Formatters+import Fmt.Internal.Template+import Fmt.Internal.Tuple+++-- $setup+-- >>> import Fmt++{- | Format an arbitrary value without requiring a 'Buildable' instance:++>>> 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++----------------------------------------------------------------------------+-- GBuildable+----------------------------------------------------------------------------++class GBuildable f where+  gbuild :: f a -> Builder++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 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 -> tupleF 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++----------------------------------------------------------------------------+-- Buildable'+----------------------------------------------------------------------------++-- | 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++instance Buildable' () where+  build' _ = "()"++instance (Buildable' a1, Buildable' a2)+  => Buildable' (a1, a2) where+  build' (a1, a2) = tupleF+    [build' a1, build' a2]++instance (Buildable' a1, Buildable' a2, Buildable' a3)+  => Buildable' (a1, a2, a3) where+  build' (a1, a2, a3) = tupleF+    [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) = tupleF+    [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) = tupleF+    [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) = tupleF+    [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) = tupleF+    [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) = tupleF+    [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++----------------------------------------------------------------------------+-- GetFields+----------------------------------------------------------------------------++class GetFields f where+  -- | Get fields, together with their names if available+  getFields :: f a -> [(String, Builder)]++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 _ = []
+ lib/Fmt/Internal/Numeric.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}+++module Fmt.Internal.Numeric where+++#if __GLASGOW_HASKELL__ < 804+import           Data.Monoid ((<>))+#endif+import           Numeric+import           Data.Char+import           Data.Text.Lazy.Builder hiding (fromString)+import           Formatting.Buildable (Buildable(..))+import qualified Formatting.Internal.Raw as F+import qualified Data.Text.Lazy as TL+++-- $setup+-- >>> import Fmt++----------------------------------------------------------------------------+-- Integer+----------------------------------------------------------------------------++{- |+Format a number as octal:++>>> listF' octF [7,8,9,10]+"[7, 10, 11, 12]"+-}+octF :: Integral a => a -> Builder+octF = baseF 8++{- |+Format a number as binary:++>>> listF' binF [7,8,9,10]+"[111, 1000, 1001, 1010]"+-}+binF :: Integral a => a -> Builder+binF = baseF 2++{- |+Format a number in arbitrary base (up to 36):++>>> baseF 3 10000+"111201101"+>>> baseF 7 10000+"41104"+>>> baseF 36 10000+"7ps"+-}+baseF :: Integral a => Int -> a -> Builder+baseF numBase = build . atBase numBase++----------------------------------------------------------------------------+-- Floating-point+----------------------------------------------------------------------------++{- |+Format a floating-point number:++>>> floatF 3.1415+"3.1415"++Numbers smaller than 1e-6 or bigger-or-equal to 1e21 will be displayed using+scientific notation:++>>> listF' floatF [1e-6,9e-7]+"[0.000001, 9.0e-7]"+>>> listF' floatF [9e20,1e21]+"[900000000000000000000.0, 1.0e21]"+-}+floatF :: Real a => a -> Builder+floatF a | d < 1e-6 || d >= 1e21 = build $ showEFloat Nothing d ""+         | otherwise             = build $ showFFloat Nothing d ""+  where d = realToFrac a :: Double++{- | Format a floating-point number using scientific notation, with the given+amount of decimal places.++>>> listF' (exptF 5) [pi,0.1,10]+"[3.14159e0, 1.00000e-1, 1.00000e1]"+-}+exptF :: Real a => Int -> a -> Builder+exptF decs a = build $ showEFloat (Just decs) (realToFrac a :: Double) ""++{- |+Format a floating-point number without scientific notation:++>>> listF' (fixedF 5) [pi,0.1,10]+"[3.14159, 0.10000, 10.00000]"+-}+fixedF :: Real a => Int -> a -> Builder+fixedF = F.fixed++----------------------------------------------------------------------------+-- Other+----------------------------------------------------------------------------++{- |+Break digits in a number:++>>> commaizeF 15830000+"15,830,000"+-}+commaizeF :: (Buildable a, Integral a) => a -> Builder+commaizeF = groupInt 3 ','++{- |+Add an ordinal suffix to a number:++>>> ordinalF 15+"15th"+>>> ordinalF 22+"22nd"+-}+ordinalF :: (Buildable a, Integral a) => a -> Builder+ordinalF n+  | tens > 3 && tens < 21 = build n <> "th"+  | otherwise = build n <> case n `mod` 10 of+                             1 -> "st"+                             2 -> "nd"+                             3 -> "rd"+                             _ -> "th"+  where+    tens = n `mod` 100++----------------------------------------------------------------------------+-- Helpers+----------------------------------------------------------------------------++groupInt :: (Buildable a, Integral a) => Int -> Char -> a -> Builder+groupInt 0 _ n = build n+groupInt i c n =+    fromLazyText . TL.reverse .+    foldr merge "" .+    TL.zip (zeros <> cycle' zeros') .+    TL.reverse .+    toLazyText . build+      $ n+  where+    zeros = TL.replicate (fromIntegral i) (TL.singleton '0')+    zeros' = TL.singleton c <> TL.tail zeros+    merge (f, c') rest+      | f == c = TL.singleton c <> TL.singleton c' <> rest+      | otherwise = TL.singleton c' <> rest+    cycle' xs = xs <> cycle' xs+    -- Suppress the warning about redundant Integral constraint+    _ = toInteger n++atBase :: Integral a => Int -> a -> String+atBase b _ | b < 2 || b > 36 = error ("baseF: Invalid base " ++ show b)+atBase b n =+  showSigned' (showIntAtBase (toInteger b) intToDigit') (toInteger n) ""+{-# INLINE atBase #-}++showSigned' :: Real a => (a -> ShowS) -> a -> ShowS+showSigned' f n+  | n < 0     = showChar '-' . f (negate n)+  | otherwise = f n++intToDigit' :: Int -> Char+intToDigit' i+  | i >= 0  && i < 10 = chr (ord '0' + i)+  | i >= 10 && i < 36 = chr (ord 'a' + i - 10)+  | otherwise = error ("intToDigit': Invalid int " ++ show i)
+ lib/Fmt/Internal/Template.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+++-- | Old-style formatting a la @text-format@.+module Fmt.Internal.Template where+++import Data.String (IsString(..))+import Data.Text (Text, splitOn)+import Data.Text.Lazy.Builder hiding (fromString)+#if !(MIN_VERSION_base(4,11,0))+import Data.Semigroup+#endif+import Formatting.Buildable (Buildable(..))+import Fmt.Internal.Core (FromBuilder(..))+++-- $setup+-- >>> import Fmt++{- | 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). Also it's polyvariadic:++>>> format "{} + {} = {}" 2 2 4+2 + 2 = 4++You can use arbitrary formatters:++>>> format "0x{} + 0x{} = 0x{}" (hexF 130) (hexF 270) (hexF (130+270))+0x82 + 0x10e = 0x190+-}+format :: FormatType r => Format -> r+format f = format' f []+{-# INLINE format #-}++{- | Like 'format', but adds a newline.+-}+formatLn :: FormatType r => Format -> r+formatLn f = format' (f <> "\n") []+{-# INLINE formatLn #-}++-- | A format string. This is intentionally incompatible with other+-- string types, to make it difficult to construct a format string by+-- concatenating string fragments (a very common way to accidentally+-- make code vulnerable to malicious data).+--+-- This type is an instance of 'IsString', so the easiest way to+-- construct a query is to enable the @OverloadedStrings@ language+-- extension and then simply write the query in double quotes.+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- >+-- > import Fmt+-- >+-- > f :: Format+-- > f = "hello {}"+--+-- The underlying type is 'Text', so literal Haskell strings that+-- contain Unicode characters will be correctly handled.+newtype Format = Format { fromFormat :: Text }+  deriving (Eq, Ord, Show)++instance Semigroup Format where+  Format a <> Format b = Format (a <> b)++instance Monoid Format where+  mempty = Format mempty+#if !(MIN_VERSION_base(4,11,0))+  mappend = (<>)+#endif++instance IsString Format where+  fromString = Format . fromString++-- Format strings are almost always constants, and they're expensive+-- to interpret (which we refer to as "cracking" here).  We'd really+-- like to have GHC memoize the cracking of a known-constant format+-- string, so that it occurs at most once.+--+-- To achieve this, we arrange to have the cracked version of a format+-- string let-floated out as a CAF, by inlining the definitions of+-- build and functions that invoke it.  This works well with GHC 7.++-- | Render a format string and arguments to a 'Builder'.+renderFormat :: Format -> [Builder] -> Builder+renderFormat fmt ps = zipParams (crack fmt) ps+{-# INLINE renderFormat #-}++zipParams :: [Builder] -> [Builder] -> Builder+zipParams fragments params = go fragments params+  where go (f:fs) (y:ys) = f <> y <> go fs ys+        go [f] []        = f+        go _ _  = error $ "Fmt.format: there were " <> show (length fragments - 1) <>+                          " sites, but " <> show (length params) <> " parameters"++crack :: Format -> [Builder]+crack = map fromText . splitOn "{}" . fromFormat++-- | Something like 'Text.Printf.PrintfType' in "Text.Printf".+class FormatType r where+  format' :: Format -> [Builder] -> r++instance (Buildable a, FormatType r) => FormatType (a -> r) where+  format' f xs = \x -> format' f (build x : xs)++instance {-# OVERLAPPABLE #-} FromBuilder r => FormatType r where+  format' f xs = fromBuilder $ renderFormat f (reverse xs)
+ lib/Fmt/Internal/Tuple.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+++module Fmt.Internal.Tuple where+++#if __GLASGOW_HASKELL__ < 804+import           Data.Monoid ((<>))+#endif+import           Data.List (intersperse)+import qualified Data.Text.Lazy as TL+import           Data.Text.Lazy.Builder+import           Formatting.Buildable (Buildable, build)+import           Lens.Micro+++-- $setup+-- >>> import Fmt.Internal.Core++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 )++You can also use 'tupleF' on lists to get tuple-like formatting.+  -}+  tupleF :: a -> Builder++instance (Buildable a1, Buildable a2)+  => TupleF (a1, a2) where+  tupleF (a1, a2) = tupleF+    [build a1, build a2]++instance (Buildable a1, Buildable a2, Buildable a3)+  => TupleF (a1, a2, a3) where+  tupleF (a1, a2, a3) = tupleF+    [build a1, build a2, build a3]++instance (Buildable a1, Buildable a2, Buildable a3, Buildable a4)+  => TupleF (a1, a2, a3, a4) where+  tupleF (a1, a2, a3, a4) = tupleF+    [build a1, build a2, build a3, build a4]++instance (Buildable a1, Buildable a2, Buildable a3, Buildable a4,+          Buildable a5)+  => TupleF (a1, a2, a3, a4, a5) where+  tupleF (a1, a2, a3, a4, a5) = tupleF+    [build a1, build a2, build a3, build a4,+     build a5]++instance (Buildable a1, Buildable a2, Buildable a3, Buildable a4,+          Buildable a5, Buildable a6)+  => TupleF (a1, a2, a3, a4, a5, a6) where+  tupleF (a1, a2, a3, a4, a5, a6) = tupleF+    [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)+  => TupleF (a1, a2, a3, a4, a5, a6, a7) where+  tupleF (a1, a2, a3, a4, a5, a6, a7) = tupleF+    [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)+  => TupleF (a1, a2, a3, a4, a5, a6, a7, a8) where+  tupleF (a1, a2, a3, a4, a5, a6, a7, a8) = tupleF+    [build a1, build a2, build a3, build a4,+     build a5, build a6, build a7, build a8]++instance Buildable a => TupleF [a] where+  tupleF = tupleF . map build++instance {-# OVERLAPPING #-} TupleF [Builder] where+  tupleF xs+    | True `elem` mls = mconcat (intersperse ",\n" items)+    | otherwise = "(" <> mconcat (intersperse ", " xs) <> ")"+   where+    (mls, items) = unzip $ zipWith3 buildItem+                             xs (set _head True falses) (set _last True falses)+    -- A list of 'False's which has the same length as 'xs'+    falses = map (const False) xs+    -- Returns 'True' if the item is multiline+    buildItem :: Builder+              -> Bool              -- ^ Is the item the first?+              -> Bool              -- ^ Is the item the last?+              -> (Bool, Builder)+    buildItem x isFirst isLast =+      case map fromLazyText (TL.lines (toLazyText x)) of+        [] | isFirst && isLast -> (False, "()\n")+           | isFirst           -> (False, "(\n")+           |            isLast -> (False, "  )\n")+           | otherwise         -> (False, "")+        ls ->+           (not (null (tail ls)),+            mconcat . map (<> "\n") $+              ls & _head %~ (if isFirst then ("( " <>) else ("  " <>))+                 & _tail.each %~ ("  " <>)+                 & _last %~ (if isLast then (<> " )") else id))
lib/Fmt/Time.hs view
@@ -17,30 +17,39 @@ @<https://hackage.haskell.org/package/formatting/docs/Formatting-Time.html Formatting.Time>@ from the @<https://hackage.haskell.org/package/formatting formatting>@ package. -Most of the time you'll want to use one of these formatters:+Most of the time you'll want to use one of these formatters (all of the+examples below use @"2018-02-14 16:20:45.5 CST"@): -@->>> __'dateTimeF' t                  -- full date and time__-"Sun May 14 16:16:47 MSK 2017"+* 'dateTimeF' – full date and time: ->>> __'hmF' t                        -- hours and minutes__-"16:16"+    >>> dateTimeF t+    "Wed Feb 14 16:20:45 CST 2018" ->>> __'hmsF' t                       -- hours, minutes and seconds__-"16:16:47"+* 'hmF' – hours and minutes: ->>> __'dateDashF' t                  -- date in ISO 8601 format__-"2017-05-14"+    >>> hmF t+    "16:20" ->>> __'diffF' False t                -- time period (convenient for humans)__-"3 seconds"+* 'hmsF' – hours, minutes and seconds: ->>> __'diffF' True t                 -- point in time (convenient for humans)__-"3 seconds ago"-@+    >>> hmsF t+    "16:20:45" -Note that two formatters from @Formatting.Time@ have been renamed:+* 'dateDashF' – date in ISO 8601 format: +    >>> dateDashF t+    "2018-02-14"++* 'diffF' – either a time period or a point in time, in a convenient for+  humans format:++    >>> diffF False 130    -- time period (130 seconds)+    "2 minutes"+    >>> diffF True 130     -- point in time (130 seconds in the future)+    "in 2 minutes"++Note that two formatters from @Formatting.Time@ are called differently here:+ @ pico     -> 'picosecondF' decimals -> 'subsecondF'@@ -116,7 +125,7 @@ import           Data.Monoid             ((<>)) import           Data.Text               (Text) import qualified Data.Text               as T-import           Data.Text.Buildable     (build)+import           Formatting.Buildable    (build) import           Data.Text.Lazy.Builder  (Builder) import           Data.Time @@ -124,8 +133,10 @@ import           Data.Time.Locale.Compat #endif -import           Fmt.Internal            (fixedF, ordinalF)+import           Fmt.Internal.Numeric    (fixedF, ordinalF) +-- $setup+-- >>> let t = read "2018-02-14 16:20:45.5 CST" :: ZonedTime  ---------------------------------------------------------------------------- -- Custom@@ -142,25 +153,24 @@  -- | Timezone offset on the format @-HHMM@. ----- >>> t <- getZonedTime -- >>> t--- 2017-05-14 16:16:47.62135 MSK+-- 2018-02-14 16:20:45.5 CST -- >>> tzF t--- "+0300"+-- "-0600" tzF :: FormatTime a => a -> Builder tzF = timeF "%z"  -- | Timezone name. -- -- >>> tzNameF t--- "MSK"+-- "CST" tzNameF :: FormatTime a => a -> Builder tzNameF = timeF "%Z"  -- | As 'dateTimeFmt' @locale@ (e.g. @%a %b %e %H:%M:%S %Z %Y@). -- -- >>> dateTimeF t--- "Sun May 14 16:16:47 MSK 2017"+-- "Wed Feb 14 16:20:45 CST 2018" dateTimeF :: FormatTime a => a -> Builder dateTimeF = timeF "%c" @@ -171,28 +181,28 @@ -- | Same as @%H:%M@. -- -- >>> hmF t--- "16:16"+-- "16:20" hmF :: FormatTime a => a -> Builder hmF = timeF "%R"  -- | Same as @%H:%M:%S@. -- -- >>> hmsF t--- "16:16:47"+-- "16:20:45" hmsF :: FormatTime a => a -> Builder hmsF = timeF "%T"  -- | As 'timeFmt' @locale@ (e.g. @%H:%M:%S@). -- -- >>> hmsLF t--- "16:16:47"+-- "16:20:45" hmsLF :: FormatTime a => a -> Builder hmsLF = timeF "%X"  -- | As 'time12Fmt' @locale@ (e.g. @%I:%M:%S %p@). -- -- >>> hmsPLF t--- "04:16:47 PM"+-- "04:20:45 PM" hmsPLF :: FormatTime a => a -> Builder hmsPLF = timeF "%r" @@ -214,10 +224,7 @@ -- -- >>> hour24F t -- "16"--- >>> let nightT = read "2017-05-14 00:21:32.714083 UTC" :: UTCTime--- >>> nightT--- 2017-05-14 00:21:32.714083 UTC--- >>> hour24F nightT+-- >>> hour24F midnight -- "00" hour24F :: FormatTime a => a -> Builder hour24F = timeF "%H"@@ -226,45 +233,47 @@ -- -- >>> hour12F t -- "04"--- >>> hour12F nightT+-- >>> hour12F midnight -- "12" hour12F :: FormatTime a => a -> Builder hour12F = timeF "%I"  -- | Hour, 24-hour, leading space as needed, @ 0@ - @23@. ----- >>> hour24SF nightT+-- >>> hour24SF t+-- "16"+-- >>> hour24SF midnight -- " 0" hour24SF :: FormatTime a => a -> Builder hour24SF = timeF "%k"  -- | Hour, 12-hour, leading space as needed, @ 1@ - @12@. ----- >>> hour12SF nightT+-- >>> hour12SF t+-- " 4"+-- >>> hour12SF midnight -- "12" hour12SF :: FormatTime a => a -> Builder hour12SF = timeF "%l"  -- | Minute, @00@ - @59@. ----- >>> otherT--- 2017-05-14 17:12:47.897343 MSK--- >>> minuteF otherT--- "12"+-- >>> minuteF t+-- "20" minuteF :: FormatTime a => a -> Builder minuteF = timeF "%M"  -- | Second, without decimal part, @00@ - @60@. -- -- >>> secondF t--- "47"+-- "45" secondF :: FormatTime a => a -> Builder secondF = timeF "%S"  -- | Picosecond, including trailing zeros, @000000000000@ - @999999999999@. -- -- >>> picosecondF t--- "621350000000"+-- "500000000000" picosecondF :: FormatTime a => a -> Builder picosecondF = timeF "%q" @@ -272,7 +281,7 @@ -- For a whole number of seconds, this produces an empty string. -- -- >>> subsecondF t--- ".62135"+-- ".5" subsecondF :: FormatTime a => a -> Builder subsecondF = timeF "%Q" @@ -286,7 +295,7 @@ -- Unix epoch is formatted as @-1.1@ with @%s%Q@. -- -- >>> epochF t--- "1494767807"+-- "1518646845" epochF :: FormatTime a => a -> Builder epochF = timeF "%s" @@ -297,35 +306,35 @@ -- | Same as @%m\/%d\/%y@. -- -- >>> dateSlashF t--- "05/14/17"+-- "02/14/18" dateSlashF :: FormatTime a => a -> Builder dateSlashF = timeF "%D"  -- | Same as @%Y-%m-%d@. -- -- >>> dateDashF t--- "2017-05-14"+-- "2018-02-14" dateDashF :: FormatTime a => a -> Builder dateDashF = timeF "%F"  -- | As 'dateFmt' @locale@ (e.g. @%m\/%d\/%y@). -- -- >>> dateSlashLF t--- "05/14/17"+-- "02/14/18" dateSlashLF :: FormatTime a => a -> Builder dateSlashLF = timeF "%x"  -- | Year. -- -- >>> yearF t--- "2017"+-- "2018" yearF :: FormatTime a => a -> Builder yearF = timeF "%Y"  -- | Last two digits of year, @00@ - @99@. -- -- >>> yyF t--- "17"+-- "18" yyF :: FormatTime a => a -> Builder yyF = timeF "%y" @@ -339,23 +348,22 @@ -- | Month name, long form ('fst' from 'months' @locale@), @January@ - -- @December@. ----- >>> let longMonthT = read "2017-01-12 00:21:32.714083 UTC" :: UTCTime--- >>> monthNameF longMonthT--- "January"+-- >>> monthNameF t+-- "February" monthNameF :: FormatTime a => a -> Builder monthNameF = timeF "%B"  -- | Month name, short form ('snd' from 'months' @locale@), @Jan@ - @Dec@. ----- >>> monthNameShortF longMonthT--- "Jan"+-- >>> monthNameShortF t+-- "Feb" monthNameShortF :: FormatTime a => a -> Builder monthNameShortF = timeF "%b"  -- | Month of year, leading 0 as needed, @01@ - @12@. ----- >>> monthF longMonthT--- "01"+-- >>> monthF t+-- "02" monthF :: FormatTime a => a -> Builder monthF = timeF "%m" @@ -383,21 +391,21 @@ -- | Day of year for Ordinal Date format, @001@ - @366@. -- -- >>> dayF t--- "134"+-- "045" dayF :: FormatTime a => a -> Builder dayF = timeF "%j"  -- | Year for Week Date format e.g. @2013@. -- -- >>> weekYearF t--- "2017"+-- "2018" weekYearF :: FormatTime a => a -> Builder weekYearF = timeF "%G"  -- | Last two digits of year for Week Date format, @00@ - @99@. -- -- >>> weekYYF t--- "17"+-- "18" weekYYF :: FormatTime a => a -> Builder weekYYF = timeF "%g" @@ -411,21 +419,21 @@ -- | Week for Week Date format, @01@ - @53@. -- -- >>> weekF t--- "19"+-- "07" weekF :: FormatTime a => a -> Builder weekF = timeF "%V"  -- | Day for Week Date format, @1@ - @7@. -- -- >>> dayOfWeekF t--- "7"+-- "3" dayOfWeekF :: FormatTime a => a -> Builder dayOfWeekF = timeF "%u"  -- | Day of week, short form ('snd' from 'wDays' @locale@), @Sun@ - @Sat@. -- -- >>> dayNameShortF t--- "Sun"+-- "Wed" dayNameShortF :: FormatTime a => a -> Builder dayNameShortF = timeF "%a" @@ -433,7 +441,7 @@ -- @Saturday@. -- -- >>> dayNameF t--- "Sunday"+-- "Wednesday" dayNameF :: FormatTime a => a -> Builder dayNameF = timeF "%A" @@ -441,14 +449,14 @@ -- 'sundayStartWeek'), @00@ - @53@. -- -- >>> weekFromZeroF t--- "20"+-- "06" weekFromZeroF :: FormatTime a => a -> Builder weekFromZeroF = timeF "%U"  -- | Day of week number, @0@ (= Sunday) - @6@ (= Saturday). -- -- >>> dayOfWeekFromZeroF t--- "0"+-- "3" dayOfWeekFromZeroF :: FormatTime a => a -> Builder dayOfWeekFromZeroF = timeF "%w" @@ -456,7 +464,7 @@ -- 'mondayStartWeek'), @00@ - @53@. -- -- >>> weekOfYearMonF t--- "19"+-- "07" weekOfYearMonF :: FormatTime a => a -> Builder weekOfYearMonF = timeF "%W" @@ -522,10 +530,10 @@  -- | Display the absolute value time span in years. ----- >>> epochF t--- "1494767807"--- >>> yearsF 3 1494767807--- "47.399"+-- >>> epochF t    -- time passed since Jan 1, 1970+-- "1518646845"+-- >>> yearsF 3 1518646845+-- "48.156" yearsF :: RealFrac n        => Int -- ^ Decimal places.        -> n@@ -535,8 +543,8 @@  -- | Display the absolute value time span in days. ----- >>> daysF 3 1494767807--- "17300.553"+-- >>> daysF 3 1518646845+-- "17576.931" daysF :: RealFrac n       => Int -- ^ Decimal places.       -> n
tests/Main.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}   module Main where@@ -22,6 +23,8 @@ import qualified Data.ByteString.Lazy as BSL -- Generics import GHC.Generics+-- Call stacks+import Data.CallStack -- Tests import Test.Hspec @@ -149,8 +152,6 @@     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@@ -508,14 +509,14 @@ test_tuples :: Spec test_tuples = describe "tuples" $ do   test_tupleSimple-  describe "tupleLikeF" $ do+  describe "tupleF on lists" $ do     test_tupleOneLine     test_tupleMultiline  test_tupleSimple :: Spec test_tupleSimple = it "tupleF" $ do   -- we don't need complicated tests here, they're all tested in-  -- 'tupleLikeF' tests+  -- "tupleF on lists" tests   tupleF (n, s) ==#> "(25, !)"   tupleF (n, s, n, s) ==#> "(25, !, 25, !)"   tupleF (n, s, n, s, 'a', 'b', 'c', 'd') ==#>@@ -524,21 +525,21 @@ test_tupleOneLine :: Spec test_tupleOneLine = describe "one-line" $ do   it "()" $ do-    tupleLikeF [] ==#> "()"+    tupleF ([] :: [Builder]) ==#> "()"   it "('')" $ do-    tupleLikeF [""] ==#> "()"+    tupleF ([""] :: [Builder]) ==#> "()"   it "(a)" $ do-    tupleLikeF ["a"] ==#> "(a)"+    tupleF (["a"] :: [Builder]) ==#> "(a)"   it "(a,b)" $ do-    tupleLikeF ["a", "b"] ==#> "(a, b)"+    tupleF (["a", "b"] :: [Builder]) ==#> "(a, b)"   it "(a,'')" $ do-    tupleLikeF ["a", ""] ==#> "(a, )"+    tupleF (["a", ""] :: [Builder]) ==#> "(a, )"   it "('',b)" $ do-    tupleLikeF ["", "b"] ==#> "(, b)"+    tupleF (["", "b"] :: [Builder]) ==#> "(, b)"   it "('','')" $ do-    tupleLikeF ["", ""] ==#> "(, )"+    tupleF (["", ""] :: [Builder]) ==#> "(, )"   it "(a,b,c)" $ do-    tupleLikeF ["a", "ba", "caba"] ==#> "(a, ba, caba)"+    tupleF (["a", "ba", "caba"] :: [Builder]) ==#> "(a, ba, caba)"  test_tupleMultiline :: Spec test_tupleMultiline = describe "multiline" $ do@@ -546,45 +547,45 @@   someEmpty   it "weird case" $ do     -- not sure whether I should fix it or not-    tupleLikeF ["a\n"] ==#> "(a\n)"+    tupleF ["a\n" :: Builder] ==#> "(a\n)"    where     allNonEmpty = describe "all non-empty" $ do       it "1 element (2 lines)" $ do-          tupleLikeF ["a\nx"] ==#> [text|+          tupleF ["a\nx" :: Builder] ==#> [text|             ( a               x )             |]-          tupleLikeF ["a\n x"] ==#> [text|+          tupleF ["a\n x" :: Builder] ==#> [text|             ( a                x )             |]-          tupleLikeF [" a\nx\n"] ==#> [text|+          tupleF [" a\nx\n" :: Builder] ==#> [text|             (  a               x )             |]       it "1 element (3 lines)" $ do-          tupleLikeF ["a\nb\nc"] ==#> [text|+          tupleF ["a\nb\nc" :: Builder] ==#> [text|             ( a               b               c )             |]       it "2 elements (1 line + 2 lines)" $ do-          tupleLikeF ["a", "b\nc"] ==#> [text|+          tupleF ["a", "b\nc" :: Builder] ==#> [text|             ( a             ,               b               c )             |]       it "2 elements (2 lines + 1 line)" $ do-          tupleLikeF ["a\nb", "c"] ==#> [text|+          tupleF ["a\nb", "c" :: Builder] ==#> [text|             ( a               b             ,               c )             |]       it "3 elements (each has 2 lines)" $ do-          tupleLikeF ["a\nb", "c\nd", "e\nf"] ==#> [text|+          tupleF ["a\nb", "c\nd", "e\nf" :: Builder] ==#> [text|             ( a               b             ,@@ -597,21 +598,21 @@      someEmpty = describe "some empty" $ do       it "2 elements (0 + 2)" $ do-          tupleLikeF ["", "a\nb"] ==#> [text|+          tupleF ["", "a\nb" :: Builder] ==#> [text|             (             ,               a               b )             |]       it "2 elements (2 + 0)" $ do-          tupleLikeF ["a\nb", ""] ==#> [text|+          tupleF ["a\nb", "" :: Builder] ==#> [text|             ( a               b             ,               )             |]       it "3 elements (0 + 2 + 0)" $ do-          tupleLikeF ["", "a\nb", ""] ==#> [text|+          tupleF ["", "a\nb", "" :: Builder] ==#> [text|             (             ,               a@@ -620,7 +621,7 @@               )             |]       it "3 elements (2 + 0 + 2)" $ do-          tupleLikeF ["a\nb", "", "c\nd"] ==#> [text|+          tupleF ["a\nb", "", "c\nd" :: Builder] ==#> [text|             ( a               b             ,@@ -629,7 +630,7 @@               d )             |]       it "4 elements (2 + 0 + 0 + 2)" $ do-          tupleLikeF ["a\nb", "", "", "c\nd"] ==#> [text|+          tupleF ["a\nb", "", "", "c\nd" :: Builder] ==#> [text|             ( a               b             ,@@ -706,8 +707,8 @@ -- Utilities ---------------------------------------------------------------------------- -(==%>) :: Text -> Text -> Expectation+(==%>) :: HasCallStack => Text -> Text -> Expectation (==%>) = shouldBe -(==#>) :: Builder -> Text -> Expectation+(==#>) :: HasCallStack => Builder -> Text -> Expectation (==#>) a b = a `shouldBe` build (T.replace "_" " " b)
+ tests/doctest-config.json view
@@ -0,0 +1,3 @@+{+    "sourceFolders": ["lib"]+}
+ tests/doctests.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF doctest-discover -optF tests/doctest-config.json #-}