language-puppet-1.3.11: Puppet/Interpreter/Resolve/Sprintf.hs
module Puppet.Interpreter.Resolve.Sprintf (
sprintf
) where
import Control.Applicative
import Control.Monad.Except
import Data.Attoparsec.Text
import Data.Scientific (Scientific)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB
import qualified Data.Text.Lazy.Builder.Int as TB
import qualified Data.Text.Lazy.Builder.Scientific as TB
import Puppet.Interpreter.Types
import Puppet.Interpreter.Utils
import Puppet.Utils
import Puppet.PP (pretty)
import Puppet.Interpreter.PrettyPrinter()
data Flag = Minus | Plus | Space | Zero | Hash
deriving (Show, Eq)
data FLen = Lhh | Lh | Ll | Lll | LL | Lz | Lj | Lt
deriving (Show, Eq)
data FType = TPct | Td | Tu | Tf | TF | Te | TE | Tg | TG | Tx | TX | To | Ts | Tc | Tp | Ta | TA
deriving (Show, Eq)
data PrintfFormat = PrintfFormat { _pfFlags :: [Flag]
, _pfWidth :: Maybe Int
, _pfPrec :: Maybe Int
, _pfLen :: Maybe FLen
, _pfType :: FType
} deriving (Show, Eq)
data FormatStringPart = Raw T.Text
| Format PrintfFormat
deriving (Show, Eq)
parseFormat :: T.Text -> [FormatStringPart]
parseFormat t | T.null t = []
| T.null nxt = [Raw raw]
| otherwise = Raw raw : rformat
where
(raw, nxt) = T.break (== '%') t
tryNext = case parseFormat (T.tail nxt) of
(Raw nt : nxt') -> Raw (T.cons '%' nt) : nxt'
nxt' -> Raw (T.singleton '%') : nxt'
rformat = case parse format nxt of
Fail _ _ _ -> tryNext
Partial _ -> tryNext
Done remaining f -> Format f : parseFormat remaining
flag :: Parser Flag
flag = (Minus <$ char '-')
<|> (Plus <$ char '+')
<|> (Space <$ char ' ')
<|> (Zero <$ char '0')
<|> (Hash <$ char '#')
lenModifier :: Parser FLen
lenModifier = (Lhh <$ string "hh")
<|> (Lh <$ char 'h')
<|> (Lll <$ string "ll")
<|> (Ll <$ char 'l')
<|> (LL <$ char 'L')
<|> (Lz <$ char 'z')
<|> (Lj <$ char 'j')
<|> (Lt <$ char 't')
ftype :: Parser FType
ftype = (TPct <$ char '%')
<|> (Td <$ char 'd')
<|> (Td <$ char 'i')
<|> (Tu <$ char 'u')
<|> (Tf <$ char 'f')
<|> (TF <$ char 'F')
<|> (Te <$ char 'e')
<|> (TE <$ char 'E')
<|> (Tg <$ char 'g')
<|> (TG <$ char 'G')
<|> (Tx <$ char 'x')
<|> (TX <$ char 'X')
<|> (To <$ char 'o')
<|> (Ts <$ char 's')
<|> (Tc <$ char 'c')
<|> (Ta <$ char 'a')
<|> (Tp <$ char 'p')
<|> (TA <$ char 'A')
format :: Parser PrintfFormat
format = do
void $ char '%'
flags <- many flag
width <- optional decimal
prec <- optional $ do
void $ char '.'
decimal
len <- optional lenModifier
ft <- ftype
return (PrintfFormat flags width prec len ft)
sprintf :: T.Text -> [PValue] -> InterpreterMonad PValue
sprintf str oargs = PString . TL.toStrict . TB.toLazyText . mconcat <$> go (parseFormat str) oargs
where
go (Raw x : xs) args = (TB.fromText x :) <$> go xs args
go (Format f : _) _ | Hash `elem` _pfFlags f = throwPosError "sprintf: the # modifier is not supported"
go (Format f : xs) (arg : args) = do
let numeric = case arg of
PNumber n -> pure n
PString s -> maybe (throwError "sprintf: Don't know how to convert this to a number") return (text2Scientific s)
_ -> throwError "sprintf: Don't know how to convert this to a number"
flags = _pfFlags f
sh mkBuilder n | has Minus = TL.justifyLeft padlen ' ' (sprefix <> content)
| has Plus && has Zero = sprefix <> TL.justifyRight mpadlen '0' content
| has Plus = TL.justifyRight padlen ' ' (sprefix <> content)
| has Zero = TL.justifyRight padlen '0' content
| otherwise = TL.justifyRight padlen ' ' content
where
(mpadlen, sprefix) | Plus `elem` flags && n >= 0 = (padlen - 1, "+")
| Space `elem` flags && n >= 0 = (padlen - 1, " ")
| otherwise = (padlen, mempty)
padlen = maybe 0 fromIntegral (_pfWidth f)
has flg = flg `elem` flags
content = TB.toLazyText (mkBuilder n)
baseString <- case _pfType f of
Td -> sh (TB.formatScientificBuilder TB.Fixed (Just 0)) <$> numeric
Tf -> sh (TB.formatScientificBuilder TB.Fixed (_pfPrec f)) <$> numeric
TF -> sh (TB.formatScientificBuilder TB.Fixed (_pfPrec f)) <$> numeric
Tg -> sh (TB.formatScientificBuilder TB.Generic (_pfPrec f)) <$> numeric
TG -> sh (TB.formatScientificBuilder TB.Generic (_pfPrec f)) <$> numeric
Te -> sh (TB.formatScientificBuilder TB.Exponent (_pfPrec f)) <$> numeric
TE -> sh (TB.formatScientificBuilder TB.Exponent (_pfPrec f)) <$> numeric
Tx -> sh (TB.hexadecimal . (truncate :: Scientific -> Integer)) <$> numeric
TX -> sh (TB.hexadecimal . (truncate :: Scientific -> Integer)) <$> numeric
Ts -> return $ case arg of
PString s -> TL.fromStrict s
_ -> TL.pack (show (pretty arg))
_ -> throwPosError "sprintf: not yet supported"
(TB.fromLazyText baseString :) <$> go xs args
go [] [] = return []
go _ [] = throwPosError "sprintf: not enough arguments"
go [] _ = [] <$ let msg = "sprintf: too many arguments" in checkStrict msg msg