@@ -1,5 +1,3 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−{-# LANGUAGE FlexibleContexts #-}
31{-
42Copyright © 2017 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
53
@@ -17,7 +15,7 @@ You should have received a copy of the GNU General Public License
1715along with this program; if not, write to the Free Software
1816Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1917-}
20−{-# LANGUAGE CPP #-}
18+{-# LANGUAGE FlexibleContexts #-}
2119{- |
2220 Module : Text.Pandoc.Lua.PandocModule
2321 Copyright : Copyright © 2017 Albert Krewinkel
@@ -33,21 +31,20 @@ module Text.Pandoc.Lua.PandocModule
3331 , pushMediaBagModule
3432 ) where
3533
36−import Control.Monad (zipWithM_)
34+import Control.Monad (when, zipWithM_)
3735import Data.Default (Default (..))
3836import Data.Digest.Pure.SHA (sha1, showDigest)
3937import Data.IORef (IORef, modifyIORef', readIORef)
4038import Data.Maybe (fromMaybe)
4139import Data.Text (pack)
4240import Foreign.Lua (ToLuaStack, FromLuaStack, Lua, NumResults, liftIO)
43−import Foreign.Lua.FunctionCalling (ToHaskellFunction)
4441import System.Exit (ExitCode (..))
4542import Text.Pandoc.Class (CommonState (..), fetchItem, putCommonState,
4643 runIO, runIOorExplode, setMediaBag)
4744import Text.Pandoc.Definition (Block, Inline)
4845import Text.Pandoc.Lua.Filter (walkInlines, walkBlocks, LuaFilter)
4946import Text.Pandoc.Lua.StackInstances ()
50−import Text.Pandoc.Lua.Util (loadScriptFromDataDir)
47+import Text.Pandoc.Lua.Util (addFunction, addValue, loadScriptFromDataDir)
5148import Text.Pandoc.Walk (Walkable)
5249import Text.Pandoc.MIME (MimeType)
5350import Text.Pandoc.Options (ReaderOptions (readerExtensions))
@@ -55,6 +52,7 @@ import Text.Pandoc.Process (pipeProcess)
5552import Text.Pandoc.Readers (Reader (..), getReader)
5653
5754import qualified Data.ByteString.Lazy as BL
55+import qualified Data.ByteString.Lazy.Char8 as BSL
5856import qualified Foreign.Lua as Lua
5957import qualified Text.Pandoc.MediaBag as MB
6058
@@ -63,8 +61,8 @@ import qualified Text.Pandoc.MediaBag as MB
6361pushPandocModule :: Maybe FilePath -> Lua NumResults
6462pushPandocModule datadir = do
6563 loadScriptFromDataDir datadir "pandoc.lua"
66− addFunction "_pipe" pipeFn
67− addFunction "_read" readDoc
64+ addFunction "read" readDoc
65+ addFunction "pipe" pipeFn
6866 addFunction "sha1" sha1HashFn
6967 addFunction "walk_block" walkBlock
7068 addFunction "walk_inline" walkInline
@@ -80,19 +78,23 @@ walkInline = walkElement
8078walkBlock :: Block -> LuaFilter -> Lua Block
8179walkBlock = walkElement
8280
83−readDoc :: String -> String -> Lua NumResults
84−readDoc formatSpec content = do
81+readDoc :: String -> OrNil String -> Lua NumResults
82+readDoc content formatSpecOrNil = do
83+ let formatSpec = fromMaybe "markdown" (toMaybe formatSpecOrNil)
8584 case getReader formatSpec of
86− Left s -> Lua.push s -- Unknown reader
85+ Left s -> raiseError s -- Unknown reader
8786 Right (reader, es) ->
8887 case reader of
8988 TextReader r -> do
9089 res <- liftIO $ runIO $ r def{ readerExtensions = es } (pack content)
9190 case res of
92− Left s -> Lua.push $ show s -- error while reading
93− Right pd -> Lua.push pd -- success, push Pandoc
94− _ -> Lua.push "Only string formats are supported at the moment."
95− return 1
91+ Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc
92+ Left s -> raiseError (show s) -- error while reading
93+ _ -> raiseError "Only string formats are supported at the moment."
94+ where
95+ raiseError s = do
96+ Lua.push s
97+ fromIntegral <$> Lua.lerror
9698
9799--
98100-- MediaBag submodule
@@ -106,29 +108,64 @@ pushMediaBagModule commonState mediaBagRef = do
106108 addFunction "fetch" (fetch commonState mediaBagRef)
107109 return 1
108110
109−addFunction :: ToHaskellFunction a => String -> a -> Lua ()
110−addFunction name fn = do
111− Lua.push name
112− Lua.pushHaskellFunction fn
113− Lua.rawset (-3)
114−
115111sha1HashFn :: BL.ByteString
116112 -> Lua NumResults
117113sha1HashFn contents = do
118114 Lua.push $ showDigest (sha1 contents)
119115 return 1
120116
117+-- | Pipes input through a command.
121118pipeFn :: String
122119 -> [String]
123120 -> BL.ByteString
124121 -> Lua NumResults
125122pipeFn command args input = do
126123 (ec, output) <- liftIO $ pipeProcess Nothing command args input
127− Lua.push $ case ec of
128− ExitSuccess -> 0
129− ExitFailure n -> n
130− Lua.push output
131− return 2
124+ case ec of
125+ ExitSuccess -> do
126+ Lua.push output
127+ return 1
128+ ExitFailure n -> do
129+ Lua.push (PipeError command n output)
130+ fromIntegral <$> Lua.lerror
131+
132+data PipeError = PipeError
133+ { pipeErrorCommand :: String
134+ , pipeErrorCode :: Int
135+ , pipeErrorOutput :: BL.ByteString
136+ }
137+
138+instance FromLuaStack PipeError where
139+ peek idx =
140+ PipeError
141+ <$> (Lua.getfield idx "command" *> Lua.peek (-1) <* Lua.pop 1)
142+ <*> (Lua.getfield idx "error_code" *> Lua.peek (-1) <* Lua.pop 1)
143+ <*> (Lua.getfield idx "output" *> Lua.peek (-1) <* Lua.pop 1)
144+
145+instance ToLuaStack PipeError where
146+ push pipeErr = do
147+ Lua.newtable
148+ addValue "command" (pipeErrorCommand pipeErr)
149+ addValue "error_code" (pipeErrorCode pipeErr)
150+ addValue "output" (pipeErrorOutput pipeErr)
151+ pushPipeErrorMetaTable
152+ Lua.setmetatable (-2)
153+ where
154+ pushPipeErrorMetaTable :: Lua ()
155+ pushPipeErrorMetaTable = do
156+ v <- Lua.newmetatable "pandoc pipe error"
157+ when v $ addFunction "__tostring" pipeErrorMessage
158+
159+ pipeErrorMessage :: PipeError -> Lua BL.ByteString
160+ pipeErrorMessage (PipeError cmd errorCode output) = return $ mconcat
161+ [ BSL.pack "Error running "
162+ , BSL.pack cmd
163+ , BSL.pack " (error code "
164+ , BSL.pack $ show errorCode
165+ , BSL.pack "): "
166+ , if output == mempty then BSL.pack "<no output>" else output
167+ ]
168+-- end: pipe
132169
133170insertMediaFn :: IORef MB.MediaBag
134171 -> FilePath
@@ -183,14 +220,14 @@ fetch commonState mbRef src = do
183220 return 2 -- returns 2 values: contents, mimetype
184221
185222--
186−-- Helper types and orphan instances
223+-- Helper types
187224--
188225
189226newtype OrNil a = OrNil { toMaybe :: Maybe a }
190227
191228instance FromLuaStack a => FromLuaStack (OrNil a) where
192229 peek idx = do
193− noValue <- Lua.isnil idx
230+ noValue <- Lua.isnoneornil idx
194231 if noValue
195232 then return (OrNil Nothing)
196233 else OrNil . Just <$> Lua.peek idx