packages feed

PyF 0.6.1.1 → 0.7.0.0

raw patch · 9 files changed

+166/−181 lines, 9 filesdep +haskell-src-extsdep −formattingdep ~basedep ~containersdep ~megaparsecPVP ok

version bump matches the API change (PVP)

Dependencies added: haskell-src-exts

Dependencies removed: formatting

Dependency ranges changed: base, containers, megaparsec, template-haskell

API changes (from Hackage documentation)

- PyF: bprint :: () => Format Builder a -> a
- PyF: f' :: QuasiQuoter
- PyF: f'WithDelimiters :: (Char, Char) -> QuasiQuoter
- PyF: fBuilder :: QuasiQuoter
- PyF: fBuilderWithDelimiters :: (Char, Char) -> QuasiQuoter
- PyF: fIO :: QuasiQuoter
- PyF: fIOWithDelimiters :: (Char, Char) -> QuasiQuoter
- PyF: fLazyText :: QuasiQuoter
- PyF: fLazyTextWithDelimiters :: (Char, Char) -> QuasiQuoter
- PyF: fStrictText :: QuasiQuoter
- PyF: fStrictTextWithDelimiters :: (Char, Char) -> QuasiQuoter
- PyF: fString :: QuasiQuoter
- PyF: fStringWithDelimiters :: (Char, Char) -> QuasiQuoter
- PyF: format :: () => Format Text a -> a
- PyF: fprint :: () => Format IO () a -> a
- PyF: hprint :: () => Handle -> Format IO () a -> a
- PyF: runFormat :: Format r a -> Builder -> r -> a
- PyF: sformat :: () => Format Text a -> a
+ PyF.Internal.PythonSyntax: pattern DefaultFormatMode :: FormatMode
- PyF.Formatters: formatFractional :: (RealFloat f) => Format t t' 'Fractional -> SignMode -> Maybe (Int, AlignMode k, Char) -> Maybe (Int, Char) -> Maybe Int -> f -> String
+ PyF.Formatters: formatFractional :: RealFloat f => Format t t' 'Fractional -> SignMode -> Maybe (Int, AlignMode k, Char) -> Maybe (Int, Char) -> Maybe Int -> f -> String
- PyF.Internal.PythonSyntax: FormatMode :: Padding -> TypeFormat -> (Maybe Char) -> FormatMode
+ PyF.Internal.PythonSyntax: FormatMode :: Padding -> TypeFormat -> Maybe Char -> FormatMode
- PyF.Internal.PythonSyntax: Padding :: Integer -> (Maybe (Maybe Char, AnyAlign)) -> Padding
+ PyF.Internal.PythonSyntax: Padding :: Integer -> Maybe (Maybe Char, AnyAlign) -> Padding
- PyF.Internal.PythonSyntax: Replacement :: String -> (Maybe FormatMode) -> Item
+ PyF.Internal.PythonSyntax: Replacement :: Exp -> Maybe FormatMode -> Item

Files

ChangeLog.md view
@@ -1,5 +1,17 @@-# Revision history for FormatStringLiteral+# Revision history for PyF +- Bump dependencies to megaparsec 7+- Error message are now tested+- Name in template haskell splices are stable. This increases readability of error messages+- Better error message for badly formated expression++# Formatting removal++- All monomorphic quasiquoters (`f`, `fString`, `fText`, `fIO`, `fLazyText`) are removed+- Polymophic quasiquoter `f'` is renamed `f` and is the only entry point. Monomorphic users are encouraged to use the polymorphic quasiquoter with type annotation.+- `Formatting` dependency is removed.+- Previously named `f` quasiquoters which was exporting to `Formatting.Format` is removed. User of this behavior should use `Formatting.now` instead.+ ## 0.6.1.0 -- 2018-08-03  - Custom delimiters, you can use whatever delimiters you want in place of `{` and `}`.@@ -7,7 +19,6 @@ ## 0.6.0.0 -- 2018-08-02  - Fix the espace parsing of `{{` and `}}` as `{` and `}`-  ## 0.5.0.0 -- 2018-04-16 
PyF.cabal view
@@ -1,5 +1,5 @@ name:                PyF-version:             0.6.1.1+version:             0.7.0.0 synopsis: Quasiquotations for a python like interpolated string formater description: Quasiquotations for a python like interpolated string formater. license:             BSD3@@ -19,18 +19,16 @@                   PyF.Formatters    build-depends:       base >= 4.9 && < 5.0-                     , template-haskell >= 2.11 && < 2.14+                     , template-haskell >= 2.11 && < 2.15                       -- Parsec and some transitive deps-                     , megaparsec >= 6.0 && < 6.6+                     , megaparsec >= 7.0 && < 8.0                      , text >= 0.11 && < 1.3-                     , containers >= 0.5 && < 0.6--                     -- Formatting and some transitive deps-                     , formatting >= 6.2 && < 6.4+                     , containers >= 0.5 && < 0.7                       --                      , haskell-src-meta+                     , haskell-src-exts   hs-source-dirs: src   ghc-options: -Wall   default-language:    Haskell2010@@ -41,9 +39,18 @@   main-is:             Spec.hs   other-modules: SpecUtils SpecCustomDelimiters   build-tools:  python3-  build-depends:       base, PyF, hspec, text, template-haskell, formatting, process+  build-depends:       base, PyF, hspec, text, template-haskell, process   ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010++-- test-suite pyf-failure+--   type:                exitcode-stdio-1.0+--   hs-source-dirs:      test+--   main-is:             SpecFail.hs+--   build-tools:  python3+--   build-depends:       base, hspec, text, template-haskell, process, hspec-golden, hspec, temporary, hashable+--   ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+--   default-language:    Haskell2010  source-repository head   type:     git
Readme.md view
@@ -1,31 +1,20 @@-*PyF* is a Haskell library for string interpolation and formatting.--*PyF* exposes a quasiquoter `f` for the [Formatting](https://hackage.haskell.org/package/formatting) library. The quasiquotation introduces string interpolation and formatting with a mini language inspired from printf and Python.--# Quick Start--The following *Formatting* example:+# PyF -```haskell->>> import Formatting+[![CircleCI](https://circleci.com/gh/guibou/PyF.svg?style=svg)](https://circleci.com/gh/guibou/PyF) ->>> name = "Dave"->>> age = 54+*PyF* is a Haskell library for string interpolation and formatting. ->>> format ("Person's name is " % text % ", age is " % hex) name age-"Person's name is Dave, age is 36"-```+*PyF* exposes a quasiquoter `f` which introduces string interpolation and formatting with a mini language inspired from printf and Python. -can be written as:+# Quick Start  ```haskell->>> import Formatting >>> import PyF  >>> name = "Dave" >>> age = 54 ->>> format [f|Person's name is {name}, age is {age:x}|]+>>> [f|Person's name is {name}, age is {age:x}|] "Person's name is Dave, age is 36" ``` @@ -38,7 +27,7 @@ - Floating point representation - The interpolated value can be any Haskell expression -You will need the extension `QuasiQuotes`, enable it with `{-# LANGUAGE QuasiQuotes #-}` in top of your source file or with `:set -XQuasiQuotes` in your `ghci` session.+You will need the extension `QuasiQuotes`, enable it with `{-# LANGUAGE QuasiQuotes #-}` in top of your source file or with `:set -XQuasiQuotes` in your `ghci` session. `ExtendedDefaultRules` and `OverloadedStrings` may be more convenient.  Expression to be formatted are referenced by `{expression:formatingOptions}` where `formatingOptions` follows the [Python format mini-language](https://docs.python.org/3/library/string.html#formatspec). It is recommended to read the python documentation, but the [Test file](https://github.com/guibou/PyF/blob/master/test/Spec.hs) as well as this readme contain many examples. @@ -50,25 +39,25 @@  ```haskell >>> name = "Guillaume"->>> format [f|{name:<11}|]+>>> [f|{name:<11}|] "Guillaume  "->>> format [f|{name:>11}|]+>>> [f|{name:>11}|] "  Guillaume"->>> format [f|{name:|^13}|]+>>> [f|{name:|^13}|] "||Guillaume||" ```  Padding inside `=` the sign:  ```haskell->>> [fString|{-pi:=10.3}|]+>>> [f|{-pi:=10.3}|] "-    3.142" ```  ## Float rounding  ```haskell->>> format [f|{pi:.2}|]+>>> [f|{pi:.2}|] "3.14" ``` @@ -76,11 +65,11 @@  ```haskell >>> v = 31->>> format [f|Binary: {v:#b}|]+>>> [f|Binary: {v:#b}|] "Binary: 0b11111"->>> format [f|Octal (no prefix): {age:o}|]+>>> [f|Octal (no prefix): {age:o}|] "Octal (no prefix): 37"->>> format [f|Hexa (caps and prefix): {age:#X}|]+>>> [f|Hexa (caps and prefix): {age:#X}|] "Hexa (caps and prefix): 0x1F" ``` @@ -89,9 +78,9 @@ Using `,` or `_`.  ```haskell->>> [fString|{10 ^ 9 - 1:,}|]+>>> [f|{10 ^ 9 - 1:,}|] "999,999,999"->>> [fString|{2 ^ 32  -1:_b}|]+>>> [f|{2 ^ 32  -1:_b}|] "1111_1111_1111_1111_1111_1111_1111_1111" ``` @@ -100,9 +89,9 @@ Using `+` to display the positive sign (if any) or ` ` to display a space instead:  ```haskell->>> [fString|{pi:+.3}|]+>>> [f|{pi:+.3}|] "+3.142"->>> [fString|{pi: .3}|]+>>> [f|{pi: .3}|] " 3.142" ``` @@ -111,7 +100,7 @@ Preceding the width with a `0` enables sign-aware zero-padding, this is equivalent to inside `=` padding with a fill char of `0`.  ```haskell->>> [fString{-10:010}|]+>>> [f{-10:010}|] -000000010 ``` @@ -120,9 +109,9 @@ First argument inside the curly braces can be a valid Haskell expression, for example:  ```haskell->>> format [f|2pi = {2* pi:.2}|]+>>> [f|2pi = {2* pi:.2}|] 6.28->>> format [f|tail "hello" = {tail "hello":->6}|]+>>> [f|tail "hello" = {tail "hello":->6}|] "tail \"hello\" = --ello" ``` @@ -133,28 +122,24 @@ Most options can be combined. This generally leads to totally unreadable format string ;)  ```haskell->>> format [f|{pi:~>5.2}|]+>>> [f|{pi:~>5.2}|] "~~3.14" ``` -# Other quasiquoters--*PyF* main entry point is `f` but for convenience some other quasiquoters are provided:--- `f(StrictText|LazyText|String|Builder|IO)` directly call the underlying `Formatting` runner and produce the specified type.-- `f'` use type inference to deduce the type.+# Output type -`PyF` reexport most of `Formatting` runners, such as `format`, `sformat`, `formatToString`, ...+*PyF* main entry point `f` is polymorphic and can represents `Text`, lazy `Text`, `String`, lazy text `Builder` or `IO` operations. Most of the time, type inference will do the right thing for you, but you may need to add type annotations.  For example:  ```haskell->>> [f'|hello {pi.2}|] :: String+>>> [f|hello {pi.2}|] :: String "hello 3.14"->>> :type [fString|hello|]-[Char] ``` +Note: it works in ghci without any type annotation if the extensions+`OverloadedStrings` and `ExtendedDefaultRules` are enabled.+ # Caveats  ## Type inference@@ -199,7 +184,7 @@   too polymorphic), you will get an awful error:  ```haskell->>*> [fString|{True:d}|]+>>*> [f|{True:d}|]  <interactive>:80:10: error:     • No instance for (Integral Bool)@@ -210,21 +195,21 @@ - There is also one class of error related to alignement which can be triggered, when using alignement inside sign (i.e. `=`) with string. This can fail in two flavors:  ```haskell->>> [fString|{"hello":=10s}|]+>>> [f|{"hello":=10s}|]  <interactive>:88:1: error:     • Exception when trying to run compile-time code:         String Cannot be aligned with the inside `=` mode CallStack (from HasCallStack):   error, called at src/PyF/Internal/QQ.hs:143:18 in PyF-0.4.0.0-inplace:PyF.Internal.QQ-      Code: quoteExp fString "{\"hello\":=10s}"-    • In the quasi-quotation: [fString|{"hello":=10s}|]+      Code: quoteExp f "{\"hello\":=10s}"+    • In the quasi-quotation: [f|{"hello":=10s}|] ```  And  ```haskell-*PyF PyF.Internal.QQ> [fString|{"hello":=10}|]+*PyF PyF.Internal.QQ> [f|{"hello":=10}|]  <interactive>:89:10: error:     • String Cannot be aligned with the inside `=` mode@@ -234,7 +219,7 @@ - Finally, if you make any type error inside the expression field, you are on your own:  ```haskell->>> [fString|{3 + pi + "hello":10}|]+>>> [f|{3 + pi + "hello":10}|]  <interactive>:99:10: error:     • No instance for (Floating [Char]) arising from a use of ‘pi’@@ -255,7 +240,7 @@ import PyF  myCustomFormatter :: QuasiQuoter-myCustomFormatter = fStringWithDelimiters ('@','!')+myCustomFormatter = fWithDelimiters ('@','!') ```  Later, in another module:
src/PyF.hs view
@@ -4,46 +4,25 @@ {- | A lot of quasiquoters to format and interpolate string expression -} module PyF-  (f,-   f',-   fIO,-   fString,-   fBuilder,-   fLazyText,-   fStrictText,-+  ( f    -- * With custom delimiters-   fWithDelimiters,-   f'WithDelimiters,-   fIOWithDelimiters,-   fStringWithDelimiters,-   fBuilderWithDelimiters,-   fLazyTextWithDelimiters,-   fStrictTextWithDelimiters,--   -- * Formatting re-export-   runFormat,-   format,-   sformat,-   bprint,-   fprint,-   hprint)+  , fWithDelimiters+  ) where  import           Language.Haskell.TH.Quote (QuasiQuoter(..)) import qualified PyF.Internal.QQ as QQ -import Formatting (runFormat, format, sformat, bprint, fprint, hprint)-import qualified Formatting as F import           Language.Haskell.TH  import qualified Data.Text.Lazy as LText+import qualified Data.Text.Lazy.IO as LText import qualified Data.Text as SText import qualified Data.Text.Lazy.Builder as Builder  templateF :: (Char, Char) -> String -> QuasiQuoter templateF delimiters fName = QuasiQuoter {-    quoteExp = QQ.toExp delimiters+    quoteExp = \s -> (AppE (VarE 'magicFormat)) <$> (QQ.toExp delimiters s)   , quotePat = err "pattern"   , quoteType = err "type"   , quoteDec = err "declaration"@@ -51,69 +30,30 @@   where     err name = error (fName ++ ": This QuasiQuoter can not be used as a " ++ name ++ "!") --- | Returns an expression usable with Formatting.format (and similar functions)+-- | Generic formatter, can format an expression to (lazy) Text, String, Builder and IO () depending on type inference f :: QuasiQuoter 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 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-      e <- quoteExp qq s-      pure (AppE wrap e)-  }- class MagicFormat t where-  magicFormat :: F.Format t t -> t+  magicFormat :: Builder.Builder -> t  instance MagicFormat (IO ()) where-  magicFormat = F.fprint+  magicFormat = LText.putStrLn . Builder.toLazyText  instance MagicFormat [Char] where-  magicFormat = F.formatToString+  magicFormat = LText.unpack . Builder.toLazyText  instance MagicFormat SText.Text where-  magicFormat = F.sformat+  magicFormat = LText.toStrict . Builder.toLazyText  instance MagicFormat LText.Text where-  magicFormat = F.format+  magicFormat = Builder.toLazyText  instance MagicFormat Builder.Builder where-  magicFormat = F.bprint---- 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+  magicFormat = id  pythonDelimiters :: (Char, Char) pythonDelimiters = ('{', '}')---- | Format the format string and directly print it to stdout-fIOWithDelimiters delimiters = wrapQQ (templateF delimiters "fIO") (VarE 'F.fprint)---- | Format the format string as a 'String'-fStringWithDelimiters delimiters = wrapQQ (templateF delimiters "fString") (VarE 'F.formatToString)---- | Format the format string as a strict 'SText.Text'-fStrictTextWithDelimiters delimiters = wrapQQ (templateF delimiters "fStrictTeext") (VarE 'F.sformat)---- | Format the format string as a Lazy 'LText.Text'-fLazyTextWithDelimiters delimiters = wrapQQ (templateF delimiters "fLazy") (VarE 'F.sformat)---- | Format the format string as a 'Builder.Builder'-fBuilderWithDelimiters delimiters = wrapQQ (templateF delimiters "fBuilder") (VarE 'F.bprint)
src/PyF/Internal/PythonSyntax.hs view
@@ -33,6 +33,9 @@ import Data.Maybe (fromMaybe)  import qualified Data.Set as Set -- For fancyFailure+import qualified Language.Haskell.Meta.Syntax.Translate as SyntaxTranslate+import qualified Language.Haskell.Exts.Parser as ParseExp+import qualified Language.Haskell.Exts.SrcLoc as SrcLoc import PyF.Formatters  type Parser t = Parsec Void String t@@ -60,7 +63,7 @@  -- | A format string is composed of many chunks of raw string or replacement data Item = Raw String -- ^ A raw string-           | Replacement String (Maybe FormatMode) -- ^ A replacement string, composed of an arbitrary Haskell expression followed by an optional formatter+           | Replacement Exp (Maybe FormatMode) -- ^ A replacement string, composed of an arbitrary Haskell expression followed by an optional formatter            deriving (Show)  {- |@@ -103,7 +106,7 @@ replacementField :: (Char, Char) -> Parser Item replacementField (charOpening, charClosing) = do   _ <- char charOpening-  expr <- many (noneOf (charClosing:":"))+  expr <- evalExpr (many (noneOf (charClosing:":" :: [Char])))   fmt <- optional $ do     _ <- char ':'     format_spec@@ -170,12 +173,25 @@  lastCharFailed :: String -> Parser t lastCharFailed err = do-  (SourcePos name line col) <- getPosition+  offset <- getOffset+  setOffset (offset - 1) -  -- This is right as long as there is not line break in the string-  setPosition (SourcePos name line (mkPos (unPos col - 1)))   fancyFailure (Set.singleton (ErrorFail err)) +evalExpr :: Parser String -> Parser Exp+evalExpr exprParser = do+  offset <- getOffset+  s <- exprParser+  case SyntaxTranslate.toExp <$> ParseExp.parseExp s of+    ParseExp.ParseOk expr -> pure expr+    ParseExp.ParseFailed (SrcLoc.SrcLoc _name' line col) err -> do+      let+        linesBefore = take (line - 1) (lines s)+        currentOffset = length (unlines linesBefore) + col - 1++      setOffset (offset + currentOffset)+      fancyFailure (Set.singleton (ErrorFail err))+ overrideAlignmentIfZero :: Bool -> Maybe (Maybe Char, AnyAlign) -> Maybe (Maybe Char, AnyAlign) overrideAlignmentIfZero True Nothing = Just (Just '0', AnyAlign AlignInside) overrideAlignmentIfZero True (Just (Nothing, al)) = Just (Just '0', al)@@ -263,7 +279,7 @@     ]  fill :: Parser Char-fill = anyChar+fill = anySingle  align :: Parser AnyAlign align = choice [
src/PyF/Internal/QQ.hs view
@@ -10,7 +10,9 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE UndecidableInstances #-} -{- | This module uses the python mini language detailed in 'PyF.Internal.PythonSyntax' to build an template haskell expression which represents a 'Formatting.Format'.+{- | This module uses the python mini language detailed in+'PyF.Internal.PythonSyntax' to build an template haskell expression+representing a formatted string ('String', 'Text' or 'Builder').  -} module PyF.Internal.QQ (@@ -20,8 +22,6 @@  import Text.Megaparsec -import qualified Formatting as F- import           Language.Haskell.TH  import Data.Maybe (fromMaybe)@@ -30,13 +30,11 @@  import qualified Data.Text.Lazy as LText import qualified Data.Text as SText-import qualified Data.List.NonEmpty as NonEmpty  import qualified Data.Word as Word import qualified Data.Int as Int import Numeric.Natural -import Language.Haskell.Meta.Parse (parseExp)  import PyF.Internal.PythonSyntax import qualified PyF.Formatters as Formatters@@ -49,23 +47,40 @@ toExp:: (Char, Char) -> String -> Q Exp toExp delimiters s = do   filename <- loc_filename <$> location+  case parse (parseGenericFormatString delimiters) filename s of+    Left err -> do+      err' <- overrideErrorForFile filename err+      fail (errorBundlePretty err')+    Right items -> goFormat items++-- Megaparsec displays error relative to what they parsed+-- However the formatting string is part of a more complex file and we+-- want error reporting relative to that file+overrideErrorForFile :: FilePath -> ParseErrorBundle String e -> Q (ParseErrorBundle String e)+-- We have no may to recover interactive content+-- So we won't do better than displaying the megaparsec+-- error relative to the quasi quote content+overrideErrorForFile "<interactive>" err = pure err+-- We know the content of the file here+overrideErrorForFile filename err = do   (line, col) <- loc_start <$> location+  fileContent <- runIO (readFile filename) -  let change_log "<interactive>" currentState = currentState-      change_log _ currentState = let-        (SourcePos sName _ _) NonEmpty.:| xs = statePos currentState-        in currentState {statePos = (SourcePos sName (mkPos line) (mkPos col)) NonEmpty.:| xs}+  let+    -- drop the first lines of the file up to the line containing the quasiquote+    -- then, split in what is before the QQ and what is after.+    -- e.g.  blablabla [f|hello|] will split to+    -- "blablabla [f|" and "hello|]"+    (prefix, postfix) = splitAt (col - 1) $ unlines $ drop (line - 1) (lines fileContent) -  case parse (updateParserState (change_log filename) >> parseGenericFormatString delimiters) filename s of-    Left err -> do -      if filename == "<interactive>"-        then do-          fail (parseErrorPretty' s err)-        else do-          fileContent <- runIO (readFile filename)-          fail (parseErrorPretty' fileContent err)-    Right items -> goFormat items+  pure $ err {+    bundlePosState = (bundlePosState err) {+        pstateInput = postfix,+        pstateSourcePos = SourcePos filename (mkPos line) (mkPos col),+        pstateOffset = 0,+        pstateLinePrefix = prefix+        }}  toExpPython :: String -> Q Exp toExpPython = toExp ('{', '}')@@ -74,18 +89,15 @@ goFormat items = foldl1 fofo <$> (mapM toFormat items)  fofo :: Exp -> Exp -> Exp-fofo s0 s1 = InfixE (Just s0) (VarE '(F.%)) (Just s1)+fofo s0 s1 = InfixE (Just s0) (VarE '(<>)) (Just s1)  -- Real formatting is here  toFormat :: Item -> Q Exp-toFormat (Raw x) = [| F.now (Builder.fromString x) |]-toFormat (Replacement x y) = do+toFormat (Raw x) = [| Builder.fromString x |]+toFormat (Replacement expr y) = do   formatExpr <- padAndFormat (fromMaybe DefaultFormatMode y)--  case parseExp x of-    Right expr -> pure (AppE (VarE 'F.now) (VarE 'Builder.fromString `AppE` (formatExpr `AppE` expr)))-    Left err -> fail err+  pure (VarE 'Builder.fromString `AppE` (formatExpr `AppE` expr))  changePrec :: Precision -> Maybe Int changePrec PrecisionDefault = Just 6@@ -123,11 +135,13 @@   PercentF prec alt s -> [| formatAnyFractional $(withAlt alt Formatters.Percent) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]    -- Default / String-  DefaultF prec s -> [| \v ->-      case categorise (Proxy :: Proxy $(typeAllowed)) v of-        Integral i -> formatAnyIntegral Formatters.Decimal s (newPadding padding) (toGrp grouping 3) i-        Fractional f -> formatAnyFractional Formatters.Generic s (newPadding padding) (toGrp grouping 3) (changePrec' prec) f-        StringType f -> Formatters.formatString (newPaddingForString padding) (changePrec' prec) f++  -- Note: v / i / f uses pat and var to ensure stable name in error message+  DefaultF prec s -> [| \($(pat "v")) ->+      case categorise (Proxy :: Proxy $(typeAllowed)) $(var "v") of+        Integral $(pat "i") -> formatAnyIntegral Formatters.Decimal s (newPadding padding) (toGrp grouping 3) $(var "i")+        Fractional $(pat "f") -> formatAnyFractional Formatters.Generic s (newPadding padding) (toGrp grouping 3) (changePrec' prec) $(var "f")+        StringType $(pat "f") -> Formatters.formatString (newPaddingForString padding) (changePrec' prec) $(var "f")                          |]    where      typeAllowed :: Q Type@@ -140,6 +154,15 @@    StringF prec -> [| Formatters.formatString pad (changePrec' prec) |]     where pad = newPaddingForString padding++-- Generate stable name in TH slices+-- The name are postfixed by _PyF to limit the risk of shadowing++pat :: String -> Q Pat+pat name = pure (VarP (mkName (name ++ "_PyF")))++var :: String -> Q Exp+var name = pure (VarE (mkName (name ++ "_PyF")))  newPaddingForString :: Padding -> Maybe (Int, Formatters.AlignMode 'Formatters.AlignAll, Char) newPaddingForString padding = case padding of
test/Spec.hs view
@@ -11,7 +11,7 @@ import SpecCustomDelimiters  {--   - Normal tests are done using the recommanded API: [fString|.....|]+   - Normal tests are done using the recommanded API: [f|.....|]    - Test with $(checkExample formatString result) are checked against the python reference implementation. Result is provided as documentation.    - Test with $(checkExampleDiff formatString result) are not checked against the python reference implementation. This is known (and documented) differences.    - Test with $(check formatString) are only tested against the python reference implementation.@@ -27,9 +27,9 @@       anInt = 123       aFloat = 0.234       aString = "hello"-    it "int" $ [fString|{anInt}|] `shouldBe` "123"-    it "float" $ [fString|{aFloat}|] `shouldBe` "0.234"-    it "string" $ [fString|{aString}|] `shouldBe` "hello"+    it "int" $ [f|{anInt}|] `shouldBe` "123"+    it "float" $ [f|{aFloat}|] `shouldBe` "0.234"+    it "string" $ [f|{aString}|] `shouldBe` "hello"   describe "only expression" $ do     describe "default" $ do       it "int" $(checkExample "{123}" "123")@@ -90,6 +90,10 @@     describe "percent" $ do       it "simple" $(checkExample "{0.234:%}" "23.400000%")       it "precision" $(checkExample "{0.234:.2%}" "23.40%")++    describe "string truncating" $ do+      it "works" $ $(checkExample "{\"hello\":.3}" "hel")+     describe "padding" $ do       describe "default char" $ do         it "left" $(checkExample "{\"hello\":<10}" "hello     ")@@ -172,7 +176,7 @@         age = 31         euroToFrancs = 6.55957       in-        [fString|hello {name} you are {age} years old and the conversion rate of euro is {euroToFrancs:.2}|] `shouldBe` ("hello Guillaume you are 31 years old and the conversion rate of euro is 6.56")+        [f|hello {name} you are {age} years old and the conversion rate of euro is {euroToFrancs:.2}|] `shouldBe` ("hello Guillaume you are 31 years old and the conversion rate of euro is 6.56")     describe "error reporting" $ do@@ -180,14 +184,14 @@    describe "sub expressions" $ do     it "works" $ do-      [fString|2pi = {2 * pi:.2}|] `shouldBe` "2pi = 6.28"+      [f|2pi = {2 * pi:.2}|] `shouldBe` "2pi = 6.28"    describe "escape strings" $ do     it "works" $ do-      [fString|hello \n\b|] `shouldBe` "hello \n\b"+      [f|hello \n\b|] `shouldBe` "hello \n\b"    it "escape chars" $ do-     [fString|}}{{}}{{|] `shouldBe` "}{}{"+     [f|}}{{}}{{|] `shouldBe` "}{}{"    describe "custom delimiters" $ do     it "works" $ do
test/SpecCustomDelimiters.hs view
@@ -5,4 +5,4 @@ import PyF  myCustomFormatter :: QuasiQuoter-myCustomFormatter = fStringWithDelimiters ('@','!')+myCustomFormatter = fWithDelimiters ('@','!')
test/SpecUtils.hs view
@@ -12,7 +12,6 @@ import Language.Haskell.TH import Language.Haskell.TH.Syntax -import Formatting import System.Process import System.Exit @@ -51,7 +50,7 @@   case pythonRes of     Nothing -> [| expectationFailure $ "Expression: `" ++ s ++ "` fails in python" |]     Just res -> do-      let qexp = [| formatToString $(toExpPython s)  `shouldBe` res |]+      let qexp = [| $(toExpPython s)  `shouldBe` res |]       case exampleStr of         Nothing -> qexp         Just e -> if res == e@@ -72,7 +71,7 @@      against the python implementation -} checkExampleDiff :: String -> String -> Q Exp-checkExampleDiff s res = [| formatToString $(toExpPython s) `shouldBe` res |]+checkExampleDiff s res = [| $(toExpPython s) `shouldBe` res |]  {- | `check formatString` checks only with the python implementation -}