packages feed

marvin-interpolate 0.3.0 → 0.4.0

raw patch · 4 files changed

+77/−78 lines, 4 filesdep ~marvin-interpolatePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: marvin-interpolate

API changes (from Hackage documentation)

+ Marvin.Interpolate: parser :: ParseM Parsed

Files

README.md view
@@ -15,7 +15,7 @@  import Marvin.Interpolate -myStr = [iq|some string %{show $ map succ [1,2,3]} and data |]+myStr = [iq|some string #{show $ map succ [1,2,3]} and data |] -- "some string [2,3,4] and data" ``` @@ -26,11 +26,11 @@  import Marvin.Interpolate -myStr = $(is "some string %{show $ map succ [1,2,3]} and data")+myStr = $(is "some string #{show $ map succ [1,2,3]} and data") -- "some string [2,3,4] and data" ``` -It basically transforms the interpolated string  `[iq|interpolated string|]`, or in splices `$(is "interpolated string")` into a concatenation of all string bits and the expressions in `%{}`.+It basically transforms the interpolated string  `[iq|interpolated string|]`, or in splices `$(is "interpolated string")` into a concatenation of all string bits and the expressions in `#{}`. Therefore it is not limited to `String` alone, rather it produces a literal at compile time, which can either be interpreted as `String` or, using the [`OverloadedStrings`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#overloaded-string-literals) extension, as `Text` or `ByteString` or any other string type.  `i` (for *interpolate quoter*) and `is` (for *interpolate splice*) is the basic interpolator, which inserts the expressions verbatim. Hence when using `iq` or `is` all expressions must return the desired string type.@@ -53,28 +53,23 @@ Interpolation uses the quasi quoter sytax, which starts with `[interpolator_name|` and ends with `|]` or splice syntax `$(interpolator "interpolated string")`. Anything in between is interpreted by the library. -The format string in between uses the syntax `%{expression}`.+The format string in between uses the syntax `#{expression}`. Any valid Haskell expression can be used inside the braces. And all names which are in scope can be used, like so.  ```haskell-let x = 5 in [iS|x equals %{x}|] -- > "x equals 5"+let x = 5 in [iS|x equals #{x}|] -- > "x equals 5" ``` -There are four escape sequences to allow literal `%{` and `|]`+There are four escape sequences to allow literal `#{` and `|]`  | Input | Output | |-------|--------|-| `~]`  | `]`    |-| `~%`  | `%`    |-| `~}`  | `}`    | -| `~~`  | `~`    |+| `#]`  | `]`    |+| `##`  | `#`    |  -As a result the sequence `~%{` will show up as a literal `%{` in the output and `|~]` results in a literal `|]`.-Note that these are simple substitutions. -In general the characters themselves, if not escaped, will not throw errors, aka `~` will be `~` again in the output.-Escaping is only necessary in cases where these cahracters would have a special meaning otherwise.+As a result the sequence `##{` will show up as a literal `#{` in the output and `|#]` results in a literal `|]`.   ## Differences to/Advantages over other libraries
marvin-interpolate.cabal view
@@ -1,5 +1,5 @@ name: marvin-interpolate-version: 0.3.0+version: 0.4.0 cabal-version: >=1.10 build-type: Simple license: BSD3@@ -43,7 +43,7 @@     main-is: Spec.hs     build-depends:         base >=4.9.0.0 && <4.10,-        marvin-interpolate >=0.3.0 && <0.4,+        marvin-interpolate >=0.4.0 && <0.5,         hspec >=2.2.4 && <2.3,         text >=1.2.2.1 && <1.3     default-language: Haskell2010
src/Marvin/Interpolate.hs view
@@ -15,6 +15,7 @@   , iq   -- * Internals/extension points   , interpolateInto+  , parser   ) where  @@ -36,41 +37,56 @@ escapeChar :: Char escapeChar = '~' +type ParseM = Parsec String Int -parser :: Parsec String () Parsed++parser :: ParseM Parsed parser = manyTill (parseInterpolation <|> parseString) eof -parseString :: Parsec String () (Either String String)-parseString = Right <$> parseTillEscape "%{" True+parseString :: ParseM (Either String String)+parseString = do+    chunk <- many $ noneOf ['#']+    fmap (Right . (chunk ++)) $ (eof >> return "") <|> (lookAhead (try (char '#' >> anyChar)) >>= endOrEscape) <|> fmap return anyChar+  where+    endOrEscape :: Char -> ParseM String+    endOrEscape '{' = return ""+    endOrEscape '#' = count 2 anyChar >> return "#"+    endOrEscape ']' = count 2 anyChar >> return "]"+    endOrEscape _ = fail "" -parseInterpolation :: Parsec String () (Either String String)-parseInterpolation = Left <$> between (try $ string "%{") (char '}') (parseTillEscape "}" False) -parseTillEscape :: String -> Bool -> Parsec String () String-parseTillEscape endSeq@(endChar:_) allowEOF = do-    chunk <- many $ noneOf [escapeChar, endChar]-    rest <- eofEND-              <|> (char escapeChar >> parseEscaped)-              <|> (lookAhead (try $ string endSeq) >> return "")-              <|> (return <$> char endChar)-    return $ chunk <> rest+parseInterpolation :: ParseM (Either String String)+parseInterpolation = (try $ string "#{") >> (Left <$> parseExpr)   where-    eofEND-        | allowEOF = eof >> return "" -- <|> (try (char escapeChar >> eof) >> char escapeChar >> return [escapeChar])-        | otherwise = fail "EOF not allowed in interpolation"+    parseExpr = do+        chunk <- many $ noneOf ['}', '"', '\'', '{']+        fmap (chunk ++) $ (eof >> error "eof in interpolation") <|> (anyChar >>= continue) -    parseEscaped = (eof >> return [escapeChar]) <|> do-        next <- anyChar-        let escaped-                | next == escapeChar = [escapeChar]-                | next == '%' = "%"-                | next == ']' = "]"-                | next == '}' = "}"-                | otherwise = escapeChar : [next]-        rest <- parseTillEscape endSeq allowEOF-        return $ escaped <> rest+    continue :: Char -> ParseM String+    continue '{' = modifyState succ >> fmap ('{':) parseExpr+    continue '}' = do+        s <- getState+        if s == 0+          then return ""+          else ('}':) <$> (modifyState succ >> parseExpr)+    continue '\"' = ('"':) <$> parseStr+    continue '\'' = do+        char '\''+        inner <- ((:) <$> char '\\' <*> fmap return anyChar) <|> fmap return anyChar+        char '\''+        return $ '\'':inner ++ "'" +    parseStr = do+        chunk <- many $ noneOf ['"', '\\']+        fmap (chunk ++) $ (eof >>= fail "eof in string literal") +                          <|> (anyChar >>= continueStr)+      where +        continueStr '"' = ('"':) <$> parseExpr+        continueStr '\\' = do+            escaped <- anyChar+            (\a -> '\\':escaped:a) <$> parseStr + evalExprs :: Parsed -> [Either Exp String] evalExprs l = evalState (mapM stitch l) decls   where@@ -103,7 +119,7 @@     foldl f (LitE (StringL "")) interleaved    where-    parsed = either (error . show) id $ parse parser "inline" str+    parsed = either (error . show) id $ runParser parser 0 "inline" str     interleaved = evalExprs parsed      f expr bit = AppE (VarE 'mappend) expr `AppE` bitExpr
test/Spec.hs view
@@ -28,26 +28,14 @@ main :: IO () main = hspec $ do     describe "parsing to itself" $ do-        it "%" $-            [iq|%|] `shouldBe` "%"-        it "%anything" $-            [iq|%anything|] `shouldBe` "%anything"-        it "~" $-            [iq|~|] `shouldBe` "~"-        it "~anything" $-            $(is "~anything") `shouldBe` "~anything"+        it "#" $+            [iq|#|] `shouldBe` "#"+        it "#anything" $+            [iq|#anything|] `shouldBe` "#anything" -    describe "parsing escape sequences" $ do-        it "parses ~% as %" $-            [iq|~%|] `shouldBe` "%"-        it "parses ~] as ]" $-            [iq|~]|] `shouldBe` "]"-        it "parses ~%{} as %{}" $-            [iq|~%{}|] `shouldBe` "%{}"-        it "parses |~] as |]" $-            [iq||~]|] `shouldBe` "|]"-        it "parses ~~ as ~" $-            [iq|~~|] `shouldBe` "~"+    describe "parsing escape sequences" $+        it "parses ## as #" $+            [iq|##|] `shouldBe` "#"      describe "interpolation substitution" $ do         it "leaves an empty string" $@@ -56,32 +44,32 @@             [iq|this is not changed|] `shouldBe` "this is not changed"         it "interpolates just an external variable" $             let y = "str" in-                [iq|%{y}|] `shouldBe` y+                [iq|#{y}|] `shouldBe` y         it "interpolates an external variable" $-            let y = "str2" in [iq| hello you %{y} end|] `shouldBe` " hello you " ++ y ++ " end"+            let y = "str2" in [iq| hello you #{y} end|] `shouldBe` " hello you " ++ y ++ " end"         it "interpolates the variable x (used to be special)" $-            let x = "str" in [iq| hello x: %{x}|] `shouldBe` " hello x: " ++ x+            let x = "str" in [iq| hello x: #{x}|] `shouldBe` " hello x: " ++ x         it "interpolates infix with $" $-            [iq|str %{show $ 4 + (5 :: Int)} str|] `shouldBe` "str 9 str"+            [iq|str #{show $ 4 + (5 :: Int)} str|] `shouldBe` "str 9 str"         it "interpolates multiple bindings" $             let                 x = "multiple"                 y = "can"                 z = "local scope"-            in [iq|We %{y} interpolate %{x} bindings from %{z}|]+            in [iq|We #{y} interpolate #{x} bindings from #{z}|]                 `shouldBe` "We can interpolate multiple bindings from local scope"          it "interpolates complex expressions" $             let                 x = ["haskell", "expression"]                 y = " can be"-            in [iq|Any %{intercalate " " x ++ y} interpolated|]+            in [iq|Any #{intercalate " " x ++ y} interpolated|]                  `shouldBe` "Any haskell expression can be interpolated"       describe "splice interpolation" $         it "interpolates a splice" $-            let x = 5 :: Int in $(isS "%{x}") `shouldBe` "5"+            let x = 5 :: Int in $(isS "#{x}") `shouldBe` "5"          describe "'is' generic interpolation" $ do         it "to string" $@@ -95,24 +83,24 @@      describe "'isS' interpolation to String" $ do         it "calls show on Int" $-            $(isS "%{x}") `shouldBe` "5"+            $(isS "#{x}") `shouldBe` "5"         it "calls showStr if available" $-            $(isS "%{G}") `shouldBe` "showStr"+            $(isS "#{G}") `shouldBe` "showStr"         it "does not change Text" $-            $(isS "%{\"str\" :: T.Text}") `shouldBe` "str"+            $(isS "#{\"str\" :: T.Text}") `shouldBe` "str"      describe "'isT' interpolation to Text" $ do         it "calls show on Int" $-            $(isT "%{x}") `shouldBe` "5"+            $(isT "#{x}") `shouldBe` "5"         it "calls showT if available" $-            $(isT "%{G}") `shouldBe` "showT"+            $(isT "#{G}") `shouldBe` "showT"         it "does not change Text" $-            $(isT "%{\"str\" :: T.Text}") `shouldBe` "str"+            $(isT "#{\"str\" :: T.Text}") `shouldBe` "str"      describe "'isL' interpolation to lazy Text" $ do         it "calls show on Int" $-            $(isL "%{x}") `shouldBe` "5"+            $(isL "#{x}") `shouldBe` "5"         it "calls showStr if available" $-            $(isL "%{G}") `shouldBe` "showL"+            $(isL "#{G}") `shouldBe` "showL"         it "does not change Text" $-            $(isL "%{\"str\" :: T.Text}") `shouldBe` "str"+            $(isL "#{\"str\" :: T.Text}") `shouldBe` "str"