packages feed

sayable 1.1.1.0 → 1.2.0.0

raw patch · 5 files changed

+394/−142 lines, 5 filesdep +hspecdep +sayabledep +tastydep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: hspec, sayable, tasty, tasty-ant-xml, tasty-hspec

Dependency ranges changed: base

API changes (from Hackage documentation)

- Text.Sayable: (&!+*) :: forall tag m t b. (Sayable tag b, Sayable tag m, Foldable t) => ([Doc SayableAnn] -> Doc SayableAnn) -> b -> t m -> Saying tag
+ Text.Sayable: (&!:*) :: forall tag m t b. (Sayable tag b, Sayable tag m, Foldable t) => ([Doc SayableAnn] -> Doc SayableAnn) -> b -> t m -> Saying tag
+ Text.Sayable: (&+?) :: forall saytag m n. (Sayable saytag m, Sayable saytag n) => m -> Maybe n -> Saying saytag
+ Text.Sayable: (&:*) :: forall tag m e t. (Sayable tag m, Sayable tag e, Foldable t) => m -> t e -> Saying tag
- Text.Sayable: infixl 2 &!+*
+ Text.Sayable: infixl 2 &!:*

Files

CHANGELOG.md view
@@ -1,5 +1,14 @@ # Revision history for sayable +## 1.2.0.0 -- 2023-10-01++* Changed `&+*` and `&!+*` to `&:*` and `&!:*` to avoid confusion with normal `+`+  indication of concatenation.+* Added `&+*` which acts like `&*` but does not add a space separator relative to+  the preceding output.+* Added `&+?` operator to show an immediate adjacent `Just` value.+* Added various tests.+ ## 1.1.1.0 -- 2023-06-20  * Add Haddock documentation showing examples for operators.
+ README.md view
@@ -0,0 +1,139 @@+This module provides a set of data structures, classes, and operators that+facilitate the construction of a Prettyprinter `Doc` object.++# Motivation++Standard prettyprinting is a monotonic conversion that does not allow for+customization for different uses or environments.  For example, when debugging,+full and explicit information about a structure should be generated, but for+checkpoint logging, a simple overview is usually more appropriate.++This library provides for an additional type parameter that can be used to+control the conversion to a suitably verbose Prettyprinter Doc representation.++This is also highly useful in conjunction with logging to generate successively+more verbose information as the logging verbosity increases.++## Usage++Typical usage is to create a sayable message using the operators defined here and+then extract Prettyprinter `Doc` from the saying and convert it to a printable+format (here, simply using `show` for the default Prettyprinter rendering).++```+import qualified Prettyprinter as PP++foo :: Members '[ Logging SayMessage, Config ] r -> a -> b -> Eff r [b]+foo arg1 arg2 =+   do putStrLn $ show $ saying $ sayable @info "Entering foo with" &- arg1 &- "and" &- arg2+      rslt <- something arg1 arg2+      case rslt of+        Right vals ->+          do putStrLn $ show $ saying $ sayable @"verbose"+                 $ "Foo successfully returning" &% length vals &- "results:" &- vals+             return vals+        Left err ->+          do putStrLn $ show $ saying $ sayable @"error"+                 $ "Foo error (" &- arg1 &- PP.comma &- arg2 &- ") is" &- err+             throwError err+```++There are three messages printed: one on entry and one on either the success or+failure paths.  Each message may have different levels of information reported+for the various arguments.++## The `saytag` type parameter++Each sayable message uses a `TypeApplication` to specify a `saytag` which should+be used for controlling the rendering of that message.  This parameter is+polykinded to provide maximum flexibility, but the most common kind is `Symbol`+(e.g. `"info"`, `"verbose"`, `"error"`, etc.).++Another frequent kind used for the `saytag` is `GHC.TypeNats.Nat`, allowing for+an ordering of saytag types.  However be aware that any instance constraints+(e.g. `saytag <= 9`) are only resolved __after__ the instance head is matched, so+if the constraints do not match no other instances will be tried an an error is+generated.  Thus, rather than use constraints for selecting between instances,+the maximum value for each "range" should be an instance, along with the minimum+extremum:++```+instance {-# OVERLAPPING #-} Sayable (9::Nat) Foo where sayable f = ...[sayable for 9+]+instance {-# OVERLAPPING #-} Sayable (3::Nat) Foo where sayable f = ...[sayable for 3-8]+instance {-# OVERLAPPING #-} Sayable (0::Nat) Foo where sayable f = ...[sayable for 0-2]+instance {-# OVERLAPPABLE #-} (0 <= prevVer, prevVer ~ (ver - 1), Sayable prevVer Foo)+   => Sayable ver Foo where+   sayable = Saying . saying . sayable @Nat @prevVer+```++As a developer, it is encouraged to use whatever saytag makes sense relative to+the current context and type of information being processed.  Most of this+documentation will use the preferred `Symbol` kind for the `saytag`.++== Individual Arguments++The arguments passed to the sayable should be instances of the `Sayable` class.+There are a number of standard instances of `Sayable`, but an instance can be+declared for any object that might be output.  The `Sayable` class has two class+parameters: the second is object to be converted, and the first is the "saytag".+This allows different Sayable instances for an object to be used in different+saytag scenarios.  For example:++```+import Network.URL++instance Sayable "verbose" URL where+  sayable url =+    let newline = PP.line :: PP.Doc SayableAnn+        prettyShow x = PP.viaShow x :: PP.Doc SayableAnn+    in "URL {"+        &- "url_type=" &- prettyShow (url_type url) &- newline+        &- "url_path=" &- url_path url &- newline+        &- "url_params=" &* url_params url+        &- "}"+instance Sayable saytag URL where+  sayable = Sayable . PP.viaShow . exportURL+```++The above would cause a url emitted via a "verbose" saytag to be+expanded into a report on each individual field, whereas all other+saytags would simply output the `exportURL` representation of the `URL`.++```+>>> let host = Host (HTTP True) "github.com" Nothing+>>> url' = URL (Absolute host) "by/one"+>>> saying $ sayable @"verbose" url'+URL { url_type= Absolute (Host {protocol = HTTP True, host= "github.com", port= Nothing})+ url_path= by/one+ url_params= }+>>> saying @"info" $ sayable url'+https://github.com:442/by/one+```++Note that there are several pre-declared `Sayable` instances for common+datatypes for convenience.++== Operators++In the logging lines above, there are several operators used, each of which+starts with the `&` character.  These are described in detail in the 'Helper+operators' section below, but the general mnemonic for these is:++  * A dash is a space between sayable elements++  * A plus is immediately adjacent sayable elements++  * A colon is a separator specification++  * An asterisk is applied to a foldable (i.e. a list)++  * A percent sign preceeds a Pretty object++  * An exclamation follows a Pretty function, which is applied to the following+    argument.++  * A question mark is followed by a Maybe, with no output for a Nothing++  * A less-than character means newline (i.e. return to the left)++These characters will be combined for operators with combination effects.
Text/Sayable.hs view
@@ -1,7 +1,8 @@ {- | Module: Text.Sayable -This module provides a set of data structures, classes, and operators that facilitate the construction of a Prettyprinter Doc object.+This module provides a set of data structures, classes, and operators that+facilitate the construction of a Prettyprinter Doc object.  = Motivation @@ -40,12 +41,13 @@              throwError err @ -[Note: if viewing via Haddock HTML, the '@' in front of @"info"@,+[Note: if viewing via Haddock HTML, the ampersand in front of @"info"@, @"verbose"@, and @"error"@ on the putStrLn lines above may not be visible.]  There are three messages printed: one on entry and one on either the success or-failure paths.  Each message may have different levels of information reported for the various arguments.+failure paths.  Each message may have different levels of information reported+for the various arguments.  == The @saytag@ type parameter @@ -100,126 +102,30 @@  == Operators -In the logging lines above, there are three operators used, each of-which starts with the @&@ character:+In the logging lines above, there are several operators used, each of which+starts with the @&@ character.  These are described in detail in the 'Helper+operators' section below, but the general mnemonic for these is: -  ['&-'] This is the standard operator that takes two Sayable-         arguments and converts them to their Sayable form, then-         combining them (with an intervening space).  This is the-         standard argument to use for building the output message from-         distinct parts.+  * A dash is elements separated by a space -         >>> sez @"info" $ t'"hello" &- t'"world"-         "hello world"+  * A plus indicates immediately adjacent elements -  ['&+'] This is a variation of the standard '&-' operator that has no-         intervening space between the two arguments that are-         converted to a Sayable form.+  * A colon is designates a separator -         >>> sez @"info" $ t'"hello" &+ t'"world"-         "helloworld"+  * An asterisk is applied to a foldable (i.e. a list) -  ['&%'] This is a variation of the standard '&-' operator that only-         requires the second argument to be an instances of-         Prettyprinter.Pretty instead of an instance of 'Sayable',-         which can be convenient and avoids the need to define large-         numbers of 'Sayable' instances.+  * A percent sign preceeds a Pretty object -         >>> sez @"info" $ t'"hello" &% (t'"world", t'"!")-         "hello (world, !)"+  * An exclamation follows a Pretty function, which is applied to the following+    argument. -  ['&*'] This is a helper operator whose second argument is a-         'Foldable' series of 'Sayable' elements.  This will fold over-         the series, adding the 'Sayable' instance value for each-         element separated by commas.+  * A question mark is followed by a Maybe, with no output for a Nothing -         >>> sez @"info" $ t'"three:" &* [1, 2, 3::Int]-         "three: 1, 2, 3"+  * A less-than character means newline (i.e. return to the left) -  ['&+*'] This is similar to the '&*' helper, but it uses the first-          argument as the separator between the elements of the-          'Foldable' second argument (instead of the ", " default used-          by the '&*' helper).+These characters will be combined for operators with combination effects. -         >>> sez @"info" $ t'"three:" &- t'".." &+* [1, 2, 3::Int]-         "three: 1..2..3" -  ['&?'] This is a helper operator whose second argument is a @Maybe-         a@ (where @a@ is a @Showable@).  This will emit the-         @Showable@ of @a@ if the argument is a 'Just' value, or-         nothing (an empty Text Showable) if the argument is a-         'Nothing' value.--         >>> sez @"info" $ t'"It's" &? Just (t'"something") &- t'"or" &? (Nothing :: Maybe Text)-         "It's something or"--  ['&<'] This is a helper operator that generates a newline between its two-         arguments.--         >>> sez @"info" $ t'"Hello" &< t'"world"-         "Hello\nworld"--  ['&<*'] This is a helper operator that combines the '&<' and '&*' operators: it-          generates a newline between its two arguments and the second argument-          is a Foldable that will be output separated by commas.--         >>> sez @"info" $ t'"three:" &<* [1, 2, 3::Int]-         "three:\n1, 2, 3"--  ['&<?'] This is a helper operator that conbines the '&<' and '&?' operators: if-          the second argument is a 'Just' value, it will be output preceeded by-          the first argument and a newline.  If the second argument is 'Nothing',-          only the first argument is emitted (no newline either).--         >>> sez @"info" $ t'"First" &<? Just (t'"something")-         "First\nsomething"-         >>> sez @"info" $ t'"Then" &<? (Nothing :: Maybe Text)-         "Then"--  ['&!'] This is a helper operator to apply a Prettyprinter-         transformation function (the first argument) to a 'Sayable'-         message (the second argument).--         >>> sez @"info" $ PP.group &! t'"hi"-         "hi"--  ['&!?'] This helper operator is a combination of the '&!' operator and the '&?'-          operator: for a second-argument 'Just' value it will convert the value-          to a sayable and then apply the Prettyprinter conversion operator-          first-argument.--         >>> sez @"info" $ PP.group &!? Just (t'"hi")-         "hi"--  ['&!*'] This helper operator is a combination of the '&!' operator-          and the '&*' operator: it applies the first argument (a-          @[PrettyPrinter.Doc ann] -> PrettyPrinter.Doc ann@ function)-          to the foldable collection represented by the second-          argument.--         >>> sez @"info" $ t'"three:" &- PP.align . PP.vsep &!* [1, 2, 3::Int]-         "three: 1, \n       2, \n       3"--  ['&!$*'] This helper operator is a combination of the '&!' operator and the-           '&*' operator: it applies the first argument (a @PrettyPrinter.Doc ann-           -> PrettyPrinter.Doc ann@ function) to the *result* of a foldable-           collection represented by the second argument.  It is similar to the-           '&!*' operator except that it applies the Prettyprinter conversion to-           the singular result of the list rather than to the list of results.--           >>> sez @"info" $ t'"three:" &- PP.align &!$* [1, 2, 3::Int]-           "three: 1, 2, 3"--  ['&!+*'] This helper operator is a combination of the '&!' operator and the-           '&+*' operator (and is a trinary rather than a binary operator): it-           applies the first argument (a @[PrettyPrinter.Doc ann] ->-           PrettyPrinter.Doc ann@ function) to the foldable collection-           represented by the third argument, using the second argument to-           specify the separators between the elements.--           >>> sez @"info" $ t'"three:" &- (PP.align . PP.vsep &!+* (t'" or")) [1, 2, 3::Int]-           "three: 1 or\n       2 or\n       3"- == Convenience/other    * This module also provides an instance to convert a Sayable back@@ -435,7 +341,9 @@   , (&%)   , (&*)   , (&+*)+  , (&:*)   , (&?)+  , (&+?)   , (&<)   , (&<*)   , (&<?)@@ -443,7 +351,7 @@   , (&!?)   , (&!*)   , (&!$*)-  , (&!+*)+  , (&!:*)     -- * Annotation used in Sayables     --     -- | Generating a 'Prettyprinter.Doc' requires the identification of an @ann@@@ -548,6 +456,10 @@ -- a Saying.  This is the most common operator used to construct -- composite Sayable messages.  The two Sayable items are separated by -- a space.+--+-- >>> sez @"info" $ t'"hello" &- t'"world"+-- "hello world"+-- (&-) :: forall saytag m n . (Sayable saytag m, Sayable saytag n)      => m -> n -> Saying saytag m &- n = sayable m <> sayable n@@ -557,6 +469,10 @@ -- a Saying by placing the two Sayable items immediately adjacent with -- no intervening spaces.  This is the high-density version of the -- more common '&-' operator.+--+-- >>> sez @"info" $ t'"hello" &+ t'"world"+-- "helloworld"+-- (&+) :: forall saytag m n . (Sayable saytag m, Sayable saytag n)      => m -> n -> Saying saytag m &+ n = Saying $ (saying $ sayable @saytag m) <> (saying $ sayable @saytag n)@@ -566,6 +482,10 @@ -- Pretty item into a Saying.  This is infrequently used and primarily -- allows the composition of a data object which has a "Prettyprinter" -- instance but no 'Sayable' instance.+--+-- >>> sez @"info" $ t'"hello" &% (t'"world", t'"!")+-- "hello (world, !)"+-- (&%) :: (Sayable tag m, PP.Pretty n) => m -> n -> Saying tag m &% n = sayable m <> sayable (PP.pretty n :: PP.Doc SayableAnn) infixl 1 &%@@ -573,11 +493,15 @@ -- | A helper operator to /apply/ a "Prettyprinter" (@Doc ann -> Doc -- ann@) function (the first argument) to the Sayable in the second -- argument.  This is different from the '&%' operator in that the--- former uses 'Prettyprinter.hsep' to join two independent+-- former uses 'Prettyprinter.hcat' to join two independent -- 'Prettyprinter.Doc' 'Saying' values, whereas this operator applies -- a transformation (e.g. @Prettyprinter.annotate AnnValue@ or -- @Prettyprinter.align . Prettyprinter.group@) to the -- 'Prettyprinter.Doc' in the second 'Saying' argument.+--+-- >>> sez @"info" $ PP.group &! t'"hi"+-- "hi"+-- (&!) :: forall tag m . Sayable tag m      => (PP.Doc SayableAnn -> PP.Doc SayableAnn) -> m -> Saying tag pf &! m = Saying $ pf $ saying $ sayable @tag m@@ -594,6 +518,12 @@ -- folding over a tuple only returns the 'snd' value of a tuple. -- Consider wrapping tuples in a newtype with an explicit Sayable to -- avoid this.+--+-- >>> sez @"info" $ t'"three:" &* [1, 2, 3::Int]+-- "three: 1, 2, 3"+--+-- If the second argument is a null collection then no output is generated for+-- it. (&*) :: forall tag m e t         . (Sayable tag m, Sayable tag e, Foldable t) => m -> t e -> Saying tag m &* l = let addElem e (s, Saying p) =@@ -601,30 +531,50 @@          in sayable m <> (snd $ foldr addElem ("", Saying PP.emptyDoc) l) infixl 1 &* +-- | A helper operator that generates a sayable from a foldable group (e.g. list)+-- of sayable items.  This helper is linke the '&*' operator except that the+-- folded output is immediately adjacent to the preceeding sayable output instead+-- of separated by a space; this is useful for situations where the folded output+-- has delimiters like parentheses or brackets.+--+-- >>> sez @"info" $ t'"three:" &- '(' &+* [1,2,3::Int] &+ ')'+-- "three: (1, 2, 3)"+--+-- If the second argument is an empty collection then no output is generated for+-- it. --- | A helper operator that generates a sayable from a list of sayable--- items, separated by the first sayable (&+*) :: forall tag m e t+        . (Sayable tag m, Sayable tag e, Foldable t) => m -> t e -> Saying tag+m &+* l = let addElem e (s, Saying p) =+               ("," <> PP.softline, Saying $ saying (sayable @tag e) <> s <> p)+         in Saying (saying (sayable @tag m)+                    <> saying (snd $ foldr addElem ("", Saying PP.emptyDoc) l))+infixl 1 &+*++-- | A helper operator that generates a sayable from a list of sayable items,+-- separated by the first sayable argument (instead of the ", " that use used by+-- the '&*' operator).+--+-- >>> sez @"info" $ t'"three:" &- t'".." &:* [1, 2, 3::Int]+-- "three: 1..2..3"+--+(&:*) :: forall tag m e t          . (Sayable tag m, Sayable tag e, Foldable t) => m -> t e -> Saying tag-m &+* l = let addElem e (s, Saying p) = (Just m,+m &:* l = let addElem e (s, Saying p) = (Just m,                                          case s of                                            Nothing -> sayable @tag e &+ p                                            Just s' -> sayable @tag e &+ s' &+ p                                         )           in snd $ foldr addElem (Nothing, Saying PP.emptyDoc) l-infixl 2 &+*-+infixl 2 &:* --- | A helper operator that applies the first argument which converts an array of--- 'Prettyprinter.Doc ann' elements to a single 'PrettyPrinter.Doc ann' element--- to the second argument, which is a Foldable collection of 'Sayable' items.--- This is essentially a combination of the '&!' and '&*' operators where the--- first operation takes the list of doc items and returns a single item.+-- | A helper operator that is a combination of the '&!' and '&*' operators.  It+-- applies the first argument (which converts an array of 'Prettyprinter.Doc ann'+-- elements into a single 'PrettyPrinter.Doc ann' element) to the second argument+-- (which is a Foldable collection of 'Sayable' items). ----- > import qualified Prettyprinter as PP--- >--- > putStrLn $ sez @"info" $ t'"The stooges are" &- PP.hsep &!* ["Larry", "Mo", "Curly"]--- The stooges are Larry Mo Curly+-- >>> sez @"info" $ t'"three:" &- PP.align . PP.vsep &!* [1, 2, 3::Int]+-- "three: 1, \n       2, \n       3" -- (&!*) :: forall tag m t          . (Sayable tag m, Foldable t)@@ -640,6 +590,14 @@ -- items.  This is essentially a combination of the '&!' and '&*' operators where -- the first operation is applied to the entire list, rather than each element of -- the list (as with `&!*`).+--+-- >>> sez @"info" $ t'"three:" &- PP.align &!$* [1, 2, 3::Int]+-- "three: 1, 2, 3"+--+-- As with the '&!*' operator (and unlike the '&*' operator), a null collection+-- is passed to the converter first argument.+--+-- @since: 1.1.0.0 (&!$*) :: forall tag m t          . (Sayable tag m, Foldable t)       => (PP.Doc SayableAnn -> PP.Doc SayableAnn) -> t m -> Saying tag@@ -649,11 +607,11 @@ infixl 2 &!$*  --- | A helper operator that applies the first argument which converts+-- | A helper operator that applies the first argument (which converts -- an array of 'Prettyprinter.Doc ann' elements to a single--- 'PrettyPrinter.Doc ann' element to the second argument, which is a+-- 'PrettyPrinter.Doc ann' element) to the second argument, which is a -- Foldable collection of 'Sayable' items.  This is essentially a--- combination of the '&!' and '&+*' operators.+-- combination of the '&!' and '&:*' operators. -- -- Unlike the other operators defined in this package, this is a trinary operator -- rather than a binary operator.  Because function application (whitespace) is@@ -661,20 +619,18 @@ -- to prevent applying the second argument to the third argument before applying -- this operator. ----- > import qualified Prettyprinter as PP--- >--- > putStrLn $ sez @"info" $ PP.fillSep &!+* t'" and " $ ["one", "two", "three"]--- one and two and three+-- >>> sez @"info" $ t'"three:" &- (PP.align . PP.vsep &!:* (t'" or")) [1, 2, 3::Int]+-- "three: 1 or\n       2 or\n       3" ---(&!+*) :: forall tag m t b . (Sayable tag b, Sayable tag m, Foldable t)+(&!:*) :: forall tag m t b . (Sayable tag b, Sayable tag m, Foldable t)        => ([PP.Doc SayableAnn] -> PP.Doc SayableAnn) -> b -> t m -> Saying tag-pf &!+* b = let addElem e (s, p) =+pf &!:* b = let addElem e (s, p) =                   (Just b, (case s of                                Nothing -> saying (sayable @tag e)                                Just x -> saying (sayable @tag e &+ x)                            ) : p)             in Saying . pf . snd . foldr addElem (Nothing, [])-infixl 2 &!+*+infixl 2 &!:*   -- | A helper operator allowing a Sayable item to be wrapped in a@@ -682,26 +638,42 @@ -- 'Sayable' of the second argument in the 'Just' case, or just emits -- the 'Sayable' of the first argument if the second argument is -- 'Nothing'.+--+-- >>> sez @"info" $ t'"It's" &? Just (t'"something") &- t'"or" &? (Nothing :: Maybe Text)+-- "It's something or"+-- (&?) :: forall tag m e         . (Sayable tag m, Sayable tag e) => m -> Maybe e -> Saying tag m &? Nothing = sayable m m &? (Just a) = sayable m <> sayable a infixl 1 &? + -- | A helper operator allowing a Sayable item to be wrapped in a 'Maybe' and a -- prettyprinter conversion as the first argument.  This is a combination of the -- `&!` and `&?` operators.+--+-- >>> sez @"info" $ PP.group &!? Just (t'"hi")+-- "hi"+--+-- @since: 1.1.0.0 (&!?) :: forall tag e . (Sayable tag e)       => (PP.Doc SayableAnn -> PP.Doc SayableAnn) -> Maybe e -> Saying tag _ &!? Nothing = Saying mempty pf &!? (Just a) = Saying $ pf $ saying $ sayable @tag a infixl 1 &!? + -- | A helper operator that generates a newline between its two arguments.  Many -- times the '&-' operator is a better choice to allow normal prettyprinter -- layout capabilities, but in situations where it is known that multiple lines -- will or should be generated, this operator makes it easy to separate the -- lines.+--+-- >>> sez @"info" $ t'"Hello" &< t'"world"+-- "Hello\nworld"+--+-- @since: 1.1.0.0 (&<) :: forall saytag m n . (Sayable saytag m, Sayable saytag n)      => m -> n -> Saying saytag m &< n = Saying@@ -710,9 +682,15 @@          <> (saying $ sayable @saytag n) infixl 1 &< + -- | A helper operator that combines '&<' and '&*' which will generate a newline -- between its two arguments, where the second argument is a foldable collection -- whose elements will be sayable emitted with comma separators.+--+-- >>> sez @"info" $ t'"three:" &<* [1, 2, 3::Int]+-- "three:\n1, 2, 3"+--+-- @since: 1.1.0.0 (&<*) :: forall saytag m n t . (Sayable saytag m, Sayable saytag n, Foldable t)       => m -> t n -> Saying saytag m &<* n = let addElem e (s, Saying p) =@@ -724,9 +702,17 @@                   (snd $ foldr addElem ("", Saying PP.emptyDoc) n)) infixl 1 &<* + -- | A helper operator that emits the first argument and optionally emits a -- newline and the 'Just' value of the second argument if the second argument is--- not 'Nothing'+-- not 'Nothing' (a combination of the '&<' and '&?' operators).+--+-- >>> sez @"info" $ t'"First" &<? Just (t'"something")+-- "First\nsomething"+-- >>> sez @"info" $ t'"Then" &<? (Nothing :: Maybe Text)+-- "Then"+--+-- @since: 1.1.0.0 (&<?) :: forall saytag m n . (Sayable saytag m, Sayable saytag n)       => m -> Maybe n -> Saying saytag m &<? Nothing = sayable m@@ -737,8 +723,33 @@ infixl 1 &<?  --- | A helper function to use when @OverloadedStrings@ is active to--- identify the following quoted literal as a "Data.Text" object.+-- | A helper operator that emits the first argument and optionally emits a the+-- 'Just' value of the second argument immediately thereafter if the second+-- argument is not 'Nothing'+--+-- >>> sez @"info" $ t'"It's" &+? Nothing &- t'"ok" &+? Just "time"+-- "It's oktime"+--+-- @since: 1.2.0.0+(&+?) :: forall saytag m n . (Sayable saytag m, Sayable saytag n)+      => m -> Maybe n -> Saying saytag+m &+? Nothing = sayable m+m &+? (Just n) = Saying+                 $ (saying $ sayable @saytag m)+                 <> (saying $ sayable @saytag n)+infixl 1 &+?+++-- | A helper function to use when @OverloadedStrings@ is active to identify the+-- following quoted literal as a "Data.Text" object.  It is common to enable+-- OverloadedStrings because 'Prettyprinter.Pretty' declares an 'Data.String.IsString'+-- instance and thus facilitates the pretty-printing of string values, but this+-- causes GHC to emit warnings about assuming the types of strings, so this+-- function can be used to clarify the intended type.+--+-- >>> putStrLn $ t'"This is type: Data.Text"+-- "This is type: Data.Text"+-- t' :: Text -> Text t' = id {-# INLINE t' #-}
sayable.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               sayable-version:            1.1.1.0+version:            1.2.0.0 synopsis: Data structures, classes and operators for constructing context-adjusted pretty output description:    .@@ -21,6 +21,7 @@ category:           Text build-type:         Simple extra-doc-files:    CHANGELOG.md+                    README.md tested-with:        GHC == 9.6.2, GHC == 9.4.5, GHC == 9.2.7, GHC == 9.0.2, GHC == 8.10.7, GHC == 8.8.4  source-repository head@@ -48,3 +49,18 @@                     , bytestring                     , text                     , prettyprinter++test-suite sayableTests+    import:           bldspec+    default-language: Haskell2010+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          Test.hs+    build-depends:    base+                    , hspec+                    , prettyprinter+                    , sayable+                    , tasty >= 1.4 && < 1.5+                    , tasty-ant-xml >= 1.1 && < 1.2+                    , tasty-hspec >= 1.2 && < 1.3+                    , text
+ test/Test.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++import           Data.Text ( Text )+import qualified Prettyprinter as PP+import           Text.Sayable++import           Test.Hspec+import           Test.Tasty+import           Test.Tasty.Hspec+import           Test.Tasty.Runners.AntXML+++main :: IO ()+main = tests >>= defaultMainWithIngredients (antXMLRunner : defaultIngredients)++tests :: IO TestTree+tests = testGroup "Sayable" <$> sequence+  [+    testSpec "Operators" $+    describe "operator results shown in haddocks" $ do+      it "renders &-" $+        (sez @"info" $ t'"hello" &- t'"world") `shouldBe` "hello world"+      it "renders &+" $+        (sez @"info" $ t'"hello" &+ t'"world") `shouldBe` "helloworld"+      it "renders &%" $+        (sez @"info" $ t'"hello" &% (t'"world", t'"!")) `shouldBe` "hello (world, !)"+      it "renders &!" $+        (sez @"info" $ PP.group &! t'"hi") `shouldBe` "hi"+      it "renders &*" $+        (sez @"info" $ t'"three:" &* [1, 2, 3::Int]) `shouldBe` "three: 1, 2, 3"+      it "renders &+*" $+        (sez @"info" $ t'"three:" &- '(' &+* [1, 2, 3::Int] &+ ')')+        `shouldBe` "three: (1, 2, 3)"+      it "renders &:*" $+        (sez @"info" $ t'"three:" &- t'".." &:* [1, 2, 3::Int])+        `shouldBe` "three: 1..2..3"+      it "renders &!*" $+        (sez @"info" $ t'"three:" &- PP.align . PP.vsep &!* [1, 2, 3::Int])+        `shouldBe` "three: 1, \n       2, \n       3"+      it "renders &!$*" $+        (sez @"info" $ t'"three:" &- PP.align &!$* [1, 2, 3::Int])+         `shouldBe` "three: 1, 2, 3"+      it "renders &!:*" $+        (sez @"info" $ t'"three:" &- (PP.align . PP.vsep &!:* (t'" or")) [1, 2, 3::Int])+        `shouldBe` "three: 1 or\n       2 or\n       3"+      it "renders &?" $+        (sez @"info" $ t'"It's" &? Just (t'"something") &- t'"or" &? (Nothing :: Maybe Text))+        `shouldBe` "It's something or"+      it "renders &!?" $+        (sez @"info" $ PP.group &!? Just (t'"hi")) `shouldBe` "hi"+      it "renders &<" $+        (sez @"info" $ t'"Hello" &< t'"world") `shouldBe` "Hello\nworld"+      it "renders &<*" $+        (sez @"info" $ t'"three:" &<* [1, 2, 3::Int]) `shouldBe` "three:\n1, 2, 3"+      it "renders &<? Just" $+        (sez @"info" $ t'"First" &<? Just (t'"something"))+        `shouldBe` "First\nsomething"+      it "renders &<? Nothing" $+        (sez @"info" $ t'"Then" &<? (Nothing :: Maybe Text)) `shouldBe` "Then"+      it "renders &+?" $+        (sez @"info" $ t'"It's" &+? (Nothing :: Maybe Text) &- t'"ok" &+? Just ("time" :: Text))+        `shouldBe` "It's oktime"+  ]