1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Network.Wai.Handler.Warp
import Network.Wai.Logger (withStdoutLogger)
import Data.Aeson
import Data.Aeson.TH
import Data.Maybe (fromMaybe)
import Network.Wai
import Servant
import Text.TeXMath
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
import Text.XML.Light (ppElement)
import Options.Applicative
import Safe (readMay)
-- This is the data to be supplied by the JSON payload
-- of requests.
data Params = Params
{ text :: Text
, from :: Format
, to :: Format
, display :: Bool
} deriving (Show)
data Format =
TeX | MathML | Eqn | OMML | Typst
deriving (Show, Ord, Eq)
instance FromJSON Format where
parseJSON (String s) =
case T.toLower s of
"tex" -> pure TeX
"mathml" -> pure MathML
"eqn" -> pure Eqn
"typst" -> pure Typst
"omml" -> pure OMML
_ -> fail $ "Unknown format " <> T.unpack s
parseJSON _ = fail "Expecting string format"
instance ToJSON Format where
toJSON x = String $ T.toLower $ T.pack $ show x
instance FromHttpApiData Format where
parseQueryParam t =
case T.toLower t of
"tex" -> pure TeX
"mathml" -> pure MathML
"eqn" -> pure Eqn
"typst" -> pure Typst
"omml" -> pure OMML
_ -> Left $ "Unknown format " <> t
-- Automatically derive code to convert to/from JSON.
$(deriveJSON defaultOptions ''Params)
data Opts = Opts
{ port :: Int }
optsSpec :: Parser Opts
optsSpec = Opts
<$> option (maybeReader readMay)
( long "port"
<> short 'p'
<> metavar "NUMBER"
<> showDefault
<> value 8080
<> help "Port on which to run the server" )
main :: IO ()
main = do
let options = info (optsSpec <**> helper)
( fullDesc
<> progDesc "Run a server for texmath"
<> header "texmath-server - an HTTP server for texmath" )
opts <- execParser options
putStrLn $ "Starting server on port " <> show (port opts)
withStdoutLogger $ \logger -> do
let settings = setPort (port opts) $ setLogger logger defaultSettings
runSettings settings app
-- This is the API. The "/convert" endpoint takes a request body
-- consisting of a JSON-encoded Params structure and responds to
-- Get requests with either plain text or JSON, depending on the
-- Accept header.
type API =
"convert" :> ReqBody '[JSON] Params :> Post '[PlainText, JSON] Text
:<|>
"convert" :> QueryParam "text" Text :> QueryParam "from" Format :> QueryParam "to" Format :> QueryFlag "display" :> Get '[PlainText] Text
:<|>
"convert-batch" :> ReqBody '[JSON] [Params] :> Post '[JSON] [Text]
app :: Application
app = serve api server
api :: Proxy API
api = Proxy
server :: Server API
server = convert
:<|> (\text' from' to' display' ->
convert Params{ text = fromMaybe "" text',
from = fromMaybe TeX from',
to = fromMaybe MathML to',
display = display' })
:<|> mapM convert
where
convert params
= let dt = if display params
then DisplayBlock
else DisplayInline
txt = text params
reader = case from params of
OMML -> readOMML
TeX -> readTeX
MathML -> readMathML
Eqn -> \_ -> Left "eqn reader not implemented"
Typst -> \_ -> Left "typst reader not implemented"
writer = case to params of
Eqn -> writeEqn dt
Typst -> writeTypst dt
OMML -> T.pack . ppElement . writeOMML dt
TeX -> writeTeX
MathML -> T.pack . ppElement . writeMathML dt
in handleErr $ writer <$> reader txt
handleErr (Right t) = return t
handleErr (Left err) = throwError $
err500 { errBody = TLE.encodeUtf8 $ TL.fromStrict err }
|