qm-interpolated-string 0.1.0.0 → 0.1.1.0
raw patch · 5 files changed
+209/−67 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Text.InterpolatedString.QM: qn :: QuasiQuoter
Files
- CHANGELOG.md +5/−0
- README.md +13/−6
- qm-interpolated-string.cabal +2/−2
- src/Text/InterpolatedString/QM.hs +93/−35
- test/Spec.hs +96/−24
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+## 0.1.1.0++- Added `qn` QuasiQuoter as alternative to `qm` but without interpolation+- Some code refactoring+- More docs and tests
README.md view
@@ -3,10 +3,9 @@ [](https://hackage.haskell.org/package/qm-interpolated-string) [](https://travis-ci.org/unclechu/haskell-qm-interpolated-string) -Implementation of interpolated multiline strings that ignores indentation-and trailing whitespaces.--It's [QuasiQuoter](https://wiki.haskell.org/Quasiquotation).+Implementation of interpolated multiline string+[QuasiQuoter](https://wiki.haskell.org/Quasiquotation)+that ignores indentation and trailing whitespaces. Actually it's modification of [interpolatedstring-perl6](https://github.com/audreyt/interpolatedstring-perl6)@@ -14,13 +13,13 @@ This implementation looks just like `qc` from **interpolatedstring-perl6** package but ignores any indentation,-line-breaks (except explicitly written using `\n` char)+line breaks (except explicitly written using `\n` char) and trailing whitespaces. 'm' in 'qm' means 'multiline'. You could write a decoratively formatted string and your-decorative indentation and line-breaks wont go to the string,+decorative indentation and line breaks wont go to the string, but when you really need it, you could just escape it using backslash. ## Simple usage example@@ -61,6 +60,14 @@ ```haskell [qm| {1+2} \{3+4} |] -- Result: "3 {3+4}"+```++There is also very similar to `qm` QuasiQuoter+named as `qn` that do the same except interpolation:++```haskell+[qn| foo {1+2} |]+-- Result: "foo {1+2}" ``` ## Author
qm-interpolated-string.cabal view
@@ -1,5 +1,5 @@ name: qm-interpolated-string-version: 0.1.0.0+version: 0.1.1.0 synopsis: Implementation of interpolated multiline strings description: Implementation of interpolated multiline strings that ignores indentation and trailing whitespaces@@ -11,7 +11,7 @@ maintainer: Viacheslav Lotsmanov <lotsmanov89@gmail.com> category: Data build-type: Simple-extra-source-files: README.md+extra-source-files: README.md CHANGELOG.md cabal-version: >=1.8 tested-with: GHC == 7.6.3 , GHC == 7.8.4
src/Text/InterpolatedString/QM.hs view
@@ -13,12 +13,12 @@ {-# LANGUAGE CPP #-} -module Text.InterpolatedString.QM (qm, ShowQ(..)) where+module Text.InterpolatedString.QM (qm, qn, ShowQ(..)) where -import "base" GHC.Exts (IsString(..))+import "base" GHC.Exts (IsString (fromString)) import qualified "template-haskell" Language.Haskell.TH as TH-import "template-haskell" Language.Haskell.TH.Quote-import "haskell-src-meta" Language.Haskell.Meta.Parse+import "template-haskell" Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter))+import "haskell-src-meta" Language.Haskell.Meta.Parse (parseExp) import "bytestring" Data.ByteString.Char8 as Strict (ByteString, unpack) import "bytestring" Data.ByteString.Lazy.Char8 as Lazy (ByteString, unpack) import "text" Data.Text as T (Text, unpack)@@ -67,49 +67,69 @@ unQM :: String -> String -> [StringPart]-unQM a [] = [Literal (reverse a)]+unQM a "" = [Literal (reverse a)] unQM a ('\\':x:xs) = unQM (x:a) xs-unQM a ("\\") = unQM ('\\':a) []-unQM a ('}':xs) = AntiQuote (reverse a) : parseQM [] xs+unQM a ("\\") = unQM ('\\':a) ""+unQM a ('}':xs) = AntiQuote (reverse a) : parseQM "" xs unQM a (x:xs) = unQM (x:a) xs + parseQM :: String -> String -> [StringPart]-parseQM a [] = [Literal (reverse a)]+parseQM a "" = [Literal (reverse a)] parseQM a ('\\':'\\':xs) = parseQM ('\\':a) xs parseQM a ('\\':'{':xs) = parseQM ('{':a) xs parseQM a ('\\':' ':xs) = parseQM (' ':a) xs parseQM a ('\\':'\n':xs) = parseQM a ('\n':xs) parseQM a ('\\':'n':xs) = parseQM ('\n':a) xs-parseQM a ("\\") = parseQM ('\\':a) []-parseQM a ('{':xs) = Literal (reverse a) : unQM [] xs+parseQM a ("\\") = parseQM ('\\':a) ""+parseQM a ('{':xs) = Literal (reverse a) : unQM "" xs parseQM a (clearIndentAtSOF -> Just clean) = parseQM a clean parseQM a (clearIndentTillEOF -> Just clean) = parseQM a clean parseQM a ('\n':xs) = parseQM a xs -- cut off line breaks parseQM a (x:xs) = parseQM (x:a) xs ++parseQN :: String -> String -> [StringPart]+parseQN a "" = [Literal (reverse a)]+parseQN a ('\\':'\\':xs) = parseQN ('\\':a) xs+parseQN a ('\\':' ':xs) = parseQN (' ':a) xs+parseQN a ('\\':'\n':xs) = parseQN a ('\n':xs)+parseQN a ('\\':'n':xs) = parseQN ('\n':a) xs+parseQN a ("\\") = parseQN ('\\':a) ""+parseQN a (clearIndentAtSOF -> Just clean) = parseQN a clean+parseQN a (clearIndentTillEOF -> Just clean) = parseQN a clean+parseQN a ('\n':xs) = parseQN a xs -- cut off line breaks+parseQN a (x:xs) = parseQN (x:a) xs++ clearIndentTillEOF :: String -> Maybe String-clearIndentTillEOF str@((ifMaybe (`elem` "\t ") -> Just _) : _) = cutOff str- where cutOff :: String -> Maybe String- cutOff "" = Just ""- cutOff eof@('\n':_) = Just eof- cutOff ((ifMaybe (`elem` "\t ") -> Just _) : xs) = cutOff xs- cutOff _ = Nothing-clearIndentTillEOF _ = Nothing+clearIndentTillEOF s | s == "" = Nothing+ | head s `elem` "\t " = cutOff s+ | otherwise = Nothing + where cutOff x | x == "" = Just ""+ | head x == '\n' = Just x+ | head x `elem` "\t " = cutOff $ tail x+ | otherwise = Nothing++ clearIndentAtSOF :: String -> Maybe String-clearIndentAtSOF ('\n' : xs) = if result /= xs- then Just $ '\n' : cutOff xs- else Nothing- where cutOff :: String -> String- cutOff ((ifMaybe (`elem` "\t ") -> Just _) : ys) = cutOff ys- cutOff s = s- result = cutOff xs-clearIndentAtSOF _ = Nothing+clearIndentAtSOF s | s == "" = Nothing+ | head s == '\n' && hasChanges = Just processed+ | otherwise = Nothing + where processed = '\n' : cutOff (tail s)+ hasChanges = processed /= s++ cutOff x | x == "" = ""+ | head x `elem` "\t " = cutOff $ tail x+ | otherwise = x++ clearIndentAtStart :: String -> String-clearIndentAtStart ((ifMaybe (`elem` "\t ") -> Just _) : xs) =- clearIndentAtStart xs-clearIndentAtStart s = s+clearIndentAtStart s | s == "" = ""+ | head s `elem` "\t " = clearIndentAtStart $ tail s+ | otherwise = s makeExpr :: [StringPart] -> TH.ExpQ@@ -119,20 +139,58 @@ makeExpr (AntiQuote a : xs) = TH.appE [| mappend (toQQ $(reify a)) |] $ makeExpr xs + reify :: String -> TH.Q TH.Exp-reify s =- case parseExp s of- Left e -> TH.reportError e >> [| mempty |]- Right e -> return e+reify s = case parseExp s of+ Left e -> TH.reportError e >> [| mempty |]+ Right e -> return e +-- | QuasiQuoter for multiline interpolated string.+--+-- @+-- [qm| foo {'b':'a':'r':""}+-- \\ baz |] -- "foo bar baz"+-- @+--+-- Symbols that could be escaped:+--+-- * @\\@ - backslash itself (two backslashes one by one: @\\\\@)+-- @[qm| foo\\\\bar |] -- "foo\\\\bar"@+--+-- * Space symbol at the edge+-- (to put it to the output instead of just ignoring it)+-- @[qm| foo\\ |] -- "foo "@ or @[qm|\\ foo |] -- " foo"@+--+-- * Line break @\\n@ (actual line breaks are ignored)+--+-- * Opening bracket of interpolation block @\\{@+-- to prevent interpolatin and put it as it is+-- @[qm| {1+2} \\{3+4} |] -- "3 {3+4}"@+-- qm :: QuasiQuoter qm = QuasiQuoter f (error "Cannot use qm as a pattern") (error "Cannot use qm as a type") (error "Cannot use qm as a dec")- where f = makeExpr . parseQM [] . clearIndentAtStart . filter (/= '\r')+ where f = makeExpr . parseQM "" . clearIndentAtStart . filter (/= '\r') -ifMaybe :: (a -> Bool) -> a -> Maybe a-ifMaybe f x = if f x then Just x else Nothing+-- | Works just like `qm` but without interpolation+-- (just multiline string with decorative indentation).+--+-- @+-- [qn| foo {'b':'a':'r':""}+-- \\ baz |] -- "foo {'b':'a':'r':\\"\\"} baz"+-- @+--+-- Interpolation blocks goes just as text:+--+-- @[qn| {1+2} \\{3+4} |] -- "{1+2} \\\\{3+4}"@+--+qn :: QuasiQuoter+qn = QuasiQuoter f+ (error "Cannot use qn as a pattern")+ (error "Cannot use qn as a type")+ (error "Cannot use qn as a dec")+ where f = makeExpr . parseQN "" . clearIndentAtStart . filter (/= '\r')
test/Spec.hs view
@@ -4,38 +4,110 @@ module Main (main) where import "hspec" Test.Hspec (hspec, describe, it, shouldBe)-import "qm-interpolated-string" Text.InterpolatedString.QM (qm)+import "qm-interpolated-string" Text.InterpolatedString.QM (qm, qn) main :: IO () main = hspec $ do - describe "Examples from README" $ do+ describe "QM" $ do - it "First (decorative spacing and escaping space symbol)" $- [qm| hello world,- \ what's going on here? |]- `shouldBe` "hello world, what's going on here?"+ describe "Examples from README" $ do - it "Second (merging lines without spaces)" $- [qm|- it's actual- ly ignored- |]- `shouldBe` "it's actually ignored"+ it "First (decorative spacing and escaping space symbol)" $+ [qm| hello world,+ \ what's going on here? |]+ `shouldBe` "hello world, what's going on here?" - it "Third (explicit line-breaks symbols)" $- [qm| \ You could explicitly escape indentation or\n- line-breaks when you really need it! \- |] `shouldBe` " You could explicitly escape indentation or\n\- \line-breaks when you really need it! "+ it "Second (merging lines without spaces)" $+ [qm|+ it's actual+ ly ignored+ |]+ `shouldBe` "it's actually ignored" - it "Fourth (escaping interpolation blocks to show them as text)" $- [qm| {1+2} \{3+4} |] `shouldBe` "3 {3+4}"+ it "Third (explicit line-breaks symbols)" $+ [qm| \ You could explicitly escape indentation or\n+ line-breaks when you really need it! \+ |] `shouldBe` " You could explicitly escape indentation or\n\+ \line-breaks when you really need it! " - it "Type annotation in interpolation block" $- [qm|{10 :: Float}|] `shouldBe` "10.0"+ it "Fourth (escaping interpolation blocks to show them as text)" $+ [qm| {1+2} \{3+4} |] `shouldBe` "3 {3+4}" - it "Escaping interpolation symbols inside interpolation block" $ do- [qm|foo {"b{a{r"} baz|] `shouldBe` "foo b{a{r baz"- [qm|foo {"b\}a\}r"} baz|] `shouldBe` "foo b}a}r baz"+ it "Type annotation in interpolation block" $+ [qm|{10 :: Float}|] `shouldBe` "10.0"++ it "Escaping interpolation symbols inside interpolation block" $ do+ [qm|foo {"b{a{r"} baz|] `shouldBe` "foo b{a{r baz"+ [qm|foo {"b\}a\}r"} baz|] `shouldBe` "foo b}a}r baz"++ it "Example from generated docs" $ [qm| foo {'b':'a':'r':""}+ \ baz |] `shouldBe` "foo bar baz"++ it "Escaping backslashes" $ do [qm| foo\bar |] `shouldBe` "foo\\bar"+ [qm| foo\\bar |] `shouldBe` "foo\\bar"+ [qm| foo\\\bar |] `shouldBe` "foo\\\\bar"+ [qm| foo\\\\bar |] `shouldBe` "foo\\\\bar"++ it "Empty string" $ [qm| |] `shouldBe` ""++ it "Escaping space by slash at EOL after space" $+ [qm| foo \+ bar |] `shouldBe` "foo bar"++ it "Escaped spaces at the edges" $ do [qm| foo\ |] `shouldBe` "foo "+ [qm|\ foo |] `shouldBe` " foo"++ describe "QN (QM but without interpolation)" $ do++ describe "Examples from README" $ do++ it "First (decorative spacing and escaping space symbol)" $+ [qn| hello world,+ \ what's going on here? |]+ `shouldBe` "hello world, what's going on here?"++ it "Second (merging lines without spaces)" $+ [qn|+ it's actual+ ly ignored+ |]+ `shouldBe` "it's actually ignored"++ it "Third (explicit line-breaks symbols)" $+ [qn| \ You could explicitly escape indentation or\n+ line-breaks when you really need it! \+ |] `shouldBe` " You could explicitly escape indentation or\n\+ \line-breaks when you really need it! "++ it "Fourth (escaping interpolation blocks to show them as text)" $+ [qn| {1+2} \{3+4} |] `shouldBe` "{1+2} \\{3+4}"++ it "Example of `qn` QuasiQuoter" $+ [qn| foo {1+2} |] `shouldBe` "foo {1+2}"++ it "Type annotation in interpolation block" $+ [qn|{10 :: Float}|] `shouldBe` "{10 :: Float}"++ it "Escaping interpolation symbols inside interpolation block" $ do+ [qn|foo {"b{a{r"} baz|] `shouldBe` "foo {\"b{a{r\"} baz"+ [qn|foo {"b\}a\}r"} baz|] `shouldBe` "foo {\"b\\}a\\}r\"} baz"++ it "Example from generated docs" $+ [qn| foo {'b':'a':'r':""}+ \ baz |] `shouldBe` "foo {'b':'a':'r':\"\"} baz"++ it "Escaping backslashes" $ do [qn| foo\bar |] `shouldBe` "foo\\bar"+ [qn| foo\\bar |] `shouldBe` "foo\\bar"+ [qn| foo\\\bar |] `shouldBe` "foo\\\\bar"+ [qn| foo\\\\bar |] `shouldBe` "foo\\\\bar"++ it "Empty string" $ [qn| |] `shouldBe` ""++ it "Escaping space by slash at EOL after space" $+ [qn| foo \+ bar |] `shouldBe` "foo bar"++ it "Escaped spaces at the edges" $ do [qn| foo\ |] `shouldBe` "foo "+ [qn|\ foo |] `shouldBe` " foo"