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 +16/−208
- Data/Text/Format/Heavy/Parse/Braces.hs +73/−0
- Data/Text/Format/Heavy/Parse/Shell.hs +98/−0
- Data/Text/Format/Heavy/Parse/Types.hs +19/−0
- Data/Text/Format/Heavy/Parse/VarFormat.hs +165/−0
- text-format-heavy.cabal +5/−1
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,