aboutsummaryrefslogtreecommitdiff
path: root/pandoc-lua-engine/src/Text/Pandoc/Lua/Writer.hs
blob: 91573c87b306be9c06944ee9110f7d7018b99934 (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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE TupleSections       #-}
{-# LANGUAGE TypeApplications    #-}
{- |
   Module      : Text.Pandoc.Lua.Writer
   Copyright   : Copyright (C) 2012-2022 John MacFarlane
   License     : GNU GPL, version 2 or above

   Maintainer  : John MacFarlane <[email protected]>
   Stability   : alpha
   Portability : portable

Conversion of Pandoc documents using a custom Lua writer.
-}
module Text.Pandoc.Lua.Writer
  ( writeCustom
  ) where

import Control.Exception
import Control.Monad ((<=<))
import Data.Default (def)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import HsLua
import HsLua.Core.Run (newGCManagedState, withGCManagedState)
import Control.Monad.IO.Class (MonadIO)
import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback)
import Text.Pandoc.Error (PandocError (..))
import Text.Pandoc.Format (ExtensionsConfig (..))
import Text.Pandoc.Lua.Global (Global (..), setGlobals)
import Text.Pandoc.Lua.Init (runLuaWith)
import Text.Pandoc.Lua.Marshal.Format (peekExtensionsConfig)
import Text.Pandoc.Lua.Marshal.WriterOptions (pushWriterOptions)
import Text.Pandoc.Writers (Writer (..))
import qualified Text.Pandoc.Lua.Writer.Classic as Classic

-- | Convert Pandoc to custom markup.
writeCustom :: (PandocMonad m, MonadIO m)
            => FilePath -> m (Writer m, ExtensionsConfig, Maybe Text)
writeCustom luaFile = do
  luaState <- liftIO newGCManagedState
  luaFile' <- fromMaybe luaFile <$> findFileWithDataFallback "writers" luaFile
  either throw pure <=< runLuaWith luaState $ do
    setGlobals [ PANDOC_DOCUMENT mempty
               , PANDOC_SCRIPT_FILE luaFile'
               , PANDOC_WRITER_OPTIONS def
               ]
    dofileTrace luaFile' >>= \case
      OK -> pure ()
      _  -> throwErrorAsException
    -- Most classic writers contain code that throws an error if a global
    -- is not present. This would break our check for the existence of a
    -- "Writer" function. We resort to raw access for that reason, but
    -- could also catch the error instead.
    let rawgetglobal x = do
          pushglobaltable
          pushName x
          rawget (nth 2) <* remove (nth 2) -- remove global table

    let writerField = "Pandoc Writer function"

    extsConf <- rawgetglobal "Extensions" >>= \case
      TypeNil   -> ExtensionsConfig mempty mempty <$ pop 1
      _         -> forcePeek $ peekExtensionsConfig top `lastly` pop 1

    mtemplate <- rawgetglobal "Template" >>= \case
      TypeNil   -> pure Nothing
      TypeFunction -> Just <$> do
        callTrace 0 1
        forcePeek $ peekText top `lastly` pop 1
      _ -> Just <$> do
        forcePeek $ peekText top `lastly` pop 1

    let addProperties = (, extsConf, mtemplate)

    rawgetglobal "Writer" >>= \case
      TypeNil -> rawgetglobal "ByteStringWriter" >>= \case
        TypeNil -> do
          -- Neither `Writer` nor `BinaryWriter` are defined. Try to
          -- use the file as a classic writer.
          pop 1  -- remove nil
          pure $ addProperties . TextWriter $ \opts doc ->
            liftIO $ withGCManagedState luaState $ do
              Classic.runCustom @PandocError opts doc
        _ -> do
          -- Binary writer. Writer function is on top of the stack.
          setfield registryindex writerField
          pure $ addProperties . ByteStringWriter $ \opts doc ->
            -- Call writer with document and writer options as arguments.
            liftIO $ withGCManagedState luaState $ do
              getfield registryindex writerField
              push doc
              pushWriterOptions opts
              callTrace 2 1
              forcePeek @PandocError $ peekLazyByteString top
      _ -> do
        -- New-type text writer. Writer function is on top of the stack.
        setfield registryindex writerField
        pure $ addProperties . TextWriter $ \opts doc ->
          liftIO $ withGCManagedState luaState $ do
            getfield registryindex writerField
            push doc
            pushWriterOptions opts
            callTrace 2 1
            forcePeek @PandocError $ peekText top