module Text.Format (
FormattedPart(..), Formatted(..), withFlags,
FormatArg(..), Format(..), Formatter(..),
prebuild, build,
Formattable(..), Hole(..), fmt, FormatResult(..),
format, formats, (~~), (~%),
module Text.Format.Flags
) where
import Prelude.Unicode
import Control.Applicative
import Data.Char (intToDigit)
import Data.List (find, intercalate, nub)
import Data.Maybe (fromMaybe, listToMaybe)
import qualified Data.Text as T
import Data.Text.Lazy (Text, unpack)
import Data.String
import Numeric
import Text.Read (readMaybe)
import Text.ParserCombinators.ReadP
import Text.Format.Flags
data FormattedPart = FormattedPart {
formattedFlags ∷ FormatFlags,
formattedValue ∷ String }
deriving (Eq, Ord, Show)
instance IsString FormattedPart where
fromString = FormattedPart [] ∘ fromString
newtype Formatted = Formatted { formattedParts ∷ [FormattedPart] } deriving (Eq, Ord, Show)
instance IsString Formatted where
fromString = Formatted ∘ return ∘ fromString
instance Monoid Formatted where
mempty = Formatted []
Formatted l `mappend` Formatted r = Formatted $ l ++ r
withFlags ∷ String → [String] → Formatted
withFlags v fs = Formatted [FormattedPart fs v]
data FormatArg = FormatNamed String ([String] → Formatted) | FormatPos ([String] → Formatted)
data Format = Format {
formatString ∷ String,
formatArgs ∷ [FormatArg] }
instance Show Format where
show = mconcat ∘ map formattedValue ∘ formattedParts ∘ prebuild
instance IsString Format where
fromString str = Format str []
data Formatter = Formatter {
formatter ∷ Either String Int,
formatterDefault ∷ Maybe String,
formatterFlags ∷ [String] }
instance Show Formatter where
show (Formatter f def cfgs) = "{" ++ concat parts ++ "}" where
parts = [either id show f, fromMaybe "" (fmap ('=':) def), if null cfgs then "" else (':' : intercalate "," cfgs)]
instance Read Formatter where
readsPrec _ = readP_to_S $ between (char '{') (char '}') $ do
n ← munch (∉ "=:}")
v ← option Nothing $ do
_ ← char '='
v' ← munch1 (∉ ":}")
return $ Just v'
cs ← option [] $ do
_ ← char ':'
flip sepBy (char ',') (munch1 (∉ ",}"))
return $ Formatter (maybe (Left n) Right $ readMaybe n) v cs
prebuild ∷ Format → Formatted
prebuild = buildFormat True
build ∷ Format → Formatted
build = buildFormat False
buildFormat ∷ Bool → Format → Formatted
buildFormat pre fstr = build' 0 fstr where
build' ∷ Int → Format → Formatted
build' _ (Format "" _) = mempty
build' i (Format ('{':'{':fstr') args) = fromString "{" `mappend` build' i (Format fstr' args)
build' i (Format ('}':'}':fstr') args) = fromString "}" `mappend` build' i (Format fstr' args)
build' i (Format ('{':'}':fstr') args) = formatArg' (Formatter (Right i) Nothing []) args `mappend` build' (succ i) (Format fstr' args)
build' i (Format ('{':fstr') args) = case reads ('{':fstr') of
[] → error $ "Can't parse formatter at " ++ fstr'
(f, fstr''):_ → formatArg' f args `mappend` build' i (Format fstr'' args)
build' i (Format fstr' args) = fromString s `mappend` build' i (Format fstr'' args) where
(s, fstr'') = break (∈ "{}") fstr'
formatArg' ∷ Formatter → [FormatArg] → Formatted
formatArg' f@(Formatter (Left name) defVal fmtCfgs) args
| pre = fromMaybe (formatted f fmtCfgs) lookArg
| otherwise = fromMaybe (error $ "Argument " ++ name ++ " not set") (lookArg <|> fmap (flip formatted fmtCfgs) defVal)
where
lookArg = do
FormatNamed _ fval ← find byName args
return $ fval fmtCfgs
byName (FormatNamed n _) = n ≡ name
byName _ = False
formatArg' f@(Formatter (Right i) defVal fmtCfgs) args
| pre = fromMaybe (formatted f fmtCfgs) lookIdx
| otherwise = fromMaybe (error $ "Argument at index " ++ show i ++ " not set") (lookIdx <|> fmap (flip formatted fmtCfgs) defVal)
where
lookIdx = do
FormatPos fval ← listToMaybe $ drop i $ filter isPos args
return $ fval fmtCfgs
isPos (FormatPos _) = True
isPos _ = False
class Formattable a where
formattable ∷ a → FormatFlags → Formatted
default formattable ∷ Show a ⇒ a → FormatFlags → Formatted
formattable x _ = fromString ∘ show $ x
formatted ∷ Formattable a ⇒ a → FormatFlags → Formatted
formatted v fmts = Formatted ∘ map addFmts ∘ formattedParts ∘ formattable v $ fmts where
addFmts (FormattedPart flags' v') = FormattedPart (nub $ fmts ++ flags') v'
instance Formattable String where
formattable s _ = fromString s
instance Formattable Char where
formattable ch _ = fromString [ch]
instance Formattable Int where
formattable i fmts = fromString ∘ formatInt (baseFlag fmts) $ i
instance Formattable Integer where
formattable i fmts = fromString ∘ formatInt (baseFlag fmts) $ i
instance Formattable Double where
formattable d fmts = fromString ∘ formatDouble (preciseFlag fmts) $ d
instance Formattable Float where
formattable f fmts = fromString ∘ formatDouble (preciseFlag fmts) $ f
instance Formattable Bool
instance Formattable Text where
formattable s _ = fromString ∘ unpack $ s
instance Formattable T.Text where
formattable s _ = fromString ∘ T.unpack $ s
instance Formattable Formatter where
formattable s _ = fromString ∘ show $ s
class Hole a where
hole ∷ a → [FormatArg]
instance Hole Formatted where
hole v = [FormatPos $ const v]
instance Hole FormatArg where
hole = return
instance Hole [FormatArg] where
hole = id
instance Hole [[FormatArg]] where
hole = concat
instance Formattable a ⇒ Hole a where
hole v = [FormatPos $ formatted v]
fmt ∷ Formattable a ⇒ a → FormatArg
fmt v = FormatPos $ formatted v
class FormatResult r where
formatResult ∷ Format → r
instance FormatResult Format where
formatResult = id
instance FormatResult String where
formatResult = mconcat ∘ map formattedValue ∘ formattedParts ∘ build
instance IsString s ⇒ FormatResult s where
formatResult = fromString ∘ formatResult
instance FormatResult Formatted where
formatResult = build
format ∷ FormatResult r ⇒ String → r
format = formatResult ∘ fromString
formats ∷ FormatResult r ⇒ String → [FormatArg] → r
formats f = formatResult ∘ Format f
infixl 7 ~~
(~~) ∷ (Hole a, FormatResult r) ⇒ Format → a → r
fstr ~~ arg = formatResult $ fstr { formatArgs = formatArgs fstr ++ hole arg }
infixr 8 ~%
(~%) ∷ Formattable a ⇒ String → a → FormatArg
name ~% value = FormatNamed name (formatted value)
formatInt ∷ (Show a, Integral a) ⇒ a → a → String
formatInt base v = showIntAtBase base intToDigit v ""
formatDouble ∷ RealFloat a ⇒ Maybe Int → a → String
formatDouble p v = showGFloat p v ""