typst-0.6.1: src/Typst/Methods.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
module Typst.Methods
( getMethod,
formatNumber,
applyPureFunction
)
where
import Control.Monad (MonadPlus (mplus), foldM, void)
import Control.Monad.Reader (MonadReader (ask), MonadTrans (lift))
import qualified Data.Array as Array
import qualified Data.Foldable as F
import Data.List (intersperse, sort, sortOn)
import qualified Data.Map as M
import qualified Data.Map.Ordered as OM
import Data.Maybe (fromMaybe, listToMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V
import Text.Parsec
import Text.Parsec.String (Parser)
import Typst.Module.Standard (applyPureFunction)
import Typst.Regex
( RE (..),
RegexMatch (..),
extract,
makeRE,
match,
matchAll,
replaceRegex,
splitRegex,
)
import Typst.Types
import Typst.Util (allArgs, makeFunction, namedArg, nthArg)
import Data.Time (toGregorian, dayOfWeek, formatTime, defaultTimeLocale, UTCTime(..))
-- import Debug.Trace
getMethod ::
MonadFail m =>
(forall n. Monad n => Val -> MP n ()) ->
Val ->
Text ->
m Val
getMethod updateVal val fld = do
let methodUnimplemented name =
fail $
"Method "
<> show name
<> " is not yet implemented"
let noMethod typename name =
fail $
typename
<> " does not have a method "
<> show name
case val of
VDict m ->
case fld of
"len" ->
pure $ makeFunction $ pure $ VInteger (fromIntegral $ OM.size m)
"at" ->
pure $ makeFunction $ do
key <- nthArg 1
defval <- namedArg "default" VNone
case OM.lookup (Identifier key) m of
Nothing -> pure defval
Just v -> pure v
"insert" -> do
pure $ makeFunction $ do
key <- nthArg 1
v <- nthArg 2
lift $ updateVal $ VDict $ m OM.|> (Identifier key, v)
pure VNone
"keys" ->
pure $
makeFunction $
pure $
VArray $
V.fromList $
map (\(Identifier t, _) -> VString t) $
OM.assocs m
"values" ->
pure $ makeFunction $ pure $ VArray $ V.fromList $ map snd $ OM.assocs m
"pairs" ->
pure $ makeFunction $ do
pure $
VArray $
V.fromList $
map
( \(Identifier k, v) ->
VArray (V.fromList [VString k, v])
)
(OM.assocs m)
"remove" ->
pure $ makeFunction $ do
key <- nthArg 1
case OM.lookup (Identifier key) m of
Nothing -> pure VNone
Just oldval -> do
lift $ updateVal $ VDict $ OM.delete (Identifier key) m
pure oldval
_ -> case OM.lookup (Identifier fld) m of
Just x -> pure x
Nothing -> fail $ show (Identifier fld) <> " not found"
VColor col ->
case fld of
"darken" -> pure $ makeFunction $ do
(n :: Rational) <- nthArg 1
pure $ VColor $ case col of
RGB r g b o -> RGB (r * (1 - n)) (g * (1 - n)) (b * (1 - n)) o
CMYK c m y k -> CMYK (c * (1 - n)) (m * (1 - n)) (y * (1 - n)) (k * (1 - n))
Luma x -> Luma (x * (1 - n))
"lighten" -> pure $ makeFunction $ do
(n :: Rational) <- nthArg 1
pure $ VColor $ case col of
RGB r g b o ->
RGB
(r + ((1 - r) * n))
(g + ((1 - g) * n))
(b + ((1 - b) * n))
o
CMYK c m y k ->
CMYK
(c + ((1 - c) * n))
(m + ((1 - m) * n))
(y + ((1 - y) * n))
(k + ((1 - k) * n))
Luma x -> Luma (x + ((1 - x) * n))
"negate" -> pure $ makeFunction $ do
pure $ VColor $ case col of
RGB r g b o -> RGB (1 - r) (1 - g) (1 - b) o
CMYK c m y k -> CMYK (1 - c) (1 - m) (1 - y) k
Luma x -> Luma (1 - x)
_ -> noMethod "Color" fld
VString t -> do
let toPos n =
if n < 0
then T.length t + n
else n
case fld of
"len" ->
pure $ makeFunction $ pure $ VInteger (fromIntegral $ T.length t)
"rev" ->
pure $ makeFunction $ pure $ VString (T.reverse t)
"first" ->
if T.null t
then fail "string is empty"
else pure $ makeFunction $ pure $ VString $ T.take 1 t
"last" ->
if T.null t
then fail "string is empty"
else pure $ makeFunction $ pure $ VString $ T.takeEnd 1 t
"at" ->
pure $ makeFunction $ do
n <- toPos <$> nthArg 1
pure $ VString $ T.take 1 $ T.drop n t
"slice" ->
pure $ makeFunction $ do
start <- toPos <$> nthArg 1
mbcount <- namedArg "count" Nothing
end <- (toPos <$> nthArg 2) `mplus`
pure (maybe (T.length t) (+ start) mbcount)
if end < start
then pure $ VString ""
else pure $ VString $ T.take (end - start) $ T.drop start t
"clusters" -> pure $ makeFunction $ do
-- TODO this isn't right, but we'd need fancier libraries
-- to get at grapheme clusters
pure $ VArray $ V.fromList $ map VString $ T.chunksOf 1 t
"codepoints" -> pure $ makeFunction $ do
pure $ VArray $ V.fromList $ map VString $ T.chunksOf 1 t
"contains" -> pure $ makeFunction $ do
(patt :: RE) <- nthArg 1
pure $ VBoolean $ match patt t
"starts-with" -> pure $ makeFunction $ do
(RE reStr _) <- nthArg 1
patt <- makeRE ("^" <> reStr)
pure $ VBoolean $ match patt t
"ends-with" -> pure $ makeFunction $ do
(RE reStr _) <- nthArg 1
patt <- makeRE (reStr <> "$")
pure $ VBoolean $ match patt t
"find" -> pure $ makeFunction $ do
(patt :: RE) <- nthArg 1
pure $
let ((_, m, _) :: (Text, Text, Text)) = match patt t
in VString m
"position" -> pure $ makeFunction $ do
(patt :: RE) <- nthArg 1
pure $
let ((off, _) :: (Int, Int)) = match patt t
in VInteger (fromIntegral off)
"match" -> pure $ makeFunction $ do
(patt :: RE) <- nthArg 1
let (pre, whole, (_post :: Text), subs) = match patt t
if T.null whole
then pure VNone
else
pure $
VDict $
OM.fromList
[ ("start", VInteger (fromIntegral $ T.length pre)),
("end", VInteger (fromIntegral $ T.length pre + T.length whole)),
("text", VString whole),
("captures", VArray $ V.fromList $ map VString subs)
]
"matches" -> pure $ makeFunction $ do
(patt :: RE) <- nthArg 1
let matchToDict matchArray =
case Array.elems matchArray of
[] -> VNone
(off, len) : subs ->
let submatches = map (\(o, l) -> VString $ extract (o, l) t) subs
in VDict $
OM.fromList
[ ("start", VInteger (fromIntegral off)),
("end", VInteger (fromIntegral off + fromIntegral len)),
("text", VString $ extract (off, len) t),
("captures", VArray $ V.fromList submatches)
]
let matches = map matchToDict $ matchAll patt t
pure $ VArray $ V.fromList matches
"replace" -> pure $ makeFunction $ do
patt :: RE <- nthArg 1
(replacement :: Val) <- nthArg 2
mbCount :: Maybe Int <- namedArg "count" Nothing
case mbCount of
Just 0 -> pure $ VString t
_ ->
case replacement of
VString r ->
pure $ VString $ replaceRegex patt mbCount (const r) t
VSymbol (Symbol r _ _) ->
pure $ VString $ replaceRegex patt mbCount (const r) t
VFunction _ _ f ->
pure $
VString $
replaceRegex
patt
mbCount
( \(RegexMatch start end txt captures) ->
case applyPureFunction
f
[ VDict $
OM.fromList
[ ("start", VInteger (fromIntegral start)),
("end", VInteger (fromIntegral end)),
("text", VString txt),
("captures", VArray (V.fromList (map VString captures)))
]
] of
Success (VString s) -> s
_ -> ""
)
t
_ -> fail "replacement must be string or function"
"trim" -> pure $ makeFunction $ do
(RE patt _) <- nthArg 1 `mplus` makeRE "[[:space:]]*"
(repeated :: Bool) <- namedArg "repeat" True
(mbAt :: Maybe Val) <- namedArg "at" Nothing
let patt' =
if repeated
then "(" <> patt <> ")*"
else patt
patt'' <- case mbAt of
Just (VAlignment (Just HorizStart) _) -> makeRE $ "^" <> patt'
Just (VAlignment (Just HorizEnd) _) -> makeRE $ patt' <> "$"
Nothing -> makeRE $ "(^" <> patt' <> ")|(" <> patt' <> "$)"
_ -> fail "'at' expected either 'start' or 'end'"
pure $ VString $ replaceRegex patt'' Nothing (const mempty) t
"split" -> pure $ makeFunction $ do
arg <- nthArg 1
case arg of
VString "" ->
pure $ VArray $ V.fromList $ map VString $ "" : T.chunksOf 1 t ++ [""]
VString patt -> pure $ VArray $ V.fromList $ map VString $ T.splitOn patt t
VRegex patt ->
pure $
VArray $
V.fromList $
map VString $
splitRegex patt t
_ ->
-- defaults to split on whitespace
pure $ VArray $ V.fromList $ map VString $ T.words t
_ -> noMethod "String" fld
VCounter key ->
case fld of
"display" -> pure $ makeFunction $ do
mbnum <- M.lookup key . evalCounters <$> lift getState
maybe (fail "counter not defined") (pure . VInteger) mbnum
"step" -> pure $ makeFunction $ do
lift $ updateState $ \st ->
st {evalCounters = M.adjust (+ 1) key $ evalCounters st}
pure VNone
"update" -> pure $ makeFunction $ do
mbnum <- M.lookup key . evalCounters <$> lift getState
case mbnum of
Nothing -> fail "counter not defined"
Just num -> do
newval <- nthArg 1
(newnum :: Integer) <-
case newval of
VFunction _ _ fn ->
case applyPureFunction fn [VInteger num] of
Failure e -> fail e
Success v -> fromVal v
_ -> fromVal newval
lift $ updateState $ \st ->
st {evalCounters = M.adjust (const newnum) key $ evalCounters st}
pure VNone
"at" -> methodUnimplemented fld
"final" -> methodUnimplemented fld
_ -> noMethod "Counter" fld
VContent cs ->
case fld of
"func" -> pure $ makeFunction $ do
case F.toList cs of
[Elt name _ _] -> lift $ lookupIdentifier name
[Txt _] -> lift $ lookupIdentifier "text"
_ -> pure $ makeFunction $ do
xs <- allArgs
pure $ VContent $ foldMap valToContent xs
"has" -> pure $ makeFunction $ do
f <- nthArg 1
case F.toList cs of
[Elt _ _ fields] -> do
case M.lookup (Identifier f) fields of
Just _ -> pure $ VBoolean True
Nothing -> pure $ VBoolean False
_ | f == "children" -> pure $ VBoolean True
_ ->
fail $
"Content is not a single element: "
<> T.unpack (repr (VContent cs))
"at" -> pure $ makeFunction $ do
(field :: Text) <- ask >>= getPositionalArg 1 >>= fromVal
defval <- namedArg "default" VNone
case F.toList cs of
[Elt _ _ fields] ->
case M.lookup (Identifier field) fields of
Just v -> pure v
Nothing -> pure defval
_ -> pure defval
"location" -> methodUnimplemented fld
"text" ->
case F.toList cs of
[Txt t] -> pure $ VString t
[Elt "text" _ [("body", VContent [Txt t])]] -> pure $ VString t
[Elt _ _ fields]
| Just x <- M.lookup "text" fields -> pure x
_ -> fail "Content is not a single text element"
"fields" -> pure $ makeFunction $ do
VDict <$>
case F.toList cs of
(Elt _ _ fields:_) -> pure $ OM.fromList $ M.toList fields
_ -> pure OM.empty
_ ->
let childrenOrFallback =
if fld == "children"
then
pure $
VArray $
V.fromList $
map (\x -> VContent [x]) $
F.toList cs
else noMethod "Content" fld
in case cs of
[Elt _name _ fields] ->
maybe childrenOrFallback pure $ M.lookup (Identifier fld) fields
_ -> childrenOrFallback
VTermItem t d ->
case fld of
"term" -> pure $ VContent t
"description" -> pure $ VContent d
_ -> noMethod "TermItem" fld
VVersion xs ->
case fld of
"at" -> pure $ makeFunction $ do
i <- nthArg 1
pure $ VInteger $ fromMaybe 0 $ listToMaybe $ drop i xs
_ -> noMethod "Version" fld
VArray v -> do
let toPos n =
if n < 0
then V.length v + n
else n
case fld of
"len" ->
pure $ makeFunction $ pure $ VInteger (fromIntegral $ V.length v)
"first" ->
pure $
makeFunction $
if V.null v
then fail "empty array"
else pure $ V.head v
"last" ->
pure $
makeFunction $
if V.null v
then fail "empty array"
else pure $ V.last v
"at" -> pure $ makeFunction $ do
pos <- toPos <$> nthArg 1
defval <- namedArg "default" VNone
pure $ fromMaybe defval $ v V.!? pos
"push" -> pure $ makeFunction $ do
x <- nthArg 1
lift $ updateVal $ VArray $ V.snoc v x
pure VNone
"pop" ->
pure $
makeFunction $
if V.null v
then fail "empty array"
else do
lift $ updateVal $ VArray $ V.init v
pure $ V.last v
"slice" -> pure $ makeFunction $ do
start <- toPos <$> nthArg 1
mbcount <- namedArg "count" Nothing
end <- (toPos <$> nthArg 2) `mplus`
pure (maybe (V.length v) (+ start) mbcount)
if V.length v < end
then fail "array contains insufficient elements for slice"
else
if end < start
then pure $ VArray mempty
else pure $ VArray $ V.slice start (end - start) v
"split" -> pure $ makeFunction $ do
spliton <- nthArg 1
let go v' = case V.break (== spliton) v' of
(a, b) | V.null b -> [VArray a | not (V.null a)]
(a, b) -> VArray a : go (V.drop 1 b)
pure $ VArray $ V.fromList $ go v
"intersperse" -> pure $ makeFunction $ do
sep <- nthArg 1
pure $ VArray . V.fromList . intersperse sep . V.toList $ v
"dedup" -> pure $ makeFunction $ do
pure $ VArray $ deduplicateVector v
"insert" -> pure $ makeFunction $ do
pos <- toPos <$> nthArg 1
newval <- nthArg 2
if pos >= V.length v || pos < 0
then fail "insert position out of bounds in array"
else do
lift $ updateVal $ VArray $ V.snoc (V.take pos v) newval <> V.drop pos v
pure VNone
"remove" -> pure $ makeFunction $ do
pos <- toPos <$> nthArg 1
if pos >= V.length v || pos < 0
then fail "remove position out of bounds in array"
else do
lift $ updateVal $ VArray $ V.take pos v <> V.drop (pos + 1) v
pure $ fromMaybe VNone $ v V.!? pos
"contains" -> pure $ makeFunction $ do
item <- nthArg 1
pure $ VBoolean $ V.elem item v
"find" -> pure $ makeFunction $ do
Function fn <- nthArg 1
let go Nothing y = do
res <- lift $ fn Arguments {positional = [y], named = OM.empty}
case res of
VBoolean True -> pure $ Just y
VBoolean False -> pure Nothing
_ -> fail "function does not return a boolean"
go (Just z) _ = pure $ Just z
res <- foldM go Nothing v
case res of
Just z -> pure z
Nothing -> pure VNone
"position" -> pure $ makeFunction $ do
Function fn <- nthArg 1
let go (Left i) y = do
res <- lift $ fn Arguments {positional = [y], named = OM.empty}
case res of
VBoolean True -> pure $ Right i
VBoolean False -> pure $ Left (i + 1)
_ -> fail "function does not return a boolean"
go (Right i) _ = pure $ Right i
res <- foldM go (Left 0) v
case res of
Right i -> pure $ VInteger i
Left _ -> pure VNone
"filter" -> pure $ makeFunction $ do
Function fn <- nthArg 1
let predicate y = do
res <- lift $ fn Arguments {positional = [y], named = OM.empty}
case res of
VBoolean True -> pure True
VBoolean False -> pure False
_ -> fail "function does not return a boolean"
VArray <$> V.filterM predicate v
"map" -> pure $ makeFunction $ do
Function fn <- nthArg 1
let f y = lift $ fn Arguments {positional = [y], named = OM.empty}
VArray <$> V.mapM f v
"flatten" ->
pure $
makeFunction $
pure $
VArray $
V.concat [v' | VArray v' <- V.toList v]
"enumerate" ->
pure $
makeFunction $
pure $
VArray $
V.zipWith
(\x y -> VArray [x, y])
(V.map VInteger [0 .. (fromIntegral $ V.length v)])
v
"fold" -> pure $ makeFunction $ do
(start :: Val) <- nthArg 1
Function fn <- nthArg 2
let f acc y = fn Arguments {positional = [acc, y], named = OM.empty}
lift $ foldM f start $ V.toList v
"reduce" -> pure $ makeFunction $ do
Function fn <- nthArg 1
let f acc y = fn Arguments {positional = [acc, y], named = OM.empty}
case V.toList v of
[] -> pure VNone
(x:xs) -> lift $ foldM f x xs
"any" -> pure $ makeFunction $ do
Function fn <- nthArg 1
let predicate y = do
res <- lift $ fn Arguments {positional = [y], named = OM.empty}
case res of
VBoolean True -> pure True
VBoolean False -> pure False
_ -> fail "function not return a boolean"
(VBoolean . V.any id) <$> mapM predicate v
"all" -> pure $ makeFunction $ do
Function fn <- nthArg 1
let predicate y = do
res <- lift $ fn Arguments {positional = [y], named = OM.empty}
case res of
VBoolean True -> pure True
VBoolean False -> pure False
_ -> fail "function not return a boolean"
(VBoolean . V.all id) <$> mapM predicate v
"rev" -> pure $ makeFunction $ pure $ VArray $ V.reverse v
"join" -> pure $ makeFunction $ do
separator <- nthArg 1
lastsep <- namedArg "last" separator
let xs' = F.toList v
let xs = case xs' of
[] -> []
_ -> intersperse separator (init xs') ++ [lastsep, last xs']
foldM joinVals VNone xs
"sorted" -> pure $ makeFunction $ do
(mbKeyFn :: Maybe Function) <- namedArg "key" Nothing
case mbKeyFn of
Nothing -> pure $ VArray $ V.fromList $ sort $ V.toList v
Just (Function kf) -> do
let kf' x = lift $ kf Arguments {positional = [x], named = OM.empty}
VArray . V.fromList . map fst . sortOn snd
<$> (mapM (\x -> (x,) <$> kf' x) (V.toList v))
"zip" -> pure $ makeFunction $ do
(xs :: [Val]) <- positional <$> ask
let len = V.length v
pure $ VArray $ V.filter (/= VNone) $
V.map (\i -> maybe VNone (VArray . V.fromList)
(mapM (\x ->
case x of
VArray v' -> v' V.!? i
_ -> Nothing) (val : xs)))
(V.enumFromTo 0 (len - 1))
"to-dict" -> pure $ makeFunction $
VDict . OM.fromList <$>
mapM (\x -> do
vx <- fromVal x
case V.toList vx of
[a,b] -> do
k <- fromVal a
pure (Identifier k, b)
_ -> fail "vector has wrong shape") (V.toList v)
"windows" -> pure $ makeFunction $ do
(windowsize :: Int) <- nthArg 1
case V.length v - windowsize of
n | n < 0 -> pure $ VArray mempty
| otherwise -> pure $ VArray $ V.fromList $
map (\x -> VArray $ V.take windowsize $ V.drop x v) [0..n]
"sum" -> pure $ makeFunction $ do
mbv <- namedArg "default" Nothing
case V.uncons v of
Nothing ->
maybe
(fail "sum of empty array with no default value")
pure
mbv
Just (h, rest) ->
pure $
fromMaybe VNone $
V.foldl
( \mbsum x -> case mbsum of
Nothing -> Nothing
Just y -> maybePlus y x
)
(Just h)
rest
"product" -> pure $ makeFunction $ do
mbv <- namedArg "default" Nothing
case V.uncons v of
Nothing ->
maybe
(fail "product of empty array with no default value")
pure
mbv
Just (h, rest) ->
pure $
fromMaybe VNone $
V.foldl
( \mbsum x -> case mbsum of
Nothing -> Nothing
Just y -> maybeTimes y x
)
(Just h)
rest
_ -> noMethod "Array" fld
VFunction mbName scope (Function f) ->
case fld of
"with" -> pure $ makeFunction $ do
args <- ask
pure $
VFunction mbName scope $
Function $
\args' -> f (args <> args')
"where" -> pure $ makeFunction $ do
args <- ask
case mbName of
Nothing -> fail "function is not an element function"
Just name ->
pure $
VSelector $
SelectElement name (OM.assocs (named args))
_ -> noMethod "Function" fld
VSelector sel ->
case fld of
"or" -> pure $ makeFunction $ do
(other :: Selector) <- nthArg 1
pure $ VSelector $ SelectOr other sel
"and" -> pure $ makeFunction $ do
(other :: Selector) <- nthArg 1
pure $ VSelector $ SelectAnd other sel
"before" -> pure $ makeFunction $ do
(other :: Selector) <- nthArg 1
pure $ VSelector $ SelectBefore other sel
"after" -> pure $ makeFunction $ do
(other :: Selector) <- nthArg 1
pure $ VSelector $ SelectAfter other sel
_ -> noMethod "Selector" fld
VArguments args ->
case fld of
"pos" -> pure $ makeFunction $ pure $ VArray $ V.fromList (positional args)
"at" ->
pure $ makeFunction $ do
(x :: Val) <- nthArg 1
defval <- namedArg "default" VNone
case x of
VInteger{} -> do
i <- fromVal x
case positional args of
xs | i < length xs -> pure $ xs !! i
| otherwise -> pure defval
VString t ->
case OM.lookup (Identifier t) (named args) of
Just a -> pure a
Nothing -> pure defval
_ -> pure defval
"named" -> pure $ makeFunction $ pure $ VDict $ named args
_ -> noMethod "Arguments" fld
VDateTime mbdate mbtime -> do
let toSeconds = (floor :: Double -> Integer) . realToFrac
case fld of
"year" -> pure $ makeFunction $
pure $ case toGregorian <$> mbdate of
Nothing -> VNone
Just (y,_,_) -> VInteger (fromIntegral y)
"month" -> pure $ makeFunction $
pure $ case toGregorian <$> mbdate of
Nothing -> VNone
Just (_,m,_) -> VInteger (fromIntegral m)
"day" -> pure $ makeFunction $
pure $ case toGregorian <$> mbdate of
Nothing -> VNone
Just (_,_,d) -> VInteger (fromIntegral d)
"weekday" -> pure $ makeFunction $
pure $ case dayOfWeek <$> mbdate of
Nothing -> VNone
Just d-> VInteger (fromIntegral $ fromEnum d)
"hour" -> pure $ makeFunction $
pure $ case toSeconds <$> mbtime of
Nothing -> VNone
Just t -> VInteger $ t `div` 3600
"minute" -> pure $ makeFunction $
pure $ case toSeconds <$> mbtime of
Nothing -> VNone
Just t -> VInteger $ (t `mod` 3600) `div` 60
"second" -> pure $ makeFunction $
pure $ case toSeconds <$> mbtime of
Nothing -> VNone
Just t -> VInteger $ t `mod` 60
"display" -> pure $ makeFunction $ do
mbfmt <- nthArg 1 `mplus` pure Nothing
mbformat <- case mbfmt of
Nothing -> pure Nothing
Just fmt ->
case toTimeFormat <$> parseDisplayFormat fmt of
Left e -> fail $ "Could not parse display format: " <> show e
Right f -> pure $ Just f
pure $ VString $ T.pack $
case (mbdate, mbtime) of
(Nothing, Just t) -> formatTime defaultTimeLocale (fromMaybe "%X" mbformat) t
(Just d, Nothing) -> formatTime defaultTimeLocale (fromMaybe "%F" mbformat) d
(Nothing, Nothing) -> ""
(Just d, Just t) -> formatTime defaultTimeLocale (fromMaybe "%X %F" mbformat)
(UTCTime d t)
_ -> noMethod "DateTime" fld
_ -> noMethod (drop 1 $ takeWhile (/= ' ') $ show val) fld
formatNumber :: Text -> Int -> Text
formatNumber t n = F.foldMap go $ T.unpack t
where
go '1' | n >= 0 = T.pack (show n)
go 'a' | n >= 1 = T.singleton $ cycle ['a' .. 'z'] !! (n - 1 `mod` 26)
go 'A' | n >= 1 = T.singleton $ cycle ['A' .. 'Z'] !! (n - 1 `mod` 26)
go 'i' | n >= 1 = T.toLower $ toRomanNumeral n
go 'I' | n >= 1 = toRomanNumeral n
go 'い' | n >= 1 = T.pack (show n) -- TODO
go 'イ' | n >= 1 = T.pack (show n) -- TODO
go 'א' | n >= 1 = T.pack (show n) -- TODO
go '*'
| n >= 1 =
T.singleton $ cycle ['*', '†', '‡', '§', '¶', '‖'] !! (n - 1 `mod` 6)
| otherwise = "-"
go c = T.singleton c
toRomanNumeral :: Int -> T.Text
toRomanNumeral x
| x >= 4000 || x < 0 = "?"
| x >= 1000 = "M" <> toRomanNumeral (x - 1000)
| x >= 900 = "CM" <> toRomanNumeral (x - 900)
| x >= 500 = "D" <> toRomanNumeral (x - 500)
| x >= 400 = "CD" <> toRomanNumeral (x - 400)
| x >= 100 = "C" <> toRomanNumeral (x - 100)
| x >= 90 = "XC" <> toRomanNumeral (x - 90)
| x >= 50 = "L" <> toRomanNumeral (x - 50)
| x >= 40 = "XL" <> toRomanNumeral (x - 40)
| x >= 10 = "X" <> toRomanNumeral (x - 10)
| x == 9 = "IX"
| x >= 5 = "V" <> toRomanNumeral (x - 5)
| x == 4 = "IV"
| x >= 1 = "I" <> toRomanNumeral (x - 1)
| otherwise = ""
-- parser for DateTime display format
data FormatPart =
Literal String
| Variable String [(String, String)]
deriving Show
parseDisplayFormat :: String -> Either ParseError [FormatPart]
parseDisplayFormat = parse (many pFormatPart <* eof) ""
pFormatPart :: Parser FormatPart
pFormatPart = pVariable <|> pLiteral
pLiteral :: Parser FormatPart
pLiteral = Literal <$> many1 (satisfy (/='['))
pVariable :: Parser FormatPart
pVariable = do
void $ char '['
name <- many1 letter
spaces
modifiers <- many pModifier
void $ char ']'
pure $ Variable name modifiers
pModifier :: Parser (String, String)
pModifier = do
name <- many1 letter
void $ char ':'
spaces
val <- many1 alphaNum
spaces
pure (name, val)
-- convert formatparts into Data.Time format string
toTimeFormat :: [FormatPart] -> String
toTimeFormat = concatMap toTimeFormatPart
toTimeFormatPart :: FormatPart -> String
toTimeFormatPart (Literal s) = foldr esc "" s
where
esc '%' = ("%%" ++)
esc '\t' = ("%t" ++)
esc '\n' = ("%n" ++)
esc c = (c:)
toTimeFormatPart (Variable "year" mods) =
withPadding mods $
case lookup "repr" mods of
Just "last_two" -> "y"
_ -> "Y"
toTimeFormatPart (Variable "month" mods) =
withPadding mods $
case lookup "repr" mods of
Just "numerical" -> "%m"
Just "long" -> "b"
Just "short" -> "h"
_ -> "m"
toTimeFormatPart (Variable "day" mods) =
case lookup "padding" mods of
Just "space" -> "%e"
Just "zero" -> "%d"
_ -> "%e"
toTimeFormatPart (Variable "week_number" mods) =
withPadding mods $
case lookup "repr" mods of
Just "ISO" -> "V"
Just "sunday" -> "U"
Just "monday" -> "W"
_ -> "V"
toTimeFormatPart (Variable "weekday" mods) =
withPadding mods $
case lookup "repr" mods of
Just "long" -> "A"
Just "short" -> "a"
Just "sunday" -> "w"
Just "monday" -> "u"
_ -> ""
toTimeFormatPart (Variable "hour" mods) =
case lookup "hour" mods of
Just "24" | lookup "padding" mods == Just "zero" -> "%H"
| otherwise -> "%k"
Just "12" | lookup "padding" mods == Just "zero" -> "%I"
| otherwise -> "%l"
_ -> "%k"
toTimeFormatPart (Variable "period" mods) =
case lookup "case" mods of
Just "lower" -> "%P"
_ -> "%p"
toTimeFormatPart (Variable "minute" _) = "%M"
toTimeFormatPart (Variable "second" _) = "%S"
toTimeFormatPart _ = "?"
withPadding :: [(String, String)] -> String -> String
withPadding mods s = '%' :
case lookup "padding" mods of
Just "zero" -> '0' : s
Just "space" -> '_' : s
_ -> s
deduplicateVector :: Eq a => V.Vector a -> V.Vector a
deduplicateVector =
V.foldl' (\acc x -> if x `V.elem` acc then acc else acc `V.snoc` x) mempty