107 lines
3.7 KiB
Haskell
107 lines
3.7 KiB
Haskell
|
{-# LANGUAGE DataKinds #-}
|
||
|
{-# LANGUAGE TemplateHaskell #-}
|
||
|
{-# LANGUAGE TypeOperators #-}
|
||
|
{-# LANGUAGE FlexibleContexts #-}
|
||
|
{-# LANGUAGE OverloadedStrings #-}
|
||
|
module PandocCGI
|
||
|
( app
|
||
|
, Params(..)
|
||
|
) where
|
||
|
|
||
|
import Data.Aeson
|
||
|
import Data.Aeson.TH
|
||
|
import Network.Wai
|
||
|
import Servant
|
||
|
import Text.Pandoc
|
||
|
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 Data.Maybe (fromMaybe)
|
||
|
import Data.Char (isAlphaNum)
|
||
|
|
||
|
-- This is the data to be supplied by the JSON payload
|
||
|
-- of requests. Maybe values may be omitted and will be
|
||
|
-- given default values.
|
||
|
data Params = Params
|
||
|
{ text :: Text
|
||
|
, from :: Maybe Text
|
||
|
, to :: Maybe Text
|
||
|
, wrapText :: Maybe WrapOption
|
||
|
, columns :: Maybe Int
|
||
|
, standalone :: Maybe Bool
|
||
|
, template :: Maybe Text
|
||
|
} deriving (Show)
|
||
|
|
||
|
-- Automatically derive code to convert to/from JSON.
|
||
|
$(deriveJSON defaultOptions ''Params)
|
||
|
|
||
|
-- 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-batch" :> ReqBody '[JSON] [Params] :> Post '[JSON] [Text]
|
||
|
|
||
|
app :: Application
|
||
|
app = serve api server
|
||
|
|
||
|
api :: Proxy API
|
||
|
api = Proxy
|
||
|
|
||
|
server :: Server API
|
||
|
server = convert
|
||
|
:<|> mapM convert
|
||
|
where
|
||
|
-- We use runPure for the pandoc conversions, which ensures that
|
||
|
-- they will do no IO. This makes the server safe to use. However,
|
||
|
-- it will mean that features requiring IO, like RST includes, will not work.
|
||
|
-- Changing this to
|
||
|
-- handleErr =<< liftIO (runIO (convert' params))
|
||
|
-- will allow the IO operations.
|
||
|
convert params = handleErr $ runPure (convert' params)
|
||
|
|
||
|
convert' :: PandocMonad m => Params -> m Text
|
||
|
convert' params = do
|
||
|
let readerFormat = fromMaybe "markdown" $ from params
|
||
|
let writerFormat = fromMaybe "html" $ to params
|
||
|
(readerSpec, readerExts) <- getReader readerFormat
|
||
|
(writerSpec, writerExts) <- getWriter writerFormat
|
||
|
let isStandalone = fromMaybe False (standalone params)
|
||
|
let toformat = T.toLower $ T.takeWhile isAlphaNum $ writerFormat
|
||
|
mbTemplate <- if isStandalone
|
||
|
then case template params of
|
||
|
Nothing -> Just <$>
|
||
|
compileDefaultTemplate toformat
|
||
|
Just t -> Just <$>
|
||
|
compileCustomTemplate toformat t
|
||
|
else return Nothing
|
||
|
-- We don't yet handle binary formats:
|
||
|
reader <- case readerSpec of
|
||
|
TextReader r -> return r
|
||
|
_ -> throwError $ PandocAppError $
|
||
|
readerFormat <> " is not a text reader"
|
||
|
writer <- case writerSpec of
|
||
|
TextWriter w -> return w
|
||
|
_ -> throwError $ PandocAppError $
|
||
|
readerFormat <> " is not a text reader"
|
||
|
reader def{ readerExtensions = readerExts
|
||
|
, readerStandalone = isStandalone }
|
||
|
(text params) >>=
|
||
|
writer def{ writerExtensions = writerExts
|
||
|
, writerWrapText = fromMaybe WrapAuto (wrapText params)
|
||
|
, writerColumns = fromMaybe 72 (columns params)
|
||
|
, writerTemplate = mbTemplate }
|
||
|
|
||
|
handleErr (Right t) = return t
|
||
|
handleErr (Left err) = throwError $
|
||
|
err500 { errBody = TLE.encodeUtf8 $ TL.fromStrict $ renderError err }
|
||
|
|
||
|
compileCustomTemplate toformat t = do
|
||
|
res <- runWithPartials $ compileTemplate ("custom." <> T.unpack toformat) t
|
||
|
case res of
|
||
|
Left e -> throwError $ PandocTemplateError (T.pack e)
|
||
|
Right tpl -> return tpl
|