aboutsummaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Readers/Custom.hs
blob: 9252a9e45ab6073550f62913eacae02454e02de2 (plain)
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
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE OverloadedStrings   #-}
{- |
   Module      : Text.Pandoc.Readers.Custom
   Copyright   : Copyright (C) 2021 John MacFarlane
   License     : GNU GPL, version 2 or above

   Maintainer  : John MacFarlane <jgm@berkeley.edu>
   Stability   : alpha
   Portability : portable

Supports custom parsers written in Lua which produce a Pandoc AST.
-}
module Text.Pandoc.Readers.Custom ( readCustom ) where
import Control.Exception
import Control.Monad (when)
import HsLua as Lua hiding (Operation (Div), render)
import Control.Monad.IO.Class (MonadIO)
import Text.Pandoc.Definition
import Text.Pandoc.Class (PandocMonad, report)
import Text.Pandoc.Logging
import Text.Pandoc.Lua (Global (..), runLua, setGlobals)
import Text.Pandoc.Lua.PandocLua
import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc)
import Text.Pandoc.Lua.Util (dofileWithTraceback, callWithTraceback,
                             pcallWithTraceback)
import Text.Pandoc.Options
import Text.Pandoc.Sources (ToSources(..), sourcesToText)
import qualified Data.Text as T

-- | Convert custom markup to Pandoc.
readCustom :: (PandocMonad m, MonadIO m, ToSources s)
            => FilePath -> ReaderOptions -> s -> m Pandoc
readCustom luaFile opts srcs = do
  let globals = [ PANDOC_SCRIPT_FILE luaFile ]
  res <- runLua $ do
    setGlobals globals
    stat <- dofileWithTraceback luaFile
    -- check for error in lua script (later we'll change the return type
    -- to handle this more gracefully):
    when (stat /= Lua.OK)
      Lua.throwErrorAsException
    parseCustom
  case res of
    Left msg -> throw msg
    Right doc -> return doc
 where
  parseCustom = do
    let input = toSources srcs
    getglobal "Reader"
    push input
    push opts
    pcallWithTraceback 2 1 >>= \case
      OK -> forcePeek $ peekPandoc top
      ErrRun -> do
        -- Caught a runtime error. Check if parsing might work if we
        -- pass a string instead of a Sources list, then retry.
        runPeek (peekText top) >>= \case
          Failure {} ->
            -- not a string error object. Bail!
            throwErrorAsException
          Success errmsg -> do
            if "string expected, got pandoc Sources" `T.isInfixOf` errmsg
              then do
                pop 1
                _ <- unPandocLua $ do
                  report $ Deprecated "old Reader function signature" $
                    T.unlines
                    [ "Reader functions should accept a sources list; "
                    , "functions expecting `string` input are deprecated. "
                    , "Use `tostring` to convert the first argument to a "
                    , "string."
                    ]
                getglobal "Reader"
                push $ sourcesToText input  -- push sources as string
                push opts
                callWithTraceback 2 1
                forcePeek $ peekPandoc top
              else
                -- nothing we can do here
                throwErrorAsException
      _ ->  -- not a runtime error, we won't be able to recover from that
        throwErrorAsException