packages feed

exon 1.1.0.0 → 1.2.0.0

raw patch · 12 files changed

+127/−74 lines, 12 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Exon.Class.Exon: SkipWs :: a -> SkipWs a
- Exon.Class.Exon: instance (Exon.Class.Exon.ExonAppend result builder, Exon.Class.Exon.ExonSegment result builder, Exon.Class.Exon.ExonBuilder inner builder) => Exon.Class.Exon.ExonBuild result inner
- Exon.Class.Exon: instance (GHC.Base.Monoid builder, result GHC.Types.~ builder) => Exon.Class.Exon.ExonBuilder result builder
- Exon.Class.Exon: instance Data.String.IsString a => Data.String.IsString (Exon.Class.Exon.SkipWs a)
- Exon.Class.Exon: instance Data.String.IsString builder => Exon.Class.Exon.ExonString (Exon.Class.Exon.SkipWs result) builder
- Exon.Class.Exon: instance Exon.Class.Exon.ExonString result builder => Exon.Class.Exon.ExonSegment result builder
- Exon.Class.Exon: instance GHC.Classes.Eq a => GHC.Classes.Eq (Exon.Class.Exon.SkipWs a)
- Exon.Class.Exon: instance GHC.Generics.Generic (Exon.Class.Exon.SkipWs a)
- Exon.Class.Exon: instance GHC.Show.Show a => GHC.Show.Show (Exon.Class.Exon.SkipWs a)
- Exon.Class.Exon: newtype SkipWs a
- Exon.Class.Exon: skipWs :: SkipWs a -> a
- Exon.Data.Result: instance GHC.Base.Monoid a => GHC.Base.Monoid (Exon.Data.Result.Result a)
- Exon.Quote: intron :: QuasiQuoter
+ Exon: class ExonExpression (result :: Type) (inner :: Type) (builder :: Type)
+ Exon: exonExpression :: ExonExpression result inner builder => (inner -> builder) -> inner -> Result builder
+ Exon.Class.Exon: class ExonExpression (result :: Type) (inner :: Type) (builder :: Type)
+ Exon.Class.Exon: exonExpression :: ExonExpression result inner builder => (inner -> builder) -> inner -> Result builder
+ Exon.Class.Exon: instance (Exon.Class.Exon.ExonAppend result builder, Exon.Class.Exon.ExonSegment result inner builder, Exon.Class.Exon.ExonBuilder inner builder) => Exon.Class.Exon.ExonBuild result inner
+ Exon.Class.Exon: instance (Exon.Class.Exon.ExonString result builder, Exon.Class.Exon.ExonExpression result inner builder) => Exon.Class.Exon.ExonSegment result inner builder
+ Exon.Class.Exon: instance (GHC.Base.Monoid builder, inner GHC.Types.~ builder) => Exon.Class.Exon.ExonBuilder inner builder
+ Exon.Class.Exon: instance Exon.Class.Exon.ExonExpression result inner builder
+ Exon.Data.Result: instance GHC.Base.Semigroup (Exon.Data.Result.Result a) => GHC.Base.Monoid (Exon.Data.Result.Result a)
+ Exon.SkipWs: SkipWs :: a -> SkipWs a
+ Exon.SkipWs: instance Data.String.IsString a => Data.String.IsString (Exon.SkipWs.SkipWs a)
+ Exon.SkipWs: instance Data.String.IsString builder => Exon.Class.Exon.ExonString (Exon.SkipWs.SkipWs result) builder
+ Exon.SkipWs: instance GHC.Classes.Eq a => GHC.Classes.Eq (Exon.SkipWs.SkipWs a)
+ Exon.SkipWs: instance GHC.Generics.Generic (Exon.SkipWs.SkipWs a)
+ Exon.SkipWs: instance GHC.Show.Show a => GHC.Show.Show (Exon.SkipWs.SkipWs a)
+ Exon.SkipWs: intron :: QuasiQuoter
+ Exon.SkipWs: newtype SkipWs a
+ Exon.SkipWs: skipWs :: SkipWs a -> a
- Exon: class ExonSegment (result :: Type) (builder :: Type)
+ Exon: class ExonSegment (result :: Type) (inner :: Type) (builder :: Type)
- Exon: exonAppend :: ExonAppend result builder => builder -> builder -> Result builder
+ Exon: exonAppend :: (ExonAppend result builder, Semigroup builder) => builder -> builder -> Result builder
- Exon: exonBuilder :: ExonBuilder inner builder => inner -> builder
+ Exon: exonBuilder :: (ExonBuilder inner builder, inner ~ builder) => inner -> builder
- Exon: exonBuilderExtract :: ExonBuilder inner builder => Result builder -> inner
+ Exon: exonBuilderExtract :: (ExonBuilder inner builder, Monoid builder) => inner ~ builder => Result builder -> inner
- Exon: exonSegment :: ExonSegment result builder => Segment builder -> Result builder
+ Exon: exonSegment :: ExonSegment result inner builder => (inner -> builder) -> Segment inner -> Result builder
- Exon.Class.Exon: class ExonSegment (result :: Type) (builder :: Type)
+ Exon.Class.Exon: class ExonSegment (result :: Type) (inner :: Type) (builder :: Type)
- Exon.Class.Exon: exonAppend :: ExonAppend result builder => builder -> builder -> Result builder
+ Exon.Class.Exon: exonAppend :: (ExonAppend result builder, Semigroup builder) => builder -> builder -> Result builder
- Exon.Class.Exon: exonBuilder :: ExonBuilder inner builder => inner -> builder
+ Exon.Class.Exon: exonBuilder :: (ExonBuilder inner builder, inner ~ builder) => inner -> builder
- Exon.Class.Exon: exonBuilderExtract :: ExonBuilder inner builder => Result builder -> inner
+ Exon.Class.Exon: exonBuilderExtract :: (ExonBuilder inner builder, Monoid builder) => inner ~ builder => Result builder -> inner
- Exon.Class.Exon: exonSegment :: ExonSegment result builder => Segment builder -> Result builder
+ Exon.Class.Exon: exonSegment :: ExonSegment result inner builder => (inner -> builder) -> Segment inner -> Result builder

Files

changelog.md view
@@ -1,3 +1,10 @@+# 1.2.0.0++* Add class `ExonExpression`, allowing customization of how interpolated expressions are converted to builders.+  The method `exonExpression` is provided with the function `exonBuilder` as an argument to avoid having to depend on+  the class manually.+* Change multi-method classes to use the default-method implementation style for the overlappable instance.+ # 1.1.0.0  * Add `exonConcat` to `ExonAppend`. The function is pulled out of `exonBuild` to allow using a different way of folding
exon.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           exon-version:        1.1.0.0+version:        1.2.0.0 synopsis:       Customizable Quasiquote Interpolation description:    See https://hackage.haskell.org/package/exon/docs/Exon.html category:       String@@ -38,6 +38,7 @@       Exon.Generic       Exon.Parse       Exon.Quote+      Exon.SkipWs   hs-source-dirs:       lib   default-extensions:
lib/Exon.hs view
@@ -24,6 +24,7 @@   ExonSegment (..),   ExonBuilder (..),   ExonString (..),+  ExonExpression (..),   ToSegment (..),   SkipWs (..),   skipWs,@@ -45,17 +46,17 @@   ExonAppend (..),   ExonBuild (..),   ExonBuilder (..),+  ExonExpression (..),   ExonSegment (..),   ExonString (..),   ExonUse (..),-  SkipWs (..),-  skipWs,   ) import Exon.Class.ToSegment (ToSegment (..)) import Exon.Combinators (intercalate) import Exon.Data.Result (Result (..)) import Exon.Data.Segment (Segment (..))-import Exon.Quote (exon, exonWith, exun, intron, exonws)+import Exon.Quote (exon, exonWith, exonws, exun)+import Exon.SkipWs (SkipWs (..), intron, skipWs)  {- $intro This Haskell library provides quasiquote string interpolation with customizable concatenation for arbitrary types.
lib/Exon/Class/Exon.hs view
@@ -12,21 +12,6 @@ import qualified Exon.Data.Segment as Segment import Exon.Data.Segment (Segment) --- |Wrapping a quote type with this causes whitespace to be ignored.------ @since 1.0.0.0-newtype SkipWs a =-  SkipWs a-  deriving stock (Eq, Show, Generic)-  deriving newtype (IsString)---- |Defined separately because TH chokes on the selector.------ @since 1.0.0.0-skipWs :: SkipWs a -> a-skipWs (SkipWs a) =-  a- -- |Wrapping a quote type with this causes @a@ to be used irrespective of whether it is an unwrappable newtype. -- -- @since 1.0.0.0@@ -45,20 +30,28 @@   -- |Construct a builder from the newtype-unwrapped result type.   exonBuilder :: inner -> builder -  -- |Convert the result of the builder concatenation back to the newtype-unwrapped result type.-  exonBuilderExtract :: Result builder -> inner--instance {-# overlappable #-} (-    Monoid builder,-    result ~ builder-  ) => ExonBuilder result builder where+  default exonBuilder :: inner ~ builder => inner -> builder   exonBuilder =     id   {-# inline exonBuilder #-}++  -- |Convert the result of the builder concatenation back to the newtype-unwrapped result type.+  exonBuilderExtract :: Result builder -> inner++  default exonBuilderExtract ::+    Monoid builder =>+    inner ~ builder =>+    Result builder ->+    inner   exonBuilderExtract =     fold   {-# inline exonBuilderExtract #-} +instance {-# overlappable #-} (+    Monoid builder,+    inner ~ builder+  ) => ExonBuilder inner builder where+ instance (     ExonBuilder a builder   ) => ExonBuilder (ExonUse a) builder where@@ -78,20 +71,26 @@ instance ExonBuilder LText Text.Builder where   exonBuilder =     Text.fromLazyText+  {-# inline exonBuilder #-}   exonBuilderExtract =     foldMap toLazyText+  {-# inline exonBuilderExtract #-}  instance ExonBuilder ByteString ByteString.Builder where   exonBuilder =     ByteString.byteString+  {-# inline exonBuilder #-}   exonBuilderExtract =     foldMap (toStrict . toLazyByteString)+  {-# inline exonBuilderExtract #-}  instance ExonBuilder LByteString ByteString.Builder where   exonBuilder =     ByteString.lazyByteString+  {-# inline exonBuilder #-}   exonBuilderExtract =     foldMap toLazyByteString+  {-# inline exonBuilderExtract #-}  -- |This class generalizes 'IsString' for use in 'ExonSegment'. --@@ -128,41 +127,47 @@     Result . showString   {-# inline exonString #-} --- |The instance used when the result type is wrapped in 'SkipWs', which is done by 'Exon.intron'.+-- |This class allows manipulation of interpolated expressions before they are processed, for example to replace empty+-- strings with 'Empty' for the purpose of collapsing multiple whitespaces. ----- It returns 'Empty' for any whitespace.-instance (-    IsString builder-  ) => ExonString (SkipWs result) builder where-  exonWhitespace _ =-    Empty-  {-# inline exonWhitespace #-}+-- The default instance does nothing.+class ExonExpression (result :: Type) (inner :: Type) (builder :: Type) where+  -- |Process a builder value constructed from an expression before concatenation.+  exonExpression :: (inner -> builder) -> inner -> Result builder+  exonExpression builder =+    Result . builder+  {-# inline exonExpression #-} +instance {-# overlappable #-} ExonExpression result inner builder where+ -- |This class converts a 'Segment' to a builder. ----- The default implementation performs the following conversions for the differnet segment variants:+-- The default implementation performs the following conversions for the different segment variants: -- -- - [Segment.String]('Segment.String') and [Segment.Whitespace]('Segment.Whitespace') are plain 'String's parsed -- literally from the quasiquote. -- They are converted to the builder type by 'fromString' (handled by 'ExonString'). ----- - [Segment.Whitespace]('Segment.Whitespace') is ignored when the quoter 'Exon.intron' was used.+-- - [Segment.Whitespace]('Segment.Whitespace') is ignored when the quoter 'Exon.intron' was used (default behaviour of+-- 'ExonString'). ----- - [Segment.Expression]('Segment.Expression') contains a value of the builder type, which is returned as-is.+-- - [Segment.Expression]('Segment.Expression') contains a value of the unwrapped type and is converted to a builder+-- using the function in the first argument, which is usually 'exonBuilder', supplied by 'exonBuild'. -- -- @since 1.0.0.0-class ExonSegment (result :: Type) (builder :: Type) where+class ExonSegment (result :: Type) (inner :: Type) (builder :: Type) where   -- |Convert literal string segments to the result type.-  exonSegment :: Segment builder -> Result builder+  exonSegment :: (inner -> builder) -> Segment inner -> Result builder  instance {-# overlappable #-} (-    ExonString result builder-  ) => ExonSegment result builder where-    exonSegment = \case+    ExonString result builder,+    ExonExpression result inner builder+  ) => ExonSegment result inner builder where+    exonSegment builder = \case       Segment.String a ->         exonString @result a       Segment.Expression a ->-        Result a+        exonExpression @result builder a       Segment.Whitespace a ->         exonWhitespace @result a     {-# inline exonSegment #-}@@ -177,6 +182,11 @@   -- |Concatenate two segments of the builder type.   exonAppend :: builder -> builder -> Result builder +  default exonAppend :: Semigroup builder => builder -> builder -> Result builder+  exonAppend z a =+    Result (z <> a)+  {-# inline exonAppend #-}+   -- |Concatenate a list of segments of the result type.   --   -- Folds the list over 'exonAppend', skipping over 'Empty' segments.@@ -196,9 +206,6 @@ instance {-# overlappable #-} (     Semigroup builder   ) => ExonAppend result builder where-  exonAppend z a =-    Result (z <> a)-  {-# inline exonAppend #-}  instance ExonAppend result (String -> String) where   exonAppend z a =@@ -207,12 +214,12 @@  -- |This class implements the 'Segment' concatenation logic. ----- 1. Each 'Segment.Expression' is converted to the builder type by 'ExonBuilder'.--- 2. Each 'Segment.String' and 'Segment.Whitespace' is converted to the builder type by 'ExonSegment' and 'ExonString'.--- 3. The segments are folded over 'ExonAppend'.--- 4. The result is converted from the builder type to the original type by 'ExonBuilder'.+-- 1. Each 'Segment' is converted to the builder type by 'ExonSegment' using 'exonBuilder' to construct the builder from+--    expressions.+-- 2. The segments are folded over 'ExonAppend'.+-- 3. The result is converted from the builder type to the original type by 'ExonBuilder'. ----- Each step may be overridden individually+-- Each step may be overridden individually by writing overlapping instances for the involved classes. -- -- @since 1.0.0.0 class ExonBuild (result :: Type) (inner :: Type) where@@ -221,13 +228,13 @@  instance {-# overlappable #-} (     ExonAppend result builder,-    ExonSegment result builder,+    ExonSegment result inner builder,     ExonBuilder inner builder   ) => ExonBuild result inner where   exonBuild =     exonBuilderExtract .     exonConcat @result .-    fmap (exonSegment @result . fmap exonBuilder)+    fmap (exonSegment @result exonBuilder)   {-# inline exonBuild #-}  -- |This class is the main entry point for Exon.
lib/Exon/Class/Newtype.hs view
@@ -15,7 +15,7 @@     OverNewtype wrapped next inner   ) => OverNewtype current ('Just wrapped) inner where     overNewtype f segments =-      unsafeCoerce (overNewtype @wrapped @next @inner f (fmap unsafeCoerce <$> segments))+      unsafeCoerce (overNewtype @wrapped @next @inner f (unsafeCoerce segments))     {-# inline overNewtype #-}  instance OverNewtype current 'Nothing current where
lib/Exon/Data/Result.hs view
@@ -13,6 +13,6 @@   (<>) a Empty = a   (<>) (Result l) (Result r) = Result (l <> r) -instance Monoid a => Monoid (Result a) where+instance Semigroup (Result a) => Monoid (Result a) where   mempty =-    Result mempty+    Empty
lib/Exon/Quote.hs view
@@ -9,7 +9,7 @@ import Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter)) import Language.Haskell.TH.Syntax (Quasi) -import Exon.Class.Exon (SkipWs (SkipWs), exonProcess, exonProcessWith, skipWs)+import Exon.Class.Exon (exonProcess, exonProcessWith) import Exon.Class.ToSegment (toSegment) import Exon.Data.RawSegment (RawSegment (AutoExpSegment, ExpSegment, StringSegment, WsSegment)) import qualified Exon.Data.Segment as Segment@@ -161,15 +161,6 @@ exun :: QuasiQuoter exun =   exonWith Nothing False True---- |A variant of 'exon' that ignores all literal whitespace in the quote (not in interpolated expressions).------ > [intron|x|] === skipWs [exonws|x|]------ @since 1.0.0.0-intron :: QuasiQuoter-intron =-  exonWith (Just ([e|SkipWs|], [e|skipWs|])) True False  -- |A variant of 'exon' that creates segments for each sequence of whitespace characters that can be processed -- differently by 'Exon.ExonAppend', 'Exon.ExonSegment' or 'Exon.ExonString'.
+ lib/Exon/SkipWs.hs view
@@ -0,0 +1,42 @@+-- |Whitespace skipping quoter, internal+module Exon.SkipWs where++import Language.Haskell.TH.Quote (QuasiQuoter)++import Exon.Class.Exon (ExonString (exonWhitespace))+import Exon.Data.Result (Result (Empty))+import Exon.Quote (exonWith)++-- |Wrapping a quote type with this causes whitespace to be ignored.+--+-- @since 1.0.0.0+newtype SkipWs a =+  SkipWs a+  deriving stock (Eq, Show, Generic)+  deriving newtype (IsString)++-- |Defined separately because TH chokes on the selector.+--+-- @since 1.0.0.0+skipWs :: SkipWs a -> a+skipWs (SkipWs a) =+  a++-- |The instance used when the result type is wrapped in 'SkipWs', which is done by 'Exon.intron'.+--+-- It returns 'Empty' for any whitespace.+instance (+    IsString builder+  ) => ExonString (SkipWs result) builder where+  exonWhitespace _ =+    Empty+  {-# inline exonWhitespace #-}++-- |A variant of 'exon' that ignores all literal whitespace in the quote (not in interpolated expressions).+--+-- > [intron|x|] === skipWs [exonws|x|]+--+-- @since 1.0.0.0+intron :: QuasiQuoter+intron =+  exonWith (Just ([e|SkipWs|], [e|skipWs|])) True False
test/Exon/Test/BasicTest.hs view
@@ -3,10 +3,11 @@ import Data.Text (toUpper) import Hedgehog (TestT, (===)) -import Exon.Class.Exon (ExonSegment (exonSegment), SkipWs (SkipWs), skipWs)+import Exon.Class.Exon (ExonSegment (exonSegment)) import Exon.Data.Result (Result (Result)) import qualified Exon.Data.Segment as Segment-import Exon.Quote (exon, exonws, intron)+import Exon.Quote (exon, exonws)+import Exon.SkipWs (SkipWs (SkipWs), intron, skipWs)  newtype Mon =   Mon String@@ -51,10 +52,10 @@  newtype Thing = Thing String deriving stock (Generic) deriving newtype (IsString, Show, Eq) -instance ExonSegment Thing String where-  exonSegment = \case+instance ExonSegment Thing inner String where+  exonSegment builder = \case     Segment.String s -> Result s-    Segment.Expression thing -> Result thing+    Segment.Expression thing -> Result (builder thing)     Segment.Whitespace _ -> Result " >>> "  test_customWhitespace :: TestT IO ()
test/Exon/Test/BuildTest.hs view
@@ -2,9 +2,10 @@  import Hedgehog (TestT, (===)) -import Exon.Class.Exon (SkipWs (SkipWs), exonProcess, exonProcessWith, skipWs)+import Exon.Class.Exon (exonProcess, exonProcessWith) import Exon.Class.ToSegment (toSegment) import Exon.Data.Segment (Segment (Expression, Whitespace))+import Exon.SkipWs (SkipWs (SkipWs), skipWs)  newtype Str =   Str String
test/Exon/Test/NewtypeTest.hs view
@@ -2,8 +2,9 @@  import Hedgehog (TestT, (===)) -import Exon.Class.Exon (exonUse, skipWs)+import Exon.Class.Exon (exonUse) import Exon.Quote (exon, exonws, exun)+import Exon.SkipWs (skipWs)  newtype Nt =   Nt Text
test/Exon/Test/SkipWsTest.hs view
@@ -2,7 +2,8 @@  import Hedgehog (TestT, (===)) -import Exon.Quote (exon, intron)+import Exon.Quote (exon)+import Exon.SkipWs (intron)  newtype Mon =   Mon String