packages feed

yasi 0.1.2.1 → 0.2.0.0

raw patch · 7 files changed

+115/−187 lines, 7 filesdep +ghc-hs-metadep +text-displaydep −bytestringdep ~basedep ~tastydep ~template-haskellPVP ok

version bump matches the API change (PVP)

Dependencies added: ghc-hs-meta, text-display

Dependencies removed: bytestring

Dependency ranges changed: base, tasty, template-haskell

API changes (from Hackage documentation)

- Yasi: iB :: QuasiQuoter
- Yasi: iBL :: QuasiQuoter
- Yasi.Internal: ShowAbs :: Segment
- Yasi.Internal: ShowVar :: String -> Segment
- Yasi.Internal: Var :: String -> Segment
- Yasi.Internal: class Stringy a b
- Yasi.Internal: instance Yasi.Internal.Stringy Data.ByteString.Internal.ByteString Data.ByteString.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.ByteString.Internal.ByteString Data.ByteString.Lazy.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.ByteString.Lazy.Internal.ByteString Data.ByteString.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.ByteString.Lazy.Internal.ByteString Data.ByteString.Lazy.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Lazy.Text Data.ByteString.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Lazy.Text Data.ByteString.Lazy.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Lazy.Text Data.Text.Internal.Lazy.Text
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Lazy.Text Data.Text.Internal.Text
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Lazy.Text GHC.Base.String
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Text Data.ByteString.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Text Data.ByteString.Lazy.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Text Data.Text.Internal.Lazy.Text
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Text Data.Text.Internal.Text
- Yasi.Internal: instance Yasi.Internal.Stringy Data.Text.Internal.Text GHC.Base.String
- Yasi.Internal: instance Yasi.Internal.Stringy GHC.Base.String Data.ByteString.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy GHC.Base.String Data.ByteString.Lazy.Internal.ByteString
- Yasi.Internal: instance Yasi.Internal.Stringy GHC.Base.String Data.Text.Internal.Lazy.Text
- Yasi.Internal: instance Yasi.Internal.Stringy GHC.Base.String Data.Text.Internal.Text
- Yasi.Internal: instance Yasi.Internal.Stringy GHC.Base.String GHC.Base.String
- Yasi.Internal: stringy :: Stringy a b => a -> b
+ Yasi.Internal: Exp :: String -> Segment
+ Yasi.Internal: class Displayish a
+ Yasi.Internal: class Stringish a
+ Yasi.Internal: displayish :: Displayish a => a -> Builder
+ Yasi.Internal: instance Data.Text.Display.Display a => Yasi.Internal.Displayish a
+ Yasi.Internal: instance Yasi.Internal.Displayish GHC.Base.String
+ Yasi.Internal: instance Yasi.Internal.Stringish Data.Text.Internal.Lazy.Text
+ Yasi.Internal: instance Yasi.Internal.Stringish Data.Text.Internal.Text
+ Yasi.Internal: instance Yasi.Internal.Stringish GHC.Base.String
+ Yasi.Internal: stringish :: Stringish a => Builder -> a
- Yasi.Internal: ipExpr :: Exp -> (Exp -> Exp) -> [Segment] -> Q Exp
+ Yasi.Internal: ipExpr :: (Exp -> Exp) -> [Segment] -> Q Exp

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for yasi +## 0.2.0.0 -- 2022-03-13++ * Support interpolating arbitrary expressions.+ * Use `Display` from the `text-display` package.+ ## 0.1.2.1 -- 2021-03-15   * Allow to interpolate lazy into strict `ByteString`s and vice versa.
src/Yasi.hs view
@@ -1,11 +1,11 @@ -- | Yet another string interpolator -----  * Dead simple---  * No dependency on [haskell-src-meta](https://hackage.haskell.org/package/haskell-src-meta).---    It is not actively developed, has long compile times and several bugs, some of which are---    by design (e.g. operator fixities).---  * Supports interpolating 'String', 'Data.Text.Text', lazy 'Data.Text.Lazy.Text',---    'Data.ByteString.ByteString' and lazy 'Data.ByteString.Lazy.ByteString' (UTF8).+--  * Very simple, few dependencies.+--  * Based on 'Data.Text.Display.Display' instead of 'Show'.+--  * Depends on [ghc-hs-meta](https://hackage.haskell.org/package/ghc-hs-meta)+--    instead of [haskell-src-meta](https://hackage.haskell.org/package/haskell-src-meta)+--    for interpolating arbitrary expressions.+--    This result in faster compile times and fewer bugs. module Yasi   ( i, @@ -14,13 +14,9 @@     iS,     iT,     iTL,-    iB,-    iBL,   ) where -import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL import Data.String (IsString (..)) import qualified Data.Text as T import qualified Data.Text.Lazy as TL@@ -31,11 +27,14 @@ -- $setup -- >>> import Yasi -- >>> import Data.Text (Text)--- >>> import Data.ByteString (ByteString)+-- >>> import qualified Data.Text.Lazy  int :: (TH.Exp -> TH.Exp) -> TH.QuasiQuoter int = interpolator '$' +intT :: TH.Name -> TH.QuasiQuoter+intT = int . flip TH.SigE . TH.ConT+ -- | The main interpolator, intended to be used with -- [@QuasiQuotes@](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/exts/template_haskell.html#extension-QuasiQuotes). --@@ -45,18 +44,13 @@ -- >>> [i|${foo}string $bar|] :: String -- "yet another string interpolator" --+-- The result type can be 'String', strict 'T.Text' or lazy 'TL.Text'.+-- -- You can also use @${}@ to create a function interpolator (this "abstraction" feature is inspired by -- [interpolate](https://hackage.haskell.org/package/interpolate)): ----- >>> [i|more ${}${} code|] "point" "free" :: Text--- "more pointfree code"------ To use 'show' to interpolate a value:------ >>> let x = 1 + 1 in [i|1 + 1 = ${show x}|] :: ByteString--- "1 + 1 = 2"--- >>> [i|2 + 2 = $show|] (2 + 2) :: Text--- "2 + 2 = 4"+-- >>> [i|more ${}${} code${replicate 3 '!'}|] "point" "free" :: Text+-- "more pointfree code!!!" i :: TH.QuasiQuoter i = int id @@ -69,9 +63,6 @@ iFS :: TH.QuasiQuoter iFS = int $ TH.AppE (TH.VarE 'fromString) -intT :: TH.Name -> TH.QuasiQuoter-intT = int . flip TH.SigE . TH.ConT- -- | Like 'i', but with the result type fixed to 'String'. -- -- @['iS'|...|] = ['i'|...|] :: 'String'@@@ -81,7 +72,7 @@ iS :: TH.QuasiQuoter iS = intT ''String --- | Like 'i', but with the result type fixed to 'T.Text'.+-- | Like 'i', but with the result type fixed to strict 'T.Text'. -- -- @['iT'|...|] = ['i'|...|] :: 'T.Text'@ --@@ -90,19 +81,11 @@ iT :: TH.QuasiQuoter iT = intT ''T.Text --- | Like 'iT', but lazy.-iTL :: TH.QuasiQuoter-iTL = intT ''TL.Text---- | Like 'i', but with the result type fixed to 'B.ByteString'.+-- | Like 'i', but with the result type fixed to lazy 'TL.Text'. ----- @['iB'|...|] = ['i'|...|] :: 'B.ByteString'@+-- @['iTL'|...|] = ['i'|...|] :: 'TL.Text'@ ----- >>> :t [iB|hi|]--- [iB|hi|] :: ByteString-iB :: TH.QuasiQuoter-iB = intT ''B.ByteString---- | Like 'iB', but lazy.-iBL :: TH.QuasiQuoter-iBL = intT ''BL.ByteString+-- >>> :t [iTL|hi|]+-- [iTL|hi|] :: Data.Text.Lazy.Text+iTL :: TH.QuasiQuoter+iTL = intT ''TL.Text
src/Yasi/Internal.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE UndecidableInstances #-}  -- | Internal module, no stability guarantees module Yasi.Internal@@ -6,92 +6,88 @@     parseSegments,     ipExpr,     interpolator,-    Stringy (..),+    Displayish (..),+    Stringish (..),   ) where  import Control.Monad ((>=>))-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL import qualified Data.Char as C import qualified Data.Text as T-import qualified Data.Text.Encoding as T+import qualified Data.Text.Display as TD import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Encoding as TL+import qualified Data.Text.Lazy.Builder as TLB import GHC.Generics (Generic)+import qualified Language.Haskell.Meta.Parse as GhcHsMeta+import qualified Language.Haskell.TH.Lib as TH import qualified Language.Haskell.TH.Quote as TH import qualified Language.Haskell.TH.Syntax as TH -#if !(MIN_VERSION_base(4,13,0))-import Control.Monad.Fail (MonadFail)-#endif-#if !(MIN_VERSION_base(4,11,0))-import Data.Semigroup ((<>))-#endif- data Segment   = Lit String-  | Var String-  | ShowVar String+  | Exp String   | Abs -- idea due to interpolate-  | ShowAbs   deriving (Show, Eq, Generic)  parseSegments :: MonadFail m => Char -> String -> m [Segment] parseSegments c = fmap (group []) . go   where-    -- rewrite this+    -- ugly, but simple enough™     go s       | let (lit, rest) = span (/= c) s, not (null lit) = (Lit lit :) <$> go rest       | s == "" = pure []       | s == [c] = fail $ "should not end with single " <> [c]       | _ : c' : rest <- s, c == c' = (Lit [c] :) <$> go rest-      | _ : '{' : rest <- s = case span (/= '}') rest of-        (var, '}' : rest) ->-          let seg = case var of-                "" -> Abs-                "show" -> ShowAbs-                's' : 'h' : 'o' : 'w' : ' ' : var -> ShowVar var-                var -> Var var-           in (seg :) <$> go rest-        _ -> fail "missing closing bracket"+      | _ : '{' : rest <- s = case span (/= '}') rest of -- TODO smarter?+          (exp, '}' : rest) ->+            let seg = if exp == "" then Abs else Exp exp+             in (seg :) <$> go rest+          _ -> fail "missing closing bracket"       | _ : v : rest' <- s,         isVarStartChar v =-        let (vs, rest) = span isVarChar rest'-            var = v : vs-            s = if var == "show" then ShowAbs else Var var-         in (s :) <$> go rest+          let (vs, rest) = span isVarChar rest'+           in (Exp (v : vs) :) <$> go rest       | otherwise = fail $ "invalid char after " <> [c]     isVarStartChar v = C.isAscii v && C.isAlpha v     isVarChar v = C.isAscii v && (C.isAlphaNum v || v == '_' || v == '\'')      group ls [] = lit ls     group ls (Lit l : ss) = group (l : ls) ss-    group ls (s : ss) = lit ls ++ (s : group [] ss)+    group ls (s : ss) = lit ls <> (s : group [] ss)     lit [] = []     lit ls = [Lit $ mconcat $ reverse ls] -ipExpr :: TH.Exp -> (TH.Exp -> TH.Exp) -> [Segment] -> TH.Q TH.Exp-ipExpr cast combine segs = do+ipExpr :: (TH.Exp -> TH.Exp) -> [Segment] -> TH.Q TH.Exp+ipExpr transform segs = do   (ls, lams) <- go segs-  pure $ lams $ combine $ TH.ListE ls+  pure+    . lams+    . transform+    . TH.AppE (TH.VarE 'stringish)+    . foldr (flip TH.UInfixE (TH.VarE '(<>))) (TH.VarE 'mempty)+    $ ls   where     go = \case       [] -> pure ([], id)-      (s : ss) -> prep (go ss) $ case s of-        Lit l -> pure (TH.SigE (TH.LitE (TH.StringL l)) (TH.ConT ''String), id)-        Var v -> pure (TH.VarE (TH.mkName v), id)-        ShowVar v -> pure (TH.AppE (TH.VarE 'show) $ TH.VarE (TH.mkName v), id)+      s : ss -> prep (go ss) case s of+        Lit l -> (,id) <$> [|$(TH.stringE l) :: String|]+        Exp e -> do+          exts <- TH.extsEnabled+          exp <- case GhcHsMeta.parseExpWithExts exts e of+            Right e -> pure e+            Left (line, col, msg) ->+              fail . unlines $+                [ "Parse error at splice `" <> e <> "`:",+                  show line <> ":" <> show col <> ": " <> msg+                ]+          pure (exp, id)         Abs -> do           n <- TH.newName "int"           pure (TH.VarE n, TH.LamE [TH.VarP n])-        ShowAbs -> do-          n <- TH.newName "showint"-          pure (TH.AppE (TH.VarE 'show) $ TH.VarE n, TH.LamE [TH.VarP n])     prep asg af = do       (as, g) <- asg       (a, f) <- af-      pure (TH.AppE cast a : as, f . g)+      pure (TH.AppE (TH.VarE 'displayish) a : as, f . g)  interpolator ::   Char ->@@ -100,67 +96,29 @@   TH.QuasiQuoter interpolator c pp = TH.QuasiQuoter {..}   where-    quoteExp = parseSegments c >=> ipExpr (TH.VarE 'stringy) (pp . TH.AppE (TH.VarE 'mconcat))+    quoteExp = parseSegments c >=> ipExpr pp     quotePat = const $ fail "pattern context not supported"     quoteType = const $ fail "type context not supported"     quoteDec = const $ fail "declaration context not supported" -class Stringy a b where-  stringy :: a -> b--instance Stringy String String where-  stringy = id--instance Stringy String T.Text where-  stringy = T.pack--instance Stringy String TL.Text where-  stringy = TL.pack--instance Stringy String B.ByteString where-  stringy = T.encodeUtf8 . T.pack--instance Stringy String BL.ByteString where-  stringy = TL.encodeUtf8 . TL.pack--instance Stringy T.Text T.Text where-  stringy = id--instance Stringy T.Text String where-  stringy = T.unpack--instance Stringy T.Text TL.Text where-  stringy = TL.fromStrict--instance Stringy T.Text B.ByteString where-  stringy = T.encodeUtf8--instance Stringy T.Text BL.ByteString where-  stringy = BL.fromStrict . T.encodeUtf8--instance Stringy TL.Text TL.Text where-  stringy = id--instance Stringy TL.Text String where-  stringy = TL.unpack--instance Stringy TL.Text T.Text where-  stringy = TL.toStrict+class Displayish a where+  displayish :: a -> TLB.Builder -instance Stringy TL.Text B.ByteString where-  stringy = BL.toStrict . TL.encodeUtf8+instance {-# OVERLAPPABLE #-} TD.Display a => Displayish a where+  displayish = TD.displayBuilder -instance Stringy TL.Text BL.ByteString where-  stringy = TL.encodeUtf8+-- String is still used too pervasively...+instance Displayish String where+  displayish = TLB.fromString -instance Stringy B.ByteString B.ByteString where-  stringy = id+class Stringish a where+  stringish :: TLB.Builder -> a -instance Stringy B.ByteString BL.ByteString where-  stringy = BL.fromStrict+instance Stringish String where+  stringish = TL.unpack . TLB.toLazyText -instance Stringy BL.ByteString BL.ByteString where-  stringy = id+instance Stringish T.Text where+  stringish = TL.toStrict . TLB.toLazyText -instance Stringy BL.ByteString B.ByteString where-  stringy = BL.toStrict+instance Stringish TL.Text where+  stringish = TLB.toLazyText
test/Interpolations.hs view
@@ -1,44 +1,39 @@-{-# LANGUAGE CPP #-}- module Interpolations where +import Control.Applicative (Const (..)) import qualified Data.Text as T-import qualified Data.Text.Encoding as T+import Data.Text.Display (display)+import qualified Data.Text.Lazy as TL import Hedgehog import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import Test.Tasty+import Test.Tasty.HUnit import Test.Tasty.Hedgehog import Yasi -#if !(MIN_VERSION_base(4,11,0))-import Data.Semigroup ((<>))-#endif- test_interpolation :: TestTree test_interpolation =   testGroup     "interpolation"-    [ testProperty "to String" . property $ do+    [ testPropertyNamed "to String" "interpolation_to_String" . property $ do         (a, b, c) <- (,,) <$> forAll genString <*> forAll genText <*> forAll genString         [i|${a}++$b🙀🙀🙀🙀$c🙀|] === a <> "++" <> T.unpack b <> "🙀🙀🙀🙀" <> c <> "🙀",-      testProperty "to Text" . property $ do+      testPropertyNamed "to Text" "interpolation_to_Text" . property $ do         (a, b, c) <- (,,) <$> forAll genText <*> forAll genText <*> forAll genString         [i|😶😶$a😶😶$b😶😶$c😶😶|] === "😶😶" <> a <> "😶😶" <> b <> "😶😶" <> T.pack c <> "😶😶",-      testProperty "to ByteString" . property $ do-        (a, b, c) <- (,,) <$> forAll genBS <*> forAll genText <*> forAll genString-        let p = T.encodeUtf8 "℘"-        [i|℘$a℘$b℘$c℘|] === p <> a <> p <> T.encodeUtf8 b <> p <> T.encodeUtf8 (T.pack c) <> p,-      testProperty "abstractions" . property $ do-        (a, b) <- (,) <$> forAll genText <*> forAll genInt-        [i|xxx-${}$show-yyy|] a (b :: Int) === "xxx-" <> a <> T.pack (show b) <> "-yyy",-      testProperty "variants" . property $ do-        (a, b) <- (,) <$> forAll genText <*> forAll genInt-        [iT|xxx-${}$show-yyy|] a (b :: Int) === "xxx-" <> a <> T.pack (show b) <> "-yyy"+      testPropertyNamed "abstractions" "interpolation_abstractions" . property $ do+        (a, b :: Int) <- (,) <$> forAll genText <*> forAll genInt+        [i|xxx-${}${}-yyy|] a b === "xxx-" <> a <> display b <> "-yyy",+      testCase "interpolationg expressions" do+        let foo :: Int -> Int -> Int+            foo a b = a + b - 3+        [iT|${1+1 :: Int}-${foo 2 4}|] @?= T.pack "2-3"+        [iTL|${1+1 :: Int}-${foo 2 4}|] @?= TL.pack "2-3"+        [iFS|${1+1 :: Int}-${foo 2 4}|] @?= (Const "2-3" :: Const String (Int -> Bool))     ]   where-    genString = Gen.string range Gen.unicodeAll-    genText = Gen.text range Gen.unicodeAll-    genBS = Gen.bytes range+    genString = Gen.string range Gen.unicode+    genText = Gen.text range Gen.unicode     range = Range.constant 0 20     genInt = Gen.integral $ Range.constant 0 1000
test/Segments.hs view
@@ -6,22 +6,18 @@ unit_segment_parsing :: IO () unit_segment_parsing = do   ps "foo\\\"" @?= Just [Lit "foo\\\""]-  ps "$a-$b" @?= Just [Var "a", Lit "-", Var "b"]+  ps "$a-$b" @?= Just [Exp "a", Lit "-", Exp "b"]   ps "asef$" @?= Nothing   ps "lol$${}" @?= Just [Lit "lol${}"]   ps "lol$$${}" @?= Just [Lit "lol$", Abs]-  ps "${89+}${}" @?= Just [Var "89+", Abs]+  ps "${89+}${}" @?= Just [Exp "89+", Abs]   ps "${}}" @?= Just [Abs, Lit "}"]-  ps "asdf${show bar}$$" @?= Just [Lit "asdf", ShowVar "bar", Lit "$"]   ps "a$$b" @?= Just [Lit "a$b"]-  ps "$a$b$c" @?= Just [Var "a", Var "b", Var "c"]+  ps "$a$b$c" @?= Just [Exp "a", Exp "b", Exp "c"]   ps "" @?= Just []   ps " " @?= Just [Lit " "]-  ps "$foobar:\n - $b" @?= Just [Var "foobar", Lit ":\n - ", Var "b"]+  ps "$foobar:\n - $b" @?= Just [Exp "foobar", Lit ":\n - ", Exp "b"]   ps "${foobar]" @?= Nothing   ps "test-$0foo" @?= Nothing-  ps "${show }" @?= Just [ShowVar ""]-  ps "${show}" @?= Just [ShowAbs]-  ps "$show" @?= Just [ShowAbs]   where     ps = parseSegments '$'
test/Util.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE CPP #-}- module Util where  import qualified Data.Text as T@@ -11,17 +9,13 @@ import Test.Tasty.Hedgehog import Yasi.Util -#if !(MIN_VERSION_base(4,11,0))-import Data.Semigroup ((<>))-#endif- test_unindent :: TestTree test_unindent =   testGroup     "unindentation"     [ testCase "empty string" $ do         unindent "" @?= "",-      testProperty "single line" . property $ do+      testPropertyNamed "single line" "unindentation_single_line" . property $ do         t <- forAll genTextWONL         unindent t === (T.dropWhile (== ' ') t <> "\n"),       testCase "multi line" $ do
yasi.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: yasi-version: 0.1.2.1+version: 0.2.0.0  synopsis: Yet another string interpolator description:@@ -23,27 +23,23 @@  common commons   default-language: Haskell2010-  ghc-options: -Wall -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -fno-warn-name-shadowing-  if impl(ghc >= 8.2)-    ghc-options: -fhide-source-paths-  if impl(ghc >= 8.10)-    ghc-options: -Wunused-packages-  default-extensions: DeriveGeneric FlexibleContexts FlexibleInstances LambdaCase MultiParamTypeClasses OverloadedStrings QuasiQuotes RecordWildCards TemplateHaskell+  ghc-options: -Wall -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -fno-warn-name-shadowing -Wunused-packages+  default-extensions: BlockArguments DeriveGeneric FlexibleContexts FlexibleInstances LambdaCase MultiParamTypeClasses OverloadedStrings QuasiQuotes RecordWildCards ScopedTypeVariables TemplateHaskell TupleSections  library   import: commons-  if impl(ghc >= 8.4)-    ghc-options: -Wmissing-export-lists+  ghc-options: -Wmissing-export-lists   hs-source-dirs: src   exposed-modules:     Yasi     Yasi.Internal     Yasi.Util   build-depends:-      base >= 4.9 && < 5-    , template-haskell >= 2.11 && < 2.18-    , text ^>= 1.2-    , bytestring >= 0.10.8 && < 0.12+      base >= 4.14 && < 5+    , template-haskell >= 2.16 && < 2.19+    , text >= 1.2 && < 2.1+    , text-display ^>= 0.0+    , ghc-hs-meta ^>= 0.1  test-suite tasty   import: commons@@ -57,10 +53,11 @@       base     , yasi     , text-    , tasty >= 1.3 && <= 1.5+    , text-display+    , tasty >= 1.3 && < 1.5     , tasty-hunit ^>= 0.10-    , tasty-hedgehog ^>= 1.0-    , hedgehog ^>= 1.0+    , tasty-hedgehog >= 1.0 && < 1.3+    , hedgehog >= 1.0 && < 1.2   other-modules:       Segments       Interpolations