Blame src/Text/Pandoc/Lua/Module/Pandoc.hs

Packit Service d2f85f
{-
Packit Service d2f85f
Copyright © 2017 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
Packit Service d2f85f
Packit Service d2f85f
This program is free software; you can redistribute it and/or modify
Packit Service d2f85f
it under the terms of the GNU General Public License as published by
Packit Service d2f85f
the Free Software Foundation; either version 2 of the License, or
Packit Service d2f85f
(at your option) any later version.
Packit Service d2f85f
Packit Service d2f85f
This program is distributed in the hope that it will be useful,
Packit Service d2f85f
but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service d2f85f
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service d2f85f
GNU General Public License for more details.
Packit Service d2f85f
Packit Service d2f85f
You should have received a copy of the GNU General Public License
Packit Service d2f85f
along with this program; if not, write to the Free Software
Packit Service d2f85f
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Packit Service d2f85f
-}
Packit Service d2f85f
{-# LANGUAGE FlexibleContexts #-}
Packit Service d2f85f
{- |
Packit Service d2f85f
   Module      : Text.Pandoc.Lua.Module.Pandoc
Packit Service d2f85f
   Copyright   : Copyright © 2017 Albert Krewinkel
Packit Service d2f85f
   License     : GNU GPL, version 2 or above
Packit Service d2f85f
Packit Service d2f85f
   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
Packit Service d2f85f
   Stability   : alpha
Packit Service d2f85f
Packit Service d2f85f
Pandoc module for lua.
Packit Service d2f85f
-}
Packit Service d2f85f
module Text.Pandoc.Lua.Module.Pandoc
Packit Service d2f85f
  ( pushModule
Packit Service d2f85f
  ) where
Packit Service d2f85f
Packit Service d2f85f
import Control.Monad (when)
Packit Service d2f85f
import Data.Default (Default (..))
Packit Service d2f85f
import Data.Maybe (fromMaybe)
Packit Service d2f85f
import Data.Text (pack)
Packit Service d2f85f
import Foreign.Lua (ToLuaStack, FromLuaStack, Lua, NumResults, liftIO)
Packit Service d2f85f
import System.Exit (ExitCode (..))
Packit Service d2f85f
import Text.Pandoc.Class (runIO)
Packit Service d2f85f
import Text.Pandoc.Definition (Block, Inline)
Packit Service d2f85f
import Text.Pandoc.Lua.Filter (walkInlines, walkBlocks, LuaFilter)
Packit Service d2f85f
import Text.Pandoc.Lua.StackInstances ()
Packit Service d2f85f
import Text.Pandoc.Lua.Util (OrNil (toMaybe), addFunction, addValue,
Packit Service d2f85f
                             loadScriptFromDataDir, raiseError)
Packit Service d2f85f
import Text.Pandoc.Walk (Walkable)
Packit Service d2f85f
import Text.Pandoc.Options (ReaderOptions (readerExtensions))
Packit Service d2f85f
import Text.Pandoc.Process (pipeProcess)
Packit Service d2f85f
import Text.Pandoc.Readers (Reader (..), getReader)
Packit Service d2f85f
Packit Service d2f85f
import qualified Data.ByteString.Lazy as BL
Packit Service d2f85f
import qualified Data.ByteString.Lazy.Char8 as BSL
Packit Service d2f85f
import qualified Foreign.Lua as Lua
Packit Service d2f85f
Packit Service d2f85f
-- | Push the "pandoc" on the lua stack. Requires the `list` module to be
Packit Service d2f85f
-- loaded.
Packit Service d2f85f
pushModule :: Maybe FilePath -> Lua NumResults
Packit Service d2f85f
pushModule datadir = do
Packit Service d2f85f
  loadScriptFromDataDir datadir "pandoc.lua"
Packit Service d2f85f
  addFunction "read" readDoc
Packit Service d2f85f
  addFunction "pipe" pipeFn
Packit Service d2f85f
  addFunction "walk_block" walkBlock
Packit Service d2f85f
  addFunction "walk_inline" walkInline
Packit Service d2f85f
  return 1
Packit Service d2f85f
Packit Service d2f85f
walkElement :: (ToLuaStack a, Walkable [Inline] a, Walkable [Block] a)
Packit Service d2f85f
            => a -> LuaFilter -> Lua a
Packit Service d2f85f
walkElement x f = walkInlines f x >>= walkBlocks f
Packit Service d2f85f
Packit Service d2f85f
walkInline :: Inline -> LuaFilter -> Lua Inline
Packit Service d2f85f
walkInline = walkElement
Packit Service d2f85f
Packit Service d2f85f
walkBlock :: Block -> LuaFilter -> Lua Block
Packit Service d2f85f
walkBlock = walkElement
Packit Service d2f85f
Packit Service d2f85f
readDoc :: String -> OrNil String -> Lua NumResults
Packit Service d2f85f
readDoc content formatSpecOrNil = do
Packit Service d2f85f
  let formatSpec = fromMaybe "markdown" (toMaybe formatSpecOrNil)
Packit Service d2f85f
  case getReader formatSpec of
Packit Service d2f85f
    Left  s      -> raiseError s -- Unknown reader
Packit Service d2f85f
    Right (reader, es) ->
Packit Service d2f85f
      case reader of
Packit Service d2f85f
        TextReader r -> do
Packit Service d2f85f
          res <- liftIO $ runIO $ r def{ readerExtensions = es } (pack content)
Packit Service d2f85f
          case res of
Packit Service d2f85f
            Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc
Packit Service d2f85f
            Left s   -> raiseError (show s)              -- error while reading
Packit Service d2f85f
        _  -> raiseError "Only string formats are supported at the moment."
Packit Service d2f85f
Packit Service d2f85f
-- | Pipes input through a command.
Packit Service d2f85f
pipeFn :: String
Packit Service d2f85f
       -> [String]
Packit Service d2f85f
       -> BL.ByteString
Packit Service d2f85f
       -> Lua NumResults
Packit Service d2f85f
pipeFn command args input = do
Packit Service d2f85f
  (ec, output) <- liftIO $ pipeProcess Nothing command args input
Packit Service d2f85f
  case ec of
Packit Service d2f85f
    ExitSuccess -> 1 <$ Lua.push output
Packit Service d2f85f
    ExitFailure n -> raiseError (PipeError command n output)
Packit Service d2f85f
Packit Service d2f85f
data PipeError = PipeError
Packit Service d2f85f
  { pipeErrorCommand :: String
Packit Service d2f85f
  , pipeErrorCode :: Int
Packit Service d2f85f
  , pipeErrorOutput :: BL.ByteString
Packit Service d2f85f
  }
Packit Service d2f85f
Packit Service d2f85f
instance FromLuaStack PipeError where
Packit Service d2f85f
  peek idx =
Packit Service d2f85f
    PipeError
Packit Service d2f85f
    <$> (Lua.getfield idx "command"    *> Lua.peek (-1) <* Lua.pop 1)
Packit Service d2f85f
    <*> (Lua.getfield idx "error_code" *> Lua.peek (-1) <* Lua.pop 1)
Packit Service d2f85f
    <*> (Lua.getfield idx "output"     *> Lua.peek (-1) <* Lua.pop 1)
Packit Service d2f85f
Packit Service d2f85f
instance ToLuaStack PipeError where
Packit Service d2f85f
  push pipeErr = do
Packit Service d2f85f
    Lua.newtable
Packit Service d2f85f
    addValue "command" (pipeErrorCommand pipeErr)
Packit Service d2f85f
    addValue "error_code" (pipeErrorCode pipeErr)
Packit Service d2f85f
    addValue "output" (pipeErrorOutput pipeErr)
Packit Service d2f85f
    pushPipeErrorMetaTable
Packit Service d2f85f
    Lua.setmetatable (-2)
Packit Service d2f85f
      where
Packit Service d2f85f
        pushPipeErrorMetaTable :: Lua ()
Packit Service d2f85f
        pushPipeErrorMetaTable = do
Packit Service d2f85f
          v <- Lua.newmetatable "pandoc pipe error"
Packit Service d2f85f
          when v $ addFunction "__tostring" pipeErrorMessage
Packit Service d2f85f
Packit Service d2f85f
        pipeErrorMessage :: PipeError -> Lua BL.ByteString
Packit Service d2f85f
        pipeErrorMessage (PipeError cmd errorCode output) = return $ mconcat
Packit Service d2f85f
          [ BSL.pack "Error running "
Packit Service d2f85f
          , BSL.pack cmd
Packit Service d2f85f
          , BSL.pack " (error code "
Packit Service d2f85f
          , BSL.pack $ show errorCode
Packit Service d2f85f
          , BSL.pack "): "
Packit Service d2f85f
          , if output == mempty then BSL.pack "<no output>" else output
Packit Service d2f85f
          ]