packages feed

text-format-heavy 0.1.2.1 → 0.1.3.0

raw patch · 6 files changed

+376/−209 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.Text.Format.Heavy.Parse: ParserState :: Int -> ParserState
- Data.Text.Format.Heavy.Parse: [psNextIndex] :: ParserState -> Int
- Data.Text.Format.Heavy.Parse: data ParserState
- Data.Text.Format.Heavy.Parse: initParserState :: ParserState
- Data.Text.Format.Heavy.Parse: instance GHC.Classes.Eq Data.Text.Format.Heavy.Parse.ParserState
- Data.Text.Format.Heavy.Parse: instance GHC.Show.Show Data.Text.Format.Heavy.Parse.ParserState
- Data.Text.Format.Heavy.Parse: pFormat :: Parser Format
- Data.Text.Format.Heavy.Parse: type Parser a = Parsec Text ParserState a
+ Data.Text.Format.Heavy.Parse.Braces: pBracesFormat :: Parser Format
+ Data.Text.Format.Heavy.Parse.Braces: parseFormat :: Text -> Either ParseError Format
+ Data.Text.Format.Heavy.Parse.Braces: parseFormat' :: Text -> Format
+ Data.Text.Format.Heavy.Parse.Shell: pShellFormat :: Parser Format
+ Data.Text.Format.Heavy.Parse.Shell: parseShellFormat :: Text -> Either ParseError Format
+ Data.Text.Format.Heavy.Parse.Shell: parseShellFormat' :: Text -> Format
+ Data.Text.Format.Heavy.Parse.Types: ParserState :: Int -> ParserState
+ Data.Text.Format.Heavy.Parse.Types: [psNextIndex] :: ParserState -> Int
+ Data.Text.Format.Heavy.Parse.Types: data ParserState
+ Data.Text.Format.Heavy.Parse.Types: initParserState :: ParserState
+ Data.Text.Format.Heavy.Parse.Types: instance GHC.Classes.Eq Data.Text.Format.Heavy.Parse.Types.ParserState
+ Data.Text.Format.Heavy.Parse.Types: instance GHC.Show.Show Data.Text.Format.Heavy.Parse.Types.ParserState
+ Data.Text.Format.Heavy.Parse.Types: type Parser a = Parsec Text ParserState a
+ Data.Text.Format.Heavy.Parse.VarFormat: pBoolFormat :: Parsec Text st BoolFormat
+ Data.Text.Format.Heavy.Parse.VarFormat: pGenericFormat :: Parsec Text st GenericFormat
+ Data.Text.Format.Heavy.Parse.VarFormat: parseBoolFormat :: Text -> Either ParseError BoolFormat
+ Data.Text.Format.Heavy.Parse.VarFormat: parseGenericFormat :: Text -> Either ParseError GenericFormat
+ Data.Text.Format.Heavy.Parse.VarFormat: parseMaybeFormat :: Text -> Maybe (Text, Text)

Files

Data/Text/Format/Heavy/Parse.hs view
@@ -5,9 +5,20 @@ -- * String formats. This is the whole construct like @"Hello, {}! Your account balance is {1:+8.4}."@. -- * Variable formats. This is only part after colon in braces, i.e. the @+8.4@ thing in previous example. ----- The string format syntax is supposed to be very stable and simple. It is basically defined by phrase--- "any part in braces is variable substitution".+-- The string format syntax is supposed to be very stable and simple.+-- There are more than one commonly used formatting string syntax, though. This package provides the+-- following syntaxes: --+-- * The default syntax, which is basically defined by phrase "any part in braces is variable substitution".+--   This syntax is defined in @Data.Text.Format.Heavy.Parse.Braces@ module.+--+-- * Shell-like syntax, which is basically defined by phrase "any part starting with dollar sign is variable+--   substitution". This syntax is defined in @Data.Text.Format.Heavy.Parse.Shell@ module.+--+-- It is possible to define your own syntaxes: you just need to parse an instance of @Format@ type from some+-- sort of string. The default syntax will still remain default, in sence that @instance IsString Format@ is+-- defined in terms of this syntax in @Data.Text.Format.Heavy.Instances@ module.+-- -- Variable formats syntax depends on type of data which we are going to format. These formats can be -- pretty complex, for example they can include alignment, rounding, and so on. --@@ -17,9 +28,7 @@    parseGenericFormat, parseBoolFormat,    parseMaybeFormat,    -- * Parsec functions-   pFormat, pGenericFormat, pBoolFormat,-   -- * Utility types-   Parser, ParserState (..), initParserState+   pGenericFormat, pBoolFormat   ) where  import Data.Maybe@@ -30,207 +39,6 @@  import Data.Text.Format.Heavy.Types import Data.Text.Format.Heavy.Formats--data ParserState = ParserState {-    psNextIndex :: Int-  }-  deriving (Eq, Show)--initParserState :: ParserState-initParserState = ParserState 0--type Parser a = Parsec TL.Text ParserState a---- TODO: proper handling of escaping-anyChar' :: Parser Char-anyChar' =-  noneOf "{}" <|> try ('{' <$ string "\\{") <|> try ('}' <$ string "\\}")--pVerbatim :: Parser FormatItem-pVerbatim = (FString . TL.pack) `fmap` many1 anyChar'--pVariable :: Parser FormatItem-pVariable = do-    (name, fmt) <- between (char '{') (char '}') variable-    return $ FVariable (TL.pack name) fmt-  where-    variable = do-      name <- many alphaNum-      mbColon <- optionMaybe $ char ':'-      fmt <- case mbColon of-               Nothing -> return Nothing-               Just _ -> do-                  fmtStr <- many anyChar'-                  return $ Just $ TL.pack fmtStr-      name' <- if null name-                 then do-                      st <- getState-                      let n = psNextIndex st-                      modifyState $ \st -> st {psNextIndex = psNextIndex st + 1}-                      return $ show n-                 else return name-      return (name', fmt)---- | Parsec parser for string format.-pFormat :: Parser Format-pFormat = Format `fmap` many (try pVariable <|> pVerbatim)---- | Parse string format definition.-parseFormat :: TL.Text -> Either ParseError Format-parseFormat text = runParser pFormat initParserState "<format string>" text---- | Version of parseFormat which throws @error@ in case of syntax error in the formatting string.-parseFormat' :: TL.Text -> Format-parseFormat' text = either (error . show) id $ parseFormat text---- | Parsec parser for generic (Python-like) variable format.-pGenericFormat :: Parsec TL.Text st GenericFormat-pGenericFormat = do-    mbFillAlign <- optionMaybe (try pFillAlign <?> "fill and align specification")-    let fill = fromMaybe ' ' $ fst `fmap` mbFillAlign-    let align = snd `fmap` mbFillAlign-    mbSign <- optionMaybe (pSign <?> "sign specification")-    let sign = fromMaybe OnlyNegative mbSign-    mbLeading0x <- optionMaybe (pLeading0x <?> "leading 0x specification")-    let leading0x = fromMaybe False mbLeading0x-    mbWidth <- optionMaybe (pWidth <?> "width specification")-    mbPrecision <- optionMaybe (pPrecision <?> "precision specification")-    mbRadix <- optionMaybe (pRadix <?> "radix specification")-    return $ GenericFormat {-               gfFillChar = fill-             , gfAlign = align-             , gfSign = sign-             , gfLeading0x = leading0x-             , gfWidth = mbWidth-             , gfPrecision = mbPrecision-             , gfRadix = mbRadix-             }-  where-    pAlign :: Parsec TL.Text st Align-    pAlign = do-      alignChar <- oneOf "<>^"-      align <- case alignChar of-                 '<' -> return AlignLeft-                 '>' -> return AlignRight-                 '^' -> return AlignCenter-                 _ -> fail $ "Unexpected align char: " ++ [alignChar]-      return align--    pAlignWithFill :: Parsec TL.Text st (Char, Align)-    pAlignWithFill = do-      fill <- noneOf "<>=^"-      align <- pAlign-      return (fill, align)--    pAlignWithoutFill :: Parsec TL.Text st (Char, Align)-    pAlignWithoutFill = do-      align <- pAlign-      return (' ', align)--    pFillAlign :: Parsec TL.Text st (Char, Align)-    pFillAlign = do-      try pAlignWithoutFill <|> pAlignWithFill--    pSign :: Parsec TL.Text st Sign-    pSign = do-      signChar <- oneOf "+- "-      sign <- case signChar of-                '+' -> return Always-                '-' -> return OnlyNegative-                ' ' -> return SpaceForPositive-                _ -> fail $ "Unexpected sign char: " ++ [signChar]-      return sign--    pLeading0x :: Parsec TL.Text st Bool-    pLeading0x = do-      mbSharp <- optionMaybe $ char '#'-      case mbSharp of-        Nothing -> return False-        Just _ -> return True--    natural :: Parsec TL.Text st Int-    natural = do-      ws <- many1 $ oneOf "0123456789"-      return $ read ws--    pWidth :: Parsec TL.Text st Int-    pWidth = natural--    pPrecision :: Parsec TL.Text st Int-    pPrecision = do-      char '.'-      natural-    -    pRadix :: Parsec TL.Text st Radix-    pRadix = do-      rc <- oneOf "xhd"-      case rc of-        'x' -> return Hexadecimal-        'h' -> return Hexadecimal-        'd' -> return Decimal---- | Parse generic variable format.------ Syntax is:------ @--- [[fill]align][sign][#][width][.precision][radix]--- @------ where:------ * fill - padding character (space by default)--- * align - alignment indicator (@<@, @>@, or @^@)--- * sign - when to show number's sign (@+@, @-@, or space)--- * @#@ - if specified, then for hexadecimal numbers the leading @0x@ will be added--- * width - minimum length of the field--- * precision - number of decimal places after point, for floatting-point numbers--- * radix - @h@ or @x@ for hexadecimal, @d@ for decimal (default).----parseGenericFormat :: TL.Text -> Either ParseError GenericFormat-parseGenericFormat text = runParser pGenericFormat () "<variable format specification>" text---- | Parsec parser for Bool format-pBoolFormat :: Parsec TL.Text st BoolFormat-pBoolFormat = do-  true <- many $ noneOf ":,;"-  oneOf ":,;"-  false <- many $ anyChar-  return $ BoolFormat (TL.pack true) (TL.pack false)---- | Parse Bool format.------ Syntax is:------ @--- TRUE:FALSE--- @------ Colon can be replaced with comma or semicolon.------ For example, valid format specifications are @true:false@ (the default one),--- @True:False@, @yes:no@, and so on.----parseBoolFormat :: TL.Text -> Either ParseError BoolFormat-parseBoolFormat text = runParser pBoolFormat () "<boolean format specification>" text---- | Try to parse format for @Maybe x@ type.--- The syntax is:------ @--- someformat|nothing--- @------ where @someformat@ is format for the @x@ type, and @nothing@ is the string--- to be substituted for @Nothing@ value.------ Returns Nothing, if format does not contain @|@. Otherwise, returns--- @Just (someformat, nothing)@.----parseMaybeFormat :: TL.Text -> Maybe (TL.Text, TL.Text)-parseMaybeFormat text =-  let (xFmtStr, nothingStr) = TL.breakOnEnd "|" text-  in  if TL.null xFmtStr-        then Nothing-        else Just (TL.init xFmtStr, nothingStr)+import Data.Text.Format.Heavy.Parse.VarFormat+import Data.Text.Format.Heavy.Parse.Braces 
+ Data/Text/Format/Heavy/Parse/Braces.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+-- | This module defines the default syntax of format strings, generally described as+-- "any part in braces is variable substitution".+--+-- Examples of valid variable substitutions are:+--+-- * @"Simple: {}"@+--+-- * @"Numbered: {0}"@+--+-- * @"Named: {var}"@+--+-- * @"Specifying variable formatting: {var:+8.4}"@+--+module Data.Text.Format.Heavy.Parse.Braces+  (-- * Parse functions+   parseFormat, parseFormat',+   -- * Parsec functions+   pBracesFormat+     ) where++import Data.Maybe+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as B+import Text.Parsec++import Data.Text.Format.Heavy.Types+import Data.Text.Format.Heavy.Formats+import Data.Text.Format.Heavy.Parse.Types++-- TODO: proper handling of escaping+anyChar' :: Parser Char+anyChar' =+  noneOf "{}" <|> try ('{' <$ string "\\{") <|> try ('}' <$ string "\\}")++pVerbatim :: Parser FormatItem+pVerbatim = (FString . TL.pack) `fmap` many1 anyChar'++pVariable :: Parser FormatItem+pVariable = do+    (name, fmt) <- between (char '{') (char '}') variable+    return $ FVariable (TL.pack name) fmt+  where+    variable = do+      name <- many alphaNum+      mbColon <- optionMaybe $ char ':'+      fmt <- case mbColon of+               Nothing -> return Nothing+               Just _ -> do+                  fmtStr <- many anyChar'+                  return $ Just $ TL.pack fmtStr+      name' <- if null name+                 then do+                      st <- getState+                      let n = psNextIndex st+                      modifyState $ \st -> st {psNextIndex = psNextIndex st + 1}+                      return $ show n+                 else return name+      return (name', fmt)++-- | Parsec parser for string format.+pBracesFormat :: Parser Format+pBracesFormat = Format `fmap` many (try pVariable <|> pVerbatim)++-- | Parse string format definition.+parseFormat :: TL.Text -> Either ParseError Format+parseFormat text = runParser pBracesFormat initParserState "<format string>" text++-- | Version of parseFormat which throws @error@ in case of syntax error in the formatting string.+parseFormat' :: TL.Text -> Format+parseFormat' text = either (error . show) id $ parseFormat text+
+ Data/Text/Format/Heavy/Parse/Shell.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE OverloadedStrings #-}+-- | This module defines shell-like syntax of format strings, generally described as+-- "any part after dollar sign is a variable substitution".+--+-- Examples of valid variable substitutions are:+--+-- * @"Simple: ${}"@. Note that to have auto-numbered placeholders in this syntax, you have to +--   write @${}@; both dollar sign and braces are necessary.+--+-- * @"Numbered: $1"@ or @"Numbered: ${1}"@.+--+-- * @"Named: $var"@ or @"Named: ${var}"@.+--+-- * @"Specifying variable formatting: ${var:+8.4}"@. To specify variable format, you have to+--   use braces.+--+-- This syntax is not the default, so to use it you have to explicitly call @parseShellFormat'@:+--+-- @+-- {-\# LANGUAGE OverloadedStrings #\-}+-- module Main where+--+-- import Data.Time+-- import qualified Data.Text.Lazy.IO as TLIO+-- import Data.Text.Format.Heavy+-- import Data.Text.Format.Heavy.Parse.Shell+--+-- main :: IO ()+-- main = do+--   name <- getLine+--   time <- getZonedTime+--   TLIO.putStrLn $ format (parseShellFormat' "Hello, ${}! It is ${:%H:%M:%S} now.") (name, time)+-- @+--+module Data.Text.Format.Heavy.Parse.Shell+  (-- * Parse functions+   parseShellFormat, parseShellFormat',+   -- * Parsec functions+   pShellFormat+  ) where++import Data.Maybe+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as B+import Text.Parsec++import Data.Text.Format.Heavy.Types+import Data.Text.Format.Heavy.Formats+import Data.Text.Format.Heavy.Parse.Types++-- TODO: proper handling of escaping+anyChar' :: Parser Char+anyChar' =+  noneOf "$" <|> try ('$' <$ string "$$")++pVerbatim :: Parser FormatItem+pVerbatim = (FString . TL.pack) `fmap` many1 anyChar'++pVariable :: Parser FormatItem+pVariable = do+    char '$'+    (name, fmt) <- try bracedVariable <|> unbracedVariable+    return $ FVariable (TL.pack name) fmt+  where+    bracedVariable = between (char '{') (char '}') $ do+      name <- many alphaNum+      mbColon <- optionMaybe $ char ':'+      fmt <- case mbColon of+               Nothing -> return Nothing+               Just _ -> do+                  fmtStr <- many (noneOf "}" <|> try ('}' <$ string "\\}"))+                  return $ Just $ TL.pack fmtStr+      name' <- if null name+                 then do+                      st <- getState+                      let n = psNextIndex st+                      modifyState $ \st -> st {psNextIndex = psNextIndex st + 1}+                      return $ show n+                 else return name+      return (name', fmt)++    unbracedVariable = do+      name <- many1 alphaNum+      return (name, Nothing)++-- | Parsec parser for string format.+pShellFormat :: Parser Format+pShellFormat = Format `fmap` many (try pVariable <|> pVerbatim)++-- | Parse string format definition.+parseShellFormat :: TL.Text -> Either ParseError Format+parseShellFormat text = runParser pShellFormat initParserState "<format string>" text++-- | Version of parseShellFormat which throws @error@ in case of syntax error in the formatting string.+parseShellFormat' :: TL.Text -> Format+parseShellFormat' text = either (error . show) id $ parseShellFormat text+
+ Data/Text/Format/Heavy/Parse/Types.hs view
@@ -0,0 +1,19 @@++module Data.Text.Format.Heavy.Parse.Types+  (-- * Utility types+   Parser, ParserState (..), initParserState+  ) where++import qualified Data.Text.Lazy as TL+import Text.Parsec++data ParserState = ParserState {+    psNextIndex :: Int+  }+  deriving (Eq, Show)++initParserState :: ParserState+initParserState = ParserState 0++type Parser a = Parsec TL.Text ParserState a+
+ Data/Text/Format/Heavy/Parse/VarFormat.hs view
@@ -0,0 +1,165 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.Text.Format.Heavy.Parse.VarFormat+  where++import Data.Maybe+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as B+import Text.Parsec++import Data.Text.Format.Heavy.Types+import Data.Text.Format.Heavy.Formats++-- | Parsec parser for generic (Python-like) variable format.+pGenericFormat :: Parsec TL.Text st GenericFormat+pGenericFormat = do+    mbFillAlign <- optionMaybe (try pFillAlign <?> "fill and align specification")+    let fill = fromMaybe ' ' $ fst `fmap` mbFillAlign+    let align = snd `fmap` mbFillAlign+    mbSign <- optionMaybe (pSign <?> "sign specification")+    let sign = fromMaybe OnlyNegative mbSign+    mbLeading0x <- optionMaybe (pLeading0x <?> "leading 0x specification")+    let leading0x = fromMaybe False mbLeading0x+    mbWidth <- optionMaybe (pWidth <?> "width specification")+    mbPrecision <- optionMaybe (pPrecision <?> "precision specification")+    mbRadix <- optionMaybe (pRadix <?> "radix specification")+    return $ GenericFormat {+               gfFillChar = fill+             , gfAlign = align+             , gfSign = sign+             , gfLeading0x = leading0x+             , gfWidth = mbWidth+             , gfPrecision = mbPrecision+             , gfRadix = mbRadix+             }+  where+    pAlign :: Parsec TL.Text st Align+    pAlign = do+      alignChar <- oneOf "<>^"+      align <- case alignChar of+                 '<' -> return AlignLeft+                 '>' -> return AlignRight+                 '^' -> return AlignCenter+                 _ -> fail $ "Unexpected align char: " ++ [alignChar]+      return align++    pAlignWithFill :: Parsec TL.Text st (Char, Align)+    pAlignWithFill = do+      fill <- noneOf "<>=^"+      align <- pAlign+      return (fill, align)++    pAlignWithoutFill :: Parsec TL.Text st (Char, Align)+    pAlignWithoutFill = do+      align <- pAlign+      return (' ', align)++    pFillAlign :: Parsec TL.Text st (Char, Align)+    pFillAlign = do+      try pAlignWithoutFill <|> pAlignWithFill++    pSign :: Parsec TL.Text st Sign+    pSign = do+      signChar <- oneOf "+- "+      sign <- case signChar of+                '+' -> return Always+                '-' -> return OnlyNegative+                ' ' -> return SpaceForPositive+                _ -> fail $ "Unexpected sign char: " ++ [signChar]+      return sign++    pLeading0x :: Parsec TL.Text st Bool+    pLeading0x = do+      mbSharp <- optionMaybe $ char '#'+      case mbSharp of+        Nothing -> return False+        Just _ -> return True++    natural :: Parsec TL.Text st Int+    natural = do+      ws <- many1 $ oneOf "0123456789"+      return $ read ws++    pWidth :: Parsec TL.Text st Int+    pWidth = natural++    pPrecision :: Parsec TL.Text st Int+    pPrecision = do+      char '.'+      natural+    +    pRadix :: Parsec TL.Text st Radix+    pRadix = do+      rc <- oneOf "xhd"+      case rc of+        'x' -> return Hexadecimal+        'h' -> return Hexadecimal+        'd' -> return Decimal++-- | Parse generic variable format.+--+-- Syntax is:+--+-- @+-- [[fill]align][sign][#][width][.precision][radix]+-- @+--+-- where:+--+-- * fill - padding character (space by default)+-- * align - alignment indicator (@<@, @>@, or @^@)+-- * sign - when to show number's sign (@+@, @-@, or space)+-- * @#@ - if specified, then for hexadecimal numbers the leading @0x@ will be added+-- * width - minimum length of the field+-- * precision - number of decimal places after point, for floatting-point numbers+-- * radix - @h@ or @x@ for hexadecimal, @d@ for decimal (default).+--+parseGenericFormat :: TL.Text -> Either ParseError GenericFormat+parseGenericFormat text = runParser pGenericFormat () "<variable format specification>" text++-- | Parsec parser for Bool format+pBoolFormat :: Parsec TL.Text st BoolFormat+pBoolFormat = do+  true <- many $ noneOf ":,;"+  oneOf ":,;"+  false <- many $ anyChar+  return $ BoolFormat (TL.pack true) (TL.pack false)++-- | Parse Bool format.+--+-- Syntax is:+--+-- @+-- TRUE:FALSE+-- @+--+-- Colon can be replaced with comma or semicolon.+--+-- For example, valid format specifications are @true:false@ (the default one),+-- @True:False@, @yes:no@, and so on.+--+parseBoolFormat :: TL.Text -> Either ParseError BoolFormat+parseBoolFormat text = runParser pBoolFormat () "<boolean format specification>" text++-- | Try to parse format for @Maybe x@ type.+-- The syntax is:+--+-- @+-- someformat|nothing+-- @+--+-- where @someformat@ is format for the @x@ type, and @nothing@ is the string+-- to be substituted for @Nothing@ value.+--+-- Returns Nothing, if format does not contain @|@. Otherwise, returns+-- @Just (someformat, nothing)@.+--+parseMaybeFormat :: TL.Text -> Maybe (TL.Text, TL.Text)+parseMaybeFormat text =+  let (xFmtStr, nothingStr) = TL.breakOnEnd "|" text+  in  if TL.null xFmtStr+        then Nothing+        else Just (TL.init xFmtStr, nothingStr)+
text-format-heavy.cabal view
@@ -1,5 +1,5 @@ name:                text-format-heavy-version:             0.1.2.1+version:             0.1.3.0 synopsis:            Full-weight string formatting library, analog of Python's string.format description:         This package contains full-featured string formatting function, similar to                      Python's string.format. Features include:@@ -37,6 +37,10 @@ library   exposed-modules:     Data.Text.Format.Heavy,                        Data.Text.Format.Heavy.Parse,+                       Data.Text.Format.Heavy.Parse.Types,+                       Data.Text.Format.Heavy.Parse.VarFormat,+                       Data.Text.Format.Heavy.Parse.Braces,+                       Data.Text.Format.Heavy.Parse.Shell,                        Data.Text.Format.Heavy.Build,                        Data.Text.Format.Heavy.Types,                        Data.Text.Format.Heavy.Formats,