packages feed

PyF 0.6.0.2 → 0.6.1.0

raw patch · 9 files changed

+112/−28 lines, 9 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ PyF: f'WithDelimiters :: (Char, Char) -> QuasiQuoter
+ PyF: fBuilderWithDelimiters :: (Char, Char) -> QuasiQuoter
+ PyF: fIOWithDelimiters :: (Char, Char) -> QuasiQuoter
+ PyF: fLazyTextWithDelimiters :: (Char, Char) -> QuasiQuoter
+ PyF: fStrictTextWithDelimiters :: (Char, Char) -> QuasiQuoter
+ PyF: fStringWithDelimiters :: (Char, Char) -> QuasiQuoter
+ PyF: fWithDelimiters :: (Char, Char) -> QuasiQuoter
+ PyF.Internal.PythonSyntax: parseGenericFormatString :: (Char, Char) -> Parser [Item]
+ PyF.Internal.QQ: toExpPython :: String -> Q Exp
- PyF.Internal.QQ: toExp :: String -> Q Exp
+ PyF.Internal.QQ: toExp :: (Char, Char) -> String -> Q Exp

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Revision history for FormatStringLiteral +## 0.6.1.0 -- 2018-08-03++- Custom delimiters, you can use whatever delimiters you want in place of `{` and `}`.+ ## 0.6.0.0 -- 2018-08-02  - Fix the espace parsing of `{{` and `}}` as `{` and `}`
PyF.cabal view
@@ -1,5 +1,5 @@ name:                PyF-version:             0.6.0.2+version:             0.6.1.0 synopsis: Quasiquotations for a python like interpolated string formater description: Quasiquotations for a python like interpolated string formater. license:             BSD3@@ -39,7 +39,7 @@   type:                exitcode-stdio-1.0   hs-source-dirs:      test   main-is:             Spec.hs-  other-modules: SpecUtils+  other-modules: SpecUtils SpecCustomDelimiters   build-depends:       base, PyF, hspec, text, template-haskell, formatting, process   ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010
Readme.md view
@@ -241,6 +241,35 @@     ... ``` +## Custom Delimiters++If `{` and `}` does not fit your needs, for example if you are formatting a lot of json, you can use custom delimiters. All quasi quoters have a parametric form which accepts custom delimiters. Due to template haskell stage restriction, you must define your custom quasi quoter in an other module.++For example, in `MyCustomDelimiter.hs`:++```haskell+module MyCustomQQ where++import Language.Haskell.TH.Quote++import PyF++myCustomFormatter :: QuasiQuoter+myCustomFormatter = fStringWithDelimiters ('@','!')+```++Later, in another module:++```haskell+import MyCustomQQ++-- ...++[myCustomFormatter|pi = @pi:2.f!|]+```++Escaping still works by doubling the delimiters, `@@!!@@!!` will be formatted as `@!@!`.+ ## Difference with the Python Syntax  The implementation is unit-tested against the reference python implementation (python 3.6.4) and should match its result. However some formatters are not supported or some (minor) differences can be observed.@@ -256,6 +285,7 @@  - General formatters *g* and *G* behaves a bit differently. Precision influence the number of significant digits instead of the number of the magnitude at which the representation changes between fixed and exponential. - Grouping options allows grouping with an `_` for floating point, python only allows `,`.+- Custom delimiters  # Build / test @@ -274,6 +304,7 @@ - Allow extension to others type / custom formatters (for date for example) - Improve code quality. This code is really ugly, but there is a really strong test suite so, well. - Work on performance, do we really care? For now, everything is internally done with `String`.+- Directly expose the formatter to be used as a template haskell splice  # Library note 
src/PyF.hs view
@@ -12,6 +12,15 @@    fLazyText,    fStrictText, +   -- * With custom delimiters+   fWithDelimiters,+   f'WithDelimiters,+   fIOWithDelimiters,+   fStringWithDelimiters,+   fBuilderWithDelimiters,+   fLazyTextWithDelimiters,+   fStrictTextWithDelimiters,+    -- * Formatting re-export    runFormat,    format,@@ -32,9 +41,9 @@ import qualified Data.Text as SText import qualified Data.Text.Lazy.Builder as Builder -templateF :: String -> QuasiQuoter-templateF fName = QuasiQuoter {-    quoteExp = QQ.toExp+templateF :: (Char, Char) -> String -> QuasiQuoter+templateF delimiters fName = QuasiQuoter {+    quoteExp = QQ.toExp delimiters   , quotePat = err "pattern"   , quoteType = err "type"   , quoteDec = err "declaration"@@ -44,12 +53,18 @@  -- | Returns an expression usable with Formatting.format (and similar functions) f :: QuasiQuoter-f = templateF "f"+f = templateF pythonDelimiters "f" +fWithDelimiters :: (Char, Char) -> QuasiQuoter+fWithDelimiters delimiters = templateF delimiters "fWithDelimiters"+ -- | Generic formatter, can format an expression to (lazy) Text, String, Builder and IO () depending on type inference f' :: QuasiQuoter-f' = wrapQQ (templateF "f'") (VarE 'magicFormat)+f' = wrapQQ (templateF pythonDelimiters "f'") (VarE 'magicFormat) +f'WithDelimiters :: (Char, Char) -> QuasiQuoter+f'WithDelimiters delimiters = templateF delimiters "f'WithDelimiters"+ wrapQQ :: QuasiQuoter -> Exp -> QuasiQuoter wrapQQ qq wrap = qq {   quoteExp = \s -> do@@ -77,19 +92,28 @@  -- Monomorphic formatters fIO, fString, fStrictText, fLazyText, fBuilder :: QuasiQuoter+fIOWithDelimiters, fStringWithDelimiters, fStrictTextWithDelimiters, fLazyTextWithDelimiters, fBuilderWithDelimiters :: (Char, Char) -> QuasiQuoter +fIO = fIOWithDelimiters pythonDelimiters+fString = fStringWithDelimiters pythonDelimiters+fStrictText = fStrictTextWithDelimiters pythonDelimiters+fLazyText = fLazyTextWithDelimiters pythonDelimiters+fBuilder = fBuilderWithDelimiters pythonDelimiters +pythonDelimiters :: (Char, Char)+pythonDelimiters = ('{', '}')+ -- | Format the format string and directly print it to stdout-fIO = wrapQQ (templateF "fIO") (VarE 'F.fprint)+fIOWithDelimiters delimiters = wrapQQ (templateF delimiters "fIO") (VarE 'F.fprint)  -- | Format the format string as a 'String'-fString = wrapQQ (templateF "fString") (VarE 'F.formatToString)+fStringWithDelimiters delimiters = wrapQQ (templateF delimiters "fString") (VarE 'F.formatToString)  -- | Format the format string as a strict 'SText.Text'-fStrictText = wrapQQ (templateF "fStrictTeext") (VarE 'F.sformat)+fStrictTextWithDelimiters delimiters = wrapQQ (templateF delimiters "fStrictTeext") (VarE 'F.sformat)  -- | Format the format string as a Lazy 'LText.Text'-fLazyText = wrapQQ (templateF "fLazy") (VarE 'F.sformat)+fLazyTextWithDelimiters delimiters = wrapQQ (templateF delimiters "fLazy") (VarE 'F.sformat)  -- | Format the format string as a 'Builder.Builder'-fBuilder = wrapQQ (templateF "fBuilder") (VarE 'F.bprint)+fBuilderWithDelimiters delimiters = wrapQQ (templateF delimiters "fBuilder") (VarE 'F.bprint)
src/PyF/Internal/PythonSyntax.hs view
@@ -10,6 +10,7 @@ -} module PyF.Internal.PythonSyntax   ( parsePythonFormatString+  , parseGenericFormatString   , Item(..)   , FormatMode(..)   , Padding(..)@@ -77,14 +78,18 @@                        Nothing))] -} parsePythonFormatString :: Parser [Item]-parsePythonFormatString = many (rawString <|> escapedParenthesis <|> replacementField)+parsePythonFormatString = parseGenericFormatString ('{', '}') -rawString :: Parser Item-rawString = Raw . escapeChars <$> some (noneOf ("{}" :: [Char]))+parseGenericFormatString :: (Char, Char) -> Parser [Item]+parseGenericFormatString delimiters = many (rawString delimiters <|> escapedParenthesis delimiters <|> replacementField delimiters) -escapedParenthesis :: Parser Item-escapedParenthesis = Raw <$> (("{" <$ string "{{") <|> ("}" <$ string "}}"))+rawString :: (Char, Char) -> Parser Item+rawString (openingChar,closingChar) = Raw . escapeChars <$> some (noneOf ([openingChar, closingChar])) +escapedParenthesis :: (Char, Char) -> Parser Item+escapedParenthesis (openingChar, closingChar) = Raw <$> (parseRaw openingChar <|> parseRaw closingChar)+  where parseRaw c = c:[] <$ string (replicate 2 c)+ {- | Replace escape chars with their value >>> escapeChars "hello \\n" "hello \n"@@ -95,14 +100,14 @@                   [] -> ""                   ((c, xs):_) -> c : escapeChars xs -replacementField :: Parser Item-replacementField = do-  _ <- char '{'-  expr <- many (noneOf ("}:" :: [Char]))+replacementField :: (Char, Char) -> Parser Item+replacementField (charOpening, charClosing) = do+  _ <- char charOpening+  expr <- many (noneOf (charClosing:":"))   fmt <- optional $ do     _ <- char ':'     format_spec-  _ <- char '}'+  _ <- char charClosing    pure (Replacement expr fmt) 
src/PyF/Internal/QQ.hs view
@@ -14,7 +14,8 @@  -} module PyF.Internal.QQ (-  toExp)+  toExp,+  toExpPython) where  import Text.Megaparsec@@ -45,8 +46,8 @@  -- Be Careful: empty format string -- | Parse a string and return a formatter for it-toExp:: String -> Q Exp-toExp s = do+toExp:: (Char, Char) -> String -> Q Exp+toExp delimiters s = do   filename <- loc_filename <$> location   (line, col) <- loc_start <$> location @@ -55,7 +56,7 @@         (SourcePos sName _ _) NonEmpty.:| xs = statePos currentState         in currentState {statePos = (SourcePos sName (mkPos line) (mkPos col)) NonEmpty.:| xs} -  case parse (updateParserState (change_log filename) >> parsePythonFormatString) filename s of+  case parse (updateParserState (change_log filename) >> parseGenericFormatString delimiters) filename s of     Left err -> do        if filename == "<interactive>"@@ -65,6 +66,9 @@           fileContent <- runIO (readFile filename)           fail (parseErrorPretty' fileContent err)     Right items -> goFormat items++toExpPython :: String -> Q Exp+toExpPython = toExp ('{', '}')  goFormat :: [Item] -> Q Exp goFormat items = foldl1 fofo <$> (mapM toFormat items)
test/Spec.hs view
@@ -8,6 +8,7 @@  import PyF import SpecUtils+import SpecCustomDelimiters  {-    - Normal tests are done using the recommanded API: [fString|.....|]@@ -187,3 +188,9 @@    it "escape chars" $ do      [fString|}}{{}}{{|] `shouldBe` "}{}{"++  describe "custom delimiters" $ do+    it "works" $ do+      [myCustomFormatter|2 * pi = @2*pi:.2f!|] `shouldBe` "2 * pi = 6.28"+    it "escape chars" $ do+       [myCustomFormatter|@@!!@@!!|] `shouldBe` "@!@!"
+ test/SpecCustomDelimiters.hs view
@@ -0,0 +1,8 @@+module SpecCustomDelimiters where++import Language.Haskell.TH.Quote++import PyF++myCustomFormatter :: QuasiQuoter+myCustomFormatter = fStringWithDelimiters ('@','!')
test/SpecUtils.hs view
@@ -43,6 +43,7 @@ This expression is a failure if python cannot format this formatString or if the python result does not match the (provided) reference. -}+ pyCheck :: String -> Maybe String -> Q Exp pyCheck s exampleStr = do   pythonRes <- Language.Haskell.TH.Syntax.runIO (runPythonExample s)@@ -50,7 +51,7 @@   case pythonRes of     Nothing -> [| expectationFailure $ "Expression: `" ++ s ++ "` fails in python" |]     Just res -> do-      let qexp = [| formatToString $(toExp s)  `shouldBe` res |]+      let qexp = [| formatToString $(toExpPython s)  `shouldBe` res |]       case exampleStr of         Nothing -> qexp         Just e -> if res == e@@ -71,7 +72,7 @@      against the python implementation -} checkExampleDiff :: String -> String -> Q Exp-checkExampleDiff s res = [| formatToString $(toExp s) `shouldBe` res |]+checkExampleDiff s res = [| formatToString $(toExpPython s) `shouldBe` res |]  {- | `check formatString` checks only with the python implementation -}