string-interpolate 0.2.0.0 → 0.2.0.1
raw patch · 5 files changed
+147/−127 lines, 5 files
Files
- CHANGELOG.md +8/−0
- src/lib/Data/String/Interpolate.hs +1/−1
- src/lib/Data/String/Interpolate/Parse.hs +104/−102
- string-interpolate.cabal +3/−3
- test/Spec.hs +31/−21
CHANGELOG.md view
@@ -1,5 +1,13 @@ # CHANGELOG +## Unreleased++## v0.2.0.1 (2020-04-09)+++ Fixed bug caused when escaping a backslash right before an interpolation+ (Thanks Vladimir Stepchenko!)++ Fixed behavior of `iii` (Thanks Vladimir Stepchenko!)+ ## v0.2.0.0 (2019-12-16) + Added `iii` interpolator for collapsing whitespace/newlines into
src/lib/Data/String/Interpolate.hs view
@@ -54,7 +54,7 @@ import Language.Haskell.TH.Quote ( QuasiQuoter(..) ) import Data.String.Interpolate.Conversion- ( build, finalize, interpolate, ofString )+ ( build, finalize, interpolate, ofString, chompSpaces ) import Data.String.Interpolate.Parse ( InterpSegment(..), dosToUnix, parseInterpSegments ) i :: QuasiQuoter
src/lib/Data/String/Interpolate/Parse.hs view
@@ -19,17 +19,17 @@ parseInterpSegments = go "" where go :: String -> String -> Either String [InterpSegment] go acc parsee = case parsee of- "" -> Right [verbatim $ reverse acc]+ "" -> Right [Verbatim $ reverse acc] '\\':'#':rest -> go ('#':acc) rest+ '\\':_rest -> case unescapeChar parsee of+ (Nothing, rest) -> go acc rest+ (Just c, rest) -> go (c:acc) rest '#':'{':rest -> case span (/= '}') rest of (expr, _:rest') ->- ((verbatim . reverse) acc :) . (Expression expr :) <$> go "" rest'+ ((Verbatim . reverse) acc :) . (Expression expr :) <$> go "" rest' (_, "") -> Left "unterminated #{...} interpolation" c:cs -> go (c:acc) cs - verbatim :: String -> InterpSegment- verbatim = Verbatim . unescape- dosToUnix :: String -> String dosToUnix = go where go xs = case xs of@@ -40,102 +40,104 @@ -- | -- Haskell 2010 character unescaping, see: -- <http://www.haskell.org/onlinereport/haskell2010/haskellch2.html#x7-200002.6>-unescape :: String -> String-unescape = go- where- go input = case input of- "" -> ""- '\\' : 'x' : x : xs | isHexDigit x -> case span isHexDigit xs of- (ys, zs) -> (chr . readHex $ x:ys) : go zs- '\\' : 'o' : x : xs | isOctDigit x -> case span isOctDigit xs of- (ys, zs) -> (chr . readOct $ x:ys) : go zs- '\\' : x : xs | isDigit x -> case span isDigit xs of- (ys, zs) -> (chr . read $ x:ys) : go zs- '\\' : input_ -> case input_ of- '\\' : xs -> '\\' : go xs- 'a' : xs -> '\a' : go xs- 'b' : xs -> '\b' : go xs- 'f' : xs -> '\f' : go xs- 'n' : xs -> '\n' : go xs- 'r' : xs -> '\r' : go xs- 't' : xs -> '\t' : go xs- 'v' : xs -> '\v' : go xs- '&' : xs -> go xs- 'N':'U':'L' : xs -> '\NUL' : go xs- 'S':'O':'H' : xs -> '\SOH' : go xs- 'S':'T':'X' : xs -> '\STX' : go xs- 'E':'T':'X' : xs -> '\ETX' : go xs- 'E':'O':'T' : xs -> '\EOT' : go xs- 'E':'N':'Q' : xs -> '\ENQ' : go xs- 'A':'C':'K' : xs -> '\ACK' : go xs- 'B':'E':'L' : xs -> '\BEL' : go xs- 'B':'S' : xs -> '\BS' : go xs- 'H':'T' : xs -> '\HT' : go xs- 'L':'F' : xs -> '\LF' : go xs- 'V':'T' : xs -> '\VT' : go xs- 'F':'F' : xs -> '\FF' : go xs- 'C':'R' : xs -> '\CR' : go xs- 'S':'O' : xs -> '\SO' : go xs- 'S':'I' : xs -> '\SI' : go xs- 'D':'L':'E' : xs -> '\DLE' : go xs- 'D':'C':'1' : xs -> '\DC1' : go xs- 'D':'C':'2' : xs -> '\DC2' : go xs- 'D':'C':'3' : xs -> '\DC3' : go xs- 'D':'C':'4' : xs -> '\DC4' : go xs- 'N':'A':'K' : xs -> '\NAK' : go xs- 'S':'Y':'N' : xs -> '\SYN' : go xs- 'E':'T':'B' : xs -> '\ETB' : go xs- 'C':'A':'N' : xs -> '\CAN' : go xs- 'E':'M' : xs -> '\EM' : go xs- 'S':'U':'B' : xs -> '\SUB' : go xs- 'E':'S':'C' : xs -> '\ESC' : go xs- 'F':'S' : xs -> '\FS' : go xs- 'G':'S' : xs -> '\GS' : go xs- 'R':'S' : xs -> '\RS' : go xs- 'U':'S' : xs -> '\US' : go xs- 'S':'P' : xs -> '\SP' : go xs- 'D':'E':'L' : xs -> '\DEL' : go xs- '^':'@' : xs -> '\^@' : go xs- '^':'A' : xs -> '\^A' : go xs- '^':'B' : xs -> '\^B' : go xs- '^':'C' : xs -> '\^C' : go xs- '^':'D' : xs -> '\^D' : go xs- '^':'E' : xs -> '\^E' : go xs- '^':'F' : xs -> '\^F' : go xs- '^':'G' : xs -> '\^G' : go xs- '^':'H' : xs -> '\^H' : go xs- '^':'I' : xs -> '\^I' : go xs- '^':'J' : xs -> '\^J' : go xs- '^':'K' : xs -> '\^K' : go xs- '^':'L' : xs -> '\^L' : go xs- '^':'M' : xs -> '\^M' : go xs- '^':'N' : xs -> '\^N' : go xs- '^':'O' : xs -> '\^O' : go xs- '^':'P' : xs -> '\^P' : go xs- '^':'Q' : xs -> '\^Q' : go xs- '^':'R' : xs -> '\^R' : go xs- '^':'S' : xs -> '\^S' : go xs- '^':'T' : xs -> '\^T' : go xs- '^':'U' : xs -> '\^U' : go xs- '^':'V' : xs -> '\^V' : go xs- '^':'W' : xs -> '\^W' : go xs- '^':'X' : xs -> '\^X' : go xs- '^':'Y' : xs -> '\^Y' : go xs- '^':'Z' : xs -> '\^Z' : go xs- '^':'[' : xs -> '\^[' : go xs- '^':'\\' : xs -> '\^\' : go xs- '^':']' : xs -> '\^]' : go xs- '^':'^' : xs -> '\^^' : go xs- '^':'_' : xs -> '\^_' : go xs- xs -> go xs- x:xs -> x : go xs+--+-- Unescape the very first backslashed character of the string, if it results in+-- a character. Note that there is an escape sequence that doesn't result in+-- a character (\&).+unescapeChar :: String -> (Maybe Char, String)+unescapeChar input = case input of+ "" -> (Nothing, input)+ '\\' : 'x' : x : xs | isHexDigit x -> case span isHexDigit xs of+ (ys, zs) -> ((Just . chr . readHex $ x:ys), zs)+ '\\' : 'o' : x : xs | isOctDigit x -> case span isOctDigit xs of+ (ys, zs) -> ((Just . chr . readOct $ x:ys), zs)+ '\\' : x : xs | isDigit x -> case span isDigit xs of+ (ys, zs) -> ((Just . chr . read $ x:ys), zs)+ '\\' : input_ -> case input_ of+ '\\' : xs -> (Just ('\\'), xs)+ 'a' : xs -> (Just ('\a'), xs)+ 'b' : xs -> (Just ('\b'), xs)+ 'f' : xs -> (Just ('\f'), xs)+ 'n' : xs -> (Just ('\n'), xs)+ 'r' : xs -> (Just ('\r'), xs)+ 't' : xs -> (Just ('\t'), xs)+ 'v' : xs -> (Just ('\v'), xs)+ '&' : xs -> (Nothing, xs)+ 'N':'U':'L' : xs -> (Just ('\NUL'), xs)+ 'S':'O':'H' : xs -> (Just ('\SOH'), xs)+ 'S':'T':'X' : xs -> (Just ('\STX'), xs)+ 'E':'T':'X' : xs -> (Just ('\ETX'), xs)+ 'E':'O':'T' : xs -> (Just ('\EOT'), xs)+ 'E':'N':'Q' : xs -> (Just ('\ENQ'), xs)+ 'A':'C':'K' : xs -> (Just ('\ACK'), xs)+ 'B':'E':'L' : xs -> (Just ('\BEL'), xs)+ 'B':'S' : xs -> (Just ('\BS'), xs)+ 'H':'T' : xs -> (Just ('\HT'), xs)+ 'L':'F' : xs -> (Just ('\LF'), xs)+ 'V':'T' : xs -> (Just ('\VT'), xs)+ 'F':'F' : xs -> (Just ('\FF'), xs)+ 'C':'R' : xs -> (Just ('\CR'), xs)+ 'S':'O' : xs -> (Just ('\SO'), xs)+ 'S':'I' : xs -> (Just ('\SI'), xs)+ 'D':'L':'E' : xs -> (Just ('\DLE'), xs)+ 'D':'C':'1' : xs -> (Just ('\DC1'), xs)+ 'D':'C':'2' : xs -> (Just ('\DC2'), xs)+ 'D':'C':'3' : xs -> (Just ('\DC3'), xs)+ 'D':'C':'4' : xs -> (Just ('\DC4'), xs)+ 'N':'A':'K' : xs -> (Just ('\NAK'), xs)+ 'S':'Y':'N' : xs -> (Just ('\SYN'), xs)+ 'E':'T':'B' : xs -> (Just ('\ETB'), xs)+ 'C':'A':'N' : xs -> (Just ('\CAN'), xs)+ 'E':'M' : xs -> (Just ('\EM'), xs)+ 'S':'U':'B' : xs -> (Just ('\SUB'), xs)+ 'E':'S':'C' : xs -> (Just ('\ESC'), xs)+ 'F':'S' : xs -> (Just ('\FS'), xs)+ 'G':'S' : xs -> (Just ('\GS'), xs)+ 'R':'S' : xs -> (Just ('\RS'), xs)+ 'U':'S' : xs -> (Just ('\US'), xs)+ 'S':'P' : xs -> (Just ('\SP'), xs)+ 'D':'E':'L' : xs -> (Just ('\DEL'), xs)+ '^':'@' : xs -> (Just ('\^@'), xs)+ '^':'A' : xs -> (Just ('\^A'), xs)+ '^':'B' : xs -> (Just ('\^B'), xs)+ '^':'C' : xs -> (Just ('\^C'), xs)+ '^':'D' : xs -> (Just ('\^D'), xs)+ '^':'E' : xs -> (Just ('\^E'), xs)+ '^':'F' : xs -> (Just ('\^F'), xs)+ '^':'G' : xs -> (Just ('\^G'), xs)+ '^':'H' : xs -> (Just ('\^H'), xs)+ '^':'I' : xs -> (Just ('\^I'), xs)+ '^':'J' : xs -> (Just ('\^J'), xs)+ '^':'K' : xs -> (Just ('\^K'), xs)+ '^':'L' : xs -> (Just ('\^L'), xs)+ '^':'M' : xs -> (Just ('\^M'), xs)+ '^':'N' : xs -> (Just ('\^N'), xs)+ '^':'O' : xs -> (Just ('\^O'), xs)+ '^':'P' : xs -> (Just ('\^P'), xs)+ '^':'Q' : xs -> (Just ('\^Q'), xs)+ '^':'R' : xs -> (Just ('\^R'), xs)+ '^':'S' : xs -> (Just ('\^S'), xs)+ '^':'T' : xs -> (Just ('\^T'), xs)+ '^':'U' : xs -> (Just ('\^U'), xs)+ '^':'V' : xs -> (Just ('\^V'), xs)+ '^':'W' : xs -> (Just ('\^W'), xs)+ '^':'X' : xs -> (Just ('\^X'), xs)+ '^':'Y' : xs -> (Just ('\^Y'), xs)+ '^':'Z' : xs -> (Just ('\^Z'), xs)+ '^':'[' : xs -> (Just ('\^['), xs)+ '^':'\\' : xs -> (Just ('\^\'), xs)+ '^':']' : xs -> (Just ('\^]'), xs)+ '^':'^' : xs -> (Just ('\^^'), xs)+ '^':'_' : xs -> (Just ('\^_'), xs)+ xs -> (Nothing, xs)+ x:xs -> (Just x, xs) - readHex :: String -> Int- readHex xs = case N.readHex xs of- [(n, "")] -> n- _ -> error "Data.String.Interpolate.Util.readHex: no parse"+ where readHex :: String -> Int+ readHex xs = case N.readHex xs of+ [(n, "")] -> n+ _ -> error "Data.String.Interpolate.Util.readHex: no parse" - readOct :: String -> Int- readOct xs = case N.readOct xs of- [(n, "")] -> n- _ -> error "Data.String.Interpolate.Util.readHex: no parse"+ readOct :: String -> Int+ readOct xs = case N.readOct xs of+ [(n, "")] -> n+ _ -> error "Data.String.Interpolate.Util.readHex: no parse"
string-interpolate.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 788b9642a9dce838cacd376737041425b944b38d07b7fdcf802e41da0861e29f+-- hash: b3c4b493a41aadee8fb1cd235201f3fc305dfecc895817cd699b8785b1b1bed9 name: string-interpolate-version: 0.2.0.0+version: 0.2.0.1 synopsis: Haskell string/text/bytestring interpolation that just works description: Unicode-aware string interpolation that handles all textual types. .@@ -17,7 +17,7 @@ bug-reports: https://gitlab.com/williamyaoh/string-interpolate/issues author: William Yao maintainer: williamyaoh@gmail.com-copyright: 2019 William Yao+copyright: 2019-2020 William Yao license: BSD3 license-file: LICENSE build-type: Simple
test/Spec.hs view
@@ -1,26 +1,26 @@ {-# OPTIONS -Wno-orphans #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PackageImports #-}-{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-} -import Data.Word-import Data.Char ( chr, isSpace )-import qualified Data.Text as T-import qualified Data.Text.Lazy as LT-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as LB+import qualified Data.ByteString as B import qualified Data.ByteString.Builder as LB-import Data.List ( sort )+import qualified Data.ByteString.Lazy as LB+import Data.Char ( chr, isSpace )+import Data.Foldable ( foldMap )+import qualified Data.HashMap.Strict as HM+import Data.List ( sort ) import Data.Semigroup-import Data.Foldable ( foldMap )-import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Text.Lazy as LT+import Data.Word import Control.Monad.IO.Class ( liftIO ) @@ -29,16 +29,26 @@ import "hspec" Test.Hspec import "hspec" Test.Hspec.QuickCheck import "QuickCheck" Test.QuickCheck-import "QuickCheck" Test.QuickCheck.Monadic import "quickcheck-instances" Test.QuickCheck.Instances.ByteString ()+import "QuickCheck" Test.QuickCheck.Monadic import "quickcheck-unicode" Test.QuickCheck.Unicode import Data.String.Interpolate ( i, iii )-import Data.String.Interpolate.Conversion+import Data.String.Interpolate.Conversion hiding+ ( build, finalize, interpolate, ofString, chompSpaces ) main :: IO () main = hspec $ parallel $ do describe "i" $ modifyMaxSuccess (const 10000) $ modifyMaxSize (const 500) $ do+ it "should allow an escaped backslash right before an interp" $ do+ let var :: String = "bar"+ expected :: String = "foo\\bar"+ [i|foo\\#{var}|] `shouldBe` expected++ it "should only esacpe verbatim segments a single time" $ do+ let expected :: String = "\\\\\\\\"+ [i|\\\\\\\\|] `shouldBe` expected+ context "when using String as a parameter" $ do prop "just interpolating should be id" $ \(UTF8S str) -> [i|#{str}|] == str@@ -451,7 +461,7 @@ charFrequencies :: T.Text -> HM.HashMap Char Int charFrequencies = T.foldl' (flip $ HM.alter increment) HM.empty . T.filter (not . isSpace) where increment :: Maybe Int -> Maybe Int- increment Nothing = Just 1+ increment Nothing = Just 1 increment (Just x) = Just (x + 1) whitespace :: Gen T.Text