typst-0.10: app/Main.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Monad (when)
import qualified Data.ByteString as BS
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.IO as TIO
import Options.Applicative
import System.Directory (doesFileExist)
import System.Environment (lookupEnv)
import System.Exit
import System.IO (hPutStrLn, stderr)
import System.Timeout (timeout)
import Text.Show.Pretty (pPrint)
import Typst (evaluateTypst, parseTypst)
import Typst.Types (Val (..), repr, Operations(..))
import Data.Time (getCurrentTime)
import qualified Data.ByteString as B
data Opts = Opts
{ optShowParse :: Bool,
optShowEval :: Bool,
optShowRepr :: Bool,
optTimeout :: Maybe Int,
optInputs :: [(Text, Text)],
optFile :: Maybe FilePath
}
deriving (Show, Eq)
err :: String -> IO a
err msg = do
hPutStrLn stderr msg
exitWith (ExitFailure 1)
optsParser :: Parser Opts
optsParser =
Opts
<$> switch (long "parse" <> help "Show parse tree")
<*> switch (long "eval" <> help "Show evaluated result")
<*> switch (long "repr" <> help "Show repr output")
<*> optional (option auto (long "timeout" <> metavar "MS" <> help "Timeout in milliseconds"))
<*> many (option (eitherReader parseInput)
(long "input" <> metavar "KEY=VALUE" <> help "Set an input key-value pair (repeatable)"))
<*> optional (argument str (metavar "FILE" <> help "Input file (reads stdin if omitted)"))
parseInput :: String -> Either String (Text, Text)
parseInput s =
case break (== '=') s of
(_, []) -> Left "Expected KEY=VALUE format"
(k, '=' : v) -> Right (T.pack k, T.pack (unquote v))
_ -> Left "Expected KEY=VALUE format"
where
unquote ('"' : rest)
| not (null rest) && last rest == '"' = init rest
unquote ('\'' : rest)
| not (null rest) && last rest == '\'' = init rest
unquote x = x
operations :: Operations IO
operations = Operations
{ loadBytes = BS.readFile
, currentUTCTime = getCurrentTime
, lookupEnvVar = lookupEnv
, checkExistence = doesFileExist
}
main :: IO ()
main =
() <$ do
opts <- execParser (info (optsParser <**> helper)
(fullDesc <> progDesc "Parse and evaluate Typst documents"))
let mbfile = optFile opts
let showAll = case opts of
Opts False False False _ _ _ -> True
_ -> False
( case optTimeout opts of
Nothing -> fmap Just
Just ms -> timeout (ms * 1000)
)
$ do
bs <- maybe B.getContents B.readFile mbfile
let t = TE.decodeUtf8 bs
case parseTypst (fromMaybe "stdin" mbfile) t of
Left e -> err $ show e
Right parseResult -> do
when (optShowParse opts || showAll) $ do
when showAll $ putStrLn "--- parse tree ---"
pPrint parseResult
result <- evaluateTypst operations (optInputs opts) "stdin" parseResult
case result of
Left e -> err $ show e
Right c -> do
when (optShowEval opts || showAll) $ do
when showAll $ putStrLn "--- evaluated ---"
pPrint c
when (optShowRepr opts || showAll) $ do
when showAll $ putStrLn "--- repr ---"
TIO.putStrLn $ repr $ VContent [c]
exitSuccess