git.iko.soy/typeable/pandoc
on master help sign in
Merge pull request #6211 from tarleb/lua-pandocerror
0d2b8e3John MacFarlane
pandoc.cabal+9−3
@@ -412,9 +412,9 @@ library
412 blaze-markup >= 0.8 && < 0.9,
412 blaze-markup >= 0.8 && < 0.9,
413 vector >= 0.10 && < 0.13,
413 vector >= 0.10 && < 0.13,
414 jira-wiki-markup >= 1.3 && < 1.4,
414 jira-wiki-markup >= 1.3 && < 1.4,
415− hslua >= 1.0.1 && < 1.2,
415+ hslua >= 1.1 && < 1.2,
416 hslua-module-system >= 0.2 && < 0.3,
416 hslua-module-system >= 0.2 && < 0.3,
417− hslua-module-text >= 0.2 && < 0.3,
417+ hslua-module-text >= 0.2.1 && < 0.3,
418 binary >= 0.5 && < 0.11,
418 binary >= 0.5 && < 0.11,
419 SHA >= 1.6 && < 1.7,
419 SHA >= 1.6 && < 1.7,
420 haddock-library >= 1.8 && < 1.10,
420 haddock-library >= 1.8 && < 1.10,
@@ -571,6 +571,7 @@ library
571 Text.Pandoc.App.Opt,
571 Text.Pandoc.App.Opt,
572 Text.Pandoc.App.OutputSettings,
572 Text.Pandoc.App.OutputSettings,
573 Text.Pandoc.Class.CommonState,
573 Text.Pandoc.Class.CommonState,
574+ Text.Pandoc.Class.IO,
574 Text.Pandoc.Class.PandocMonad,
575 Text.Pandoc.Class.PandocMonad,
575 Text.Pandoc.Class.PandocIO,
576 Text.Pandoc.Class.PandocIO,
576 Text.Pandoc.Class.PandocPure,
577 Text.Pandoc.Class.PandocPure,
@@ -611,6 +612,7 @@ library
611 Text.Pandoc.Writers.Roff,
612 Text.Pandoc.Writers.Roff,
612 Text.Pandoc.Writers.Powerpoint.Presentation,
613 Text.Pandoc.Writers.Powerpoint.Presentation,
613 Text.Pandoc.Writers.Powerpoint.Output,
614 Text.Pandoc.Writers.Powerpoint.Output,
615+ Text.Pandoc.Lua.ErrorConversion,
614 Text.Pandoc.Lua.Filter,
616 Text.Pandoc.Lua.Filter,
615 Text.Pandoc.Lua.Global,
617 Text.Pandoc.Lua.Global,
616 Text.Pandoc.Lua.Init,
618 Text.Pandoc.Lua.Init,
@@ -621,6 +623,7 @@ library
621 Text.Pandoc.Lua.Marshaling.Context,
623 Text.Pandoc.Lua.Marshaling.Context,
622 Text.Pandoc.Lua.Marshaling.List,
624 Text.Pandoc.Lua.Marshaling.List,
623 Text.Pandoc.Lua.Marshaling.MediaBag,
625 Text.Pandoc.Lua.Marshaling.MediaBag,
626+ Text.Pandoc.Lua.Marshaling.PandocError,
624 Text.Pandoc.Lua.Marshaling.ReaderOptions,
627 Text.Pandoc.Lua.Marshaling.ReaderOptions,
625 Text.Pandoc.Lua.Marshaling.Version,
628 Text.Pandoc.Lua.Marshaling.Version,
626 Text.Pandoc.Lua.Module.MediaBag,
629 Text.Pandoc.Lua.Module.MediaBag,
@@ -629,6 +632,7 @@ library
629 Text.Pandoc.Lua.Module.Types,
632 Text.Pandoc.Lua.Module.Types,
630 Text.Pandoc.Lua.Module.Utils,
633 Text.Pandoc.Lua.Module.Utils,
631 Text.Pandoc.Lua.Packages,
634 Text.Pandoc.Lua.Packages,
635+ Text.Pandoc.Lua.PandocLua,
632 Text.Pandoc.Lua.Util,
636 Text.Pandoc.Lua.Util,
633 Text.Pandoc.Lua.Walk,
637 Text.Pandoc.Lua.Walk,
634 Text.Pandoc.CSS,
638 Text.Pandoc.CSS,
@@ -733,11 +737,13 @@ test-suite test-pandoc
733 mtl >= 2.2 && < 2.3,
737 mtl >= 2.2 && < 2.3,
734 bytestring >= 0.9 && < 0.11,
738 bytestring >= 0.9 && < 0.11,
735 base64-bytestring >= 0.1 && < 1.1,
739 base64-bytestring >= 0.1 && < 1.1,
740+ exceptions >= 0.8 && < 0.11,
736 text >= 1.1.1.0 && < 1.3,
741 text >= 1.1.1.0 && < 1.3,
737 time >= 1.5 && < 1.10,
742 time >= 1.5 && < 1.10,
738 directory >= 1.2.3 && < 1.4,
743 directory >= 1.2.3 && < 1.4,
744+ exceptions >= 0.8 && < 0.11,
739 filepath >= 1.1 && < 1.5,
745 filepath >= 1.1 && < 1.5,
740− hslua >= 1.0 && < 1.2,
746+ hslua >= 1.1 && < 1.2,
741 process >= 1.2.3 && < 1.7,
747 process >= 1.2.3 && < 1.7,
742 temporary >= 1.1 && < 1.4,
748 temporary >= 1.1 && < 1.4,
743 Diff >= 0.2 && < 0.5,
749 Diff >= 0.2 && < 0.5,
src/Text/Pandoc/Class/IO.hsnew file+231
@@ -0,0 +1,231 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{- |
4+Module : Text.Pandoc.Class.IO
5+Copyright : Copyright (C) 2016-2020 Jesse Rosenthal, John MacFarlane
6+License : GNU GPL, version 2 or above
7+
8+Maintainer : Jesse Rosenthal <jrosenthal@jhu.edu>
9+Stability : alpha
10+Portability : portable
11+
12+Default ways to perform @'PandocMonad'@ actions in a @'MonadIO'@ type.
13+
14+These functions are used to make the @'PandocIO'@ type an instance of
15+@'PandocMonad'@, but can be reused for any other MonadIO-conforming
16+types.
17+-}
18+module Text.Pandoc.Class.IO
19+ ( fileExists
20+ , getCurrentTime
21+ , getCurrentTimeZone
22+ , getDataFileName
23+ , getModificationTime
24+ , glob
25+ , logOutput
26+ , logIOError
27+ , lookupEnv
28+ , newStdGen
29+ , newUniqueHash
30+ , openURL
31+ , readFileLazy
32+ , readFileStrict
33+ , extractMedia
34+ ) where
35+
36+import Control.Monad.Except (throwError)
37+import Control.Monad.IO.Class (MonadIO, liftIO)
38+import Data.ByteString.Base64 (decodeLenient)
39+import Data.ByteString.Lazy (toChunks)
40+import Data.Text (Text, pack, unpack)
41+import Data.Time (TimeZone, UTCTime)
42+import Data.Unique (hashUnique)
43+import Network.Connection (TLSSettings (TLSSettingsSimple))
44+import Network.HTTP.Client
45+ (httpLbs, responseBody, responseHeaders,
46+ Request(port, host, requestHeaders), parseRequest, newManager)
47+import Network.HTTP.Client.Internal (addProxy)
48+import Network.HTTP.Client.TLS (mkManagerSettings)
49+import Network.HTTP.Types.Header ( hContentType )
50+import Network.Socket (withSocketsDo)
51+import Network.URI (unEscapeString)
52+import System.Directory (createDirectoryIfMissing)
53+import System.Environment (getEnv)
54+import System.FilePath ((</>), takeDirectory, normalise)
55+import System.IO (stderr)
56+import System.IO.Error
57+import System.Random (StdGen)
58+import Text.Pandoc.Class.CommonState (CommonState (..))
59+import Text.Pandoc.Class.PandocMonad
60+ (PandocMonad, getsCommonState, getMediaBag, report)
61+import Text.Pandoc.Definition (Pandoc, Inline (Image))
62+import Text.Pandoc.Error (PandocError (..))
63+import Text.Pandoc.Logging (LogMessage (..), messageVerbosity, showLogMessage)
64+import Text.Pandoc.MIME (MimeType)
65+import Text.Pandoc.MediaBag (MediaBag, lookupMedia, mediaDirectory)
66+import Text.Pandoc.Walk (walk)
67+import qualified Control.Exception as E
68+import qualified Data.ByteString as B
69+import qualified Data.ByteString.Lazy as BL
70+import qualified Data.CaseInsensitive as CI
71+import qualified Data.Text as T
72+import qualified Data.Time
73+import qualified Data.Time.LocalTime
74+import qualified Data.Unique
75+import qualified System.Directory
76+import qualified System.Environment as Env
77+import qualified System.FilePath.Glob
78+import qualified System.Random
79+import qualified Text.Pandoc.UTF8 as UTF8
80+#ifndef EMBED_DATA_FILES
81+import qualified Paths_pandoc as Paths
82+#endif
83+
84+-- | Utility function to lift IO errors into 'PandocError's.
85+liftIOError :: (PandocMonad m, MonadIO m) => (String -> IO a) -> String -> m a
86+liftIOError f u = do
87+ res <- liftIO $ tryIOError $ f u
88+ case res of
89+ Left e -> throwError $ PandocIOError (pack u) e
90+ Right r -> return r
91+
92+-- | Show potential IO errors to the user continuing execution anyway
93+logIOError :: (PandocMonad m, MonadIO m) => IO () -> m ()
94+logIOError f = do
95+ res <- liftIO $ tryIOError f
96+ case res of
97+ Left e -> report $ IgnoredIOError $ pack $ E.displayException e
98+ Right _ -> pure ()
99+
100+-- | Lookup an environment variable in the programs environment.
101+lookupEnv :: MonadIO m => Text -> m (Maybe Text)
102+lookupEnv = fmap (fmap pack) . liftIO . Env.lookupEnv . unpack
103+
104+-- | Get the current (UTC) time.
105+getCurrentTime :: MonadIO m => m UTCTime
106+getCurrentTime = liftIO Data.Time.getCurrentTime
107+
108+-- | Get the locale's time zone.
109+getCurrentTimeZone :: MonadIO m => m TimeZone
110+getCurrentTimeZone = liftIO Data.Time.LocalTime.getCurrentTimeZone
111+
112+-- | Return a new generator for random numbers.
113+newStdGen :: MonadIO m => m StdGen
114+newStdGen = liftIO System.Random.newStdGen
115+
116+-- | Return a new unique integer.
117+newUniqueHash :: MonadIO m => m Int
118+newUniqueHash = hashUnique <$> liftIO Data.Unique.newUnique
119+
120+openURL :: (PandocMonad m, MonadIO m) => Text -> m (B.ByteString, Maybe MimeType)
121+openURL u
122+ | Just u'' <- T.stripPrefix "data:" u = do
123+ let mime = T.takeWhile (/=',') u''
124+ let contents = UTF8.fromString $
125+ unEscapeString $ T.unpack $ T.drop 1 $ T.dropWhile (/=',') u''
126+ return (decodeLenient contents, Just mime)
127+ | otherwise = do
128+ let toReqHeader (n, v) = (CI.mk (UTF8.fromText n), UTF8.fromText v)
129+ customHeaders <- map toReqHeader <$> getsCommonState stRequestHeaders
130+ disableCertificateValidation <- getsCommonState stNoCheckCertificate
131+ report $ Fetching u
132+ res <- liftIO $ E.try $ withSocketsDo $ do
133+ let parseReq = parseRequest
134+ proxy <- tryIOError (getEnv "http_proxy")
135+ let addProxy' x = case proxy of
136+ Left _ -> return x
137+ Right pr -> parseReq pr >>= \r ->
138+ return (addProxy (host r) (port r) x)
139+ req <- parseReq (unpack u) >>= addProxy'
140+ let req' = req{requestHeaders = customHeaders ++ requestHeaders req}
141+ let tlsSimple = TLSSettingsSimple disableCertificateValidation False False
142+ let tlsManagerSettings = mkManagerSettings tlsSimple Nothing
143+ resp <- newManager tlsManagerSettings >>= httpLbs req'
144+ return (B.concat $ toChunks $ responseBody resp,
145+ UTF8.toText `fmap` lookup hContentType (responseHeaders resp))
146+
147+ case res of
148+ Right r -> return r
149+ Left e -> throwError $ PandocHttpError u e
150+
151+-- | Read the lazy ByteString contents from a file path, raising an error on
152+-- failure.
153+readFileLazy :: (PandocMonad m, MonadIO m) => FilePath -> m BL.ByteString
154+readFileLazy s = liftIOError BL.readFile s
155+
156+-- | Read the strict ByteString contents from a file path,
157+-- raising an error on failure.
158+readFileStrict :: (PandocMonad m, MonadIO m) => FilePath -> m B.ByteString
159+readFileStrict s = liftIOError B.readFile s
160+
161+-- | Return a list of paths that match a glob, relative to the working
162+-- directory. See 'System.FilePath.Glob' for the glob syntax.
163+glob :: (PandocMonad m, MonadIO m) => String -> m [FilePath]
164+glob = liftIOError System.FilePath.Glob.glob
165+
166+-- | Returns True if file exists.
167+fileExists :: (PandocMonad m, MonadIO m) => FilePath -> m Bool
168+fileExists = liftIOError System.Directory.doesFileExist
169+
170+-- | Returns the path of data file.
171+getDataFileName :: (PandocMonad m, MonadIO m) => FilePath -> m FilePath
172+#ifdef EMBED_DATA_FILES
173+getDataFileName = return
174+#else
175+getDataFileName = liftIOError Paths.getDataFileName
176+#endif
177+
178+-- | Return the modification time of a file.
179+getModificationTime :: (PandocMonad m, MonadIO m) => FilePath -> m UTCTime
180+getModificationTime = liftIOError System.Directory.getModificationTime
181+
182+-- | Output a log message.
183+logOutput :: (PandocMonad m, MonadIO m) => LogMessage -> m ()
184+logOutput msg = liftIO $ do
185+ UTF8.hPutStr stderr $
186+ "[" ++ show (messageVerbosity msg) ++ "] "
187+ alertIndent $ T.lines $ showLogMessage msg
188+
189+-- | Prints the list of lines to @stderr@, indenting every but the first
190+-- line by two spaces.
191+alertIndent :: [Text] -> IO ()
192+alertIndent [] = return ()
193+alertIndent (l:ls) = do
194+ UTF8.hPutStrLn stderr $ unpack l
195+ mapM_ go ls
196+ where go l' = do UTF8.hPutStr stderr " "
197+ UTF8.hPutStrLn stderr $ unpack l'
198+
199+-- | Extract media from the mediabag into a directory.
200+extractMedia :: (PandocMonad m, MonadIO m) => FilePath -> Pandoc -> m Pandoc
201+extractMedia dir d = do
202+ media <- getMediaBag
203+ case [fp | (fp, _, _) <- mediaDirectory media] of
204+ [] -> return d
205+ fps -> do
206+ mapM_ (writeMedia dir media) fps
207+ return $ walk (adjustImagePath dir fps) d
208+
209+-- | Write the contents of a media bag to a path.
210+writeMedia :: (PandocMonad m, MonadIO m)
211+ => FilePath -> MediaBag -> FilePath
212+ -> m ()
213+writeMedia dir mediabag subpath = do
214+ -- we join and split to convert a/b/c to a\b\c on Windows;
215+ -- in zip containers all paths use /
216+ let fullpath = dir </> unEscapeString (normalise subpath)
217+ let mbcontents = lookupMedia subpath mediabag
218+ case mbcontents of
219+ Nothing -> throwError $ PandocResourceNotFound $ pack subpath
220+ Just (_, bs) -> do
221+ report $ Extracting $ pack fullpath
222+ liftIOError (createDirectoryIfMissing True) (takeDirectory fullpath)
223+ logIOError $ BL.writeFile fullpath bs
224+
225+-- | If the given Inline element is an image with a @src@ path equal to
226+-- one in the list of @paths@, then prepends @dir@ to the image source;
227+-- returns the element unchanged otherwise.
228+adjustImagePath :: FilePath -> [FilePath] -> Inline -> Inline
229+adjustImagePath dir paths (Image attr lab (src, tit))
230+ | unpack src `elem` paths = Image attr lab (pack dir <> "/" <> src, tit)
231+adjustImagePath _ _ x = x
src/Text/Pandoc/Class/PandocIO.hs+21−167
@@ -1,7 +1,5 @@
1−{-# LANGUAGE CPP #-}
2{-# LANGUAGE DeriveFunctor #-}
1{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−{-# LANGUAGE OverloadedStrings #-}
5{- |
3{- |
6Module : Text.Pandoc.Class.PandocIO
4Module : Text.Pandoc.Class.PandocIO
7Copyright : Copyright (C) 2016-2020 Jesse Rosenthal, John MacFarlane
5Copyright : Copyright (C) 2016-2020 Jesse Rosenthal, John MacFarlane
@@ -16,79 +14,21 @@ This module defines @'PandocIO'@, an IO-based instance of the
16using IO operators.
14using IO operators.
17-}
15-}
18module Text.Pandoc.Class.PandocIO
16module Text.Pandoc.Class.PandocIO
19− ( getPOSIXTime
20− , getZonedTime
21− , readFileFromDirs
22− , report
23− , setTrace
24− , setRequestHeader
25− , getLog
26− , setVerbosity
27− , getVerbosity
28− , getMediaBag
29− , setMediaBag
30− , insertMedia
31− , setUserDataDir
32− , getUserDataDir
33− , fetchItem
34− , getInputFiles
35− , setInputFiles
36− , getOutputFile
37− , setOutputFile
38− , setResourcePath
39− , getResourcePath
40− , PandocIO(..)
17+ ( PandocIO(..)
41 , runIO
18 , runIO
42 , runIOorExplode
19 , runIOorExplode
43 , extractMedia
20 , extractMedia
44 ) where
21 ) where
45
22
46−import Control.Monad.Except
23+import Control.Monad.Except (ExceptT, MonadError, runExceptT)
24+import Control.Monad.IO.Class (MonadIO)
47−import Control.Monad.State.Strict
25+import Control.Monad.State (StateT, evalStateT, lift, get, put)
48−import Data.ByteString.Base64 (decodeLenient)
26+import Data.Default (Default (def))
49−import Data.ByteString.Lazy (toChunks)
50−import Data.Default
51−import Data.Text (Text)
52−import Data.Unique (hashUnique)
53−import Network.HTTP.Client
54− (httpLbs, responseBody, responseHeaders,
55− Request(port, host, requestHeaders), parseRequest, newManager)
56−import Network.HTTP.Client.Internal (addProxy)
57−import Network.HTTP.Client.TLS (mkManagerSettings)
58−import Network.Connection (TLSSettings (..))
59−import Network.HTTP.Types.Header ( hContentType )
60−import Network.Socket (withSocketsDo)
61−import Network.URI ( unEscapeString )
62−import Prelude
63−import System.Directory (createDirectoryIfMissing)
64−import System.Environment (getEnv)
65−import System.FilePath ((</>), takeDirectory, normalise)
66−import System.IO (stderr)
67−import System.IO.Error
68import Text.Pandoc.Class.CommonState (CommonState (..))
27import Text.Pandoc.Class.CommonState (CommonState (..))
69import Text.Pandoc.Class.PandocMonad
28import Text.Pandoc.Class.PandocMonad
70import Text.Pandoc.Definition
29import Text.Pandoc.Definition
71import Text.Pandoc.Error
30import Text.Pandoc.Error
72−import Text.Pandoc.Logging
31+import qualified Text.Pandoc.Class.IO as IO
73−import Text.Pandoc.MediaBag (MediaBag, lookupMedia, mediaDirectory)
74−import Text.Pandoc.Walk (walk)
75−import qualified Control.Exception as E
76−import qualified Data.ByteString as B
77−import qualified Data.ByteString.Lazy as BL
78−import qualified Data.CaseInsensitive as CI
79−import qualified Data.Text as T
80−import qualified Data.Time as IO (getCurrentTime)
81−import qualified Data.Time.LocalTime as IO (getCurrentTimeZone)
82−import qualified Data.Unique as IO (newUnique)
83−import qualified System.Directory as Directory
84−import qualified System.Directory as IO (getModificationTime)
85−import qualified System.Environment as IO (lookupEnv)
86−import qualified System.FilePath.Glob as IO (glob)
87−import qualified System.Random as IO (newStdGen)
88−import qualified Text.Pandoc.UTF8 as UTF8
89−#ifndef EMBED_DATA_FILES
90−import qualified Paths_pandoc as Paths
91−#endif
92
32
93-- | Evaluate a 'PandocIO' operation.
33-- | Evaluate a 'PandocIO' operation.
94runIO :: PandocIO a -> IO (Either PandocError a)
34runIO :: PandocIO a -> IO (Either PandocError a)
@@ -108,113 +48,27 @@ newtype PandocIO a = PandocIO {
108 , MonadError PandocError
48 , MonadError PandocError
109 )
49 )
110
50
111−-- | Utility function to lift IO errors into 'PandocError's.
112−liftIOError :: (String -> IO a) -> String -> PandocIO a
113−liftIOError f u = do
114− res <- liftIO $ tryIOError $ f u
115− case res of
116− Left e -> throwError $ PandocIOError (T.pack u) e
117− Right r -> return r
118−
119−-- | Show potential IO errors to the user continuing execution anyway
120−logIOError :: IO () -> PandocIO ()
121−logIOError f = do
122− res <- liftIO $ tryIOError f
123− case res of
124− Left e -> report $ IgnoredIOError $ T.pack $ E.displayException e
125− Right _ -> pure ()
126−
127instance PandocMonad PandocIO where
51instance PandocMonad PandocIO where
128− lookupEnv = fmap (fmap T.pack) . liftIO . IO.lookupEnv . T.unpack
52+ lookupEnv = IO.lookupEnv
129− getCurrentTime = liftIO IO.getCurrentTime
53+ getCurrentTime = IO.getCurrentTime
130− getCurrentTimeZone = liftIO IO.getCurrentTimeZone
54+ getCurrentTimeZone = IO.getCurrentTimeZone
131− newStdGen = liftIO IO.newStdGen
55+ newStdGen = IO.newStdGen
132− newUniqueHash = hashUnique <$> liftIO IO.newUnique
56+ newUniqueHash = IO.newUniqueHash
133−
134− openURL u
135− | Just u'' <- T.stripPrefix "data:" u = do
136− let mime = T.takeWhile (/=',') u''
137− let contents = UTF8.fromString $
138− unEscapeString $ T.unpack $ T.drop 1 $ T.dropWhile (/=',') u''
139− return (decodeLenient contents, Just mime)
140− | otherwise = do
141− let toReqHeader (n, v) = (CI.mk (UTF8.fromText n), UTF8.fromText v)
142− customHeaders <- map toReqHeader <$> getsCommonState stRequestHeaders
143− disableCertificateValidation <- getsCommonState stNoCheckCertificate
144− report $ Fetching u
145− res <- liftIO $ E.try $ withSocketsDo $ do
146− let parseReq = parseRequest
147− proxy <- tryIOError (getEnv "http_proxy")
148− let addProxy' x = case proxy of
149− Left _ -> return x
150− Right pr -> parseReq pr >>= \r ->
151− return (addProxy (host r) (port r) x)
152− req <- parseReq (T.unpack u) >>= addProxy'
153− let req' = req{requestHeaders = customHeaders ++ requestHeaders req}
154− resp <- newManager (mkManagerSettings (TLSSettingsSimple disableCertificateValidation False False) Nothing) >>= httpLbs req'
155− return (B.concat $ toChunks $ responseBody resp,
156− UTF8.toText `fmap` lookup hContentType (responseHeaders resp))
157
57
158− case res of
58+ openURL = IO.openURL
159− Right r -> return r
59+ readFileLazy = IO.readFileLazy
160− Left e -> throwError $ PandocHttpError u e
60+ readFileStrict = IO.readFileStrict
161
61
162− readFileLazy s = liftIOError BL.readFile s
62+ glob = IO.glob
163− readFileStrict s = liftIOError B.readFile s
63+ fileExists = IO.fileExists
64+ getDataFileName = IO.getDataFileName
65+ getModificationTime = IO.getModificationTime
164
66
165− glob = liftIOError IO.glob
166− fileExists = liftIOError Directory.doesFileExist
167−#ifdef EMBED_DATA_FILES
168− getDataFileName = return
169−#else
170− getDataFileName = liftIOError Paths.getDataFileName
171−#endif
172− getModificationTime = liftIOError IO.getModificationTime
173 getCommonState = PandocIO $ lift get
67 getCommonState = PandocIO $ lift get
174− putCommonState x = PandocIO $ lift $ put x
68+ putCommonState = PandocIO . lift . put
175− logOutput msg = liftIO $ do
176− UTF8.hPutStr stderr $
177− "[" ++ show (messageVerbosity msg) ++ "] "
178− alertIndent $ T.lines $ showLogMessage msg
179
69
180−-- | Prints the list of lines to @stderr@, indenting every but the first
70+ logOutput = IO.logOutput
181−-- line by two spaces.
182−alertIndent :: [Text] -> IO ()
183−alertIndent [] = return ()
184−alertIndent (l:ls) = do
185− UTF8.hPutStrLn stderr $ T.unpack l
186− mapM_ go ls
187− where go l' = do UTF8.hPutStr stderr " "
188− UTF8.hPutStrLn stderr $ T.unpack l'
189
71
190-- | Extract media from the mediabag into a directory.
72-- | Extract media from the mediabag into a directory.
191extractMedia :: FilePath -> Pandoc -> PandocIO Pandoc
73extractMedia :: FilePath -> Pandoc -> PandocIO Pandoc
192−extractMedia dir d = do
74+extractMedia = IO.extractMedia
193− media <- getMediaBag
194− case [fp | (fp, _, _) <- mediaDirectory media] of
195− [] -> return d
196− fps -> do
197− mapM_ (writeMedia dir media) fps
198− return $ walk (adjustImagePath dir fps) d
199−
200−-- | Write the contents of a media bag to a path.
201−writeMedia :: FilePath -> MediaBag -> FilePath -> PandocIO ()
202−writeMedia dir mediabag subpath = do
203− -- we join and split to convert a/b/c to a\b\c on Windows;
204− -- in zip containers all paths use /
205− let fullpath = dir </> unEscapeString (normalise subpath)
206− let mbcontents = lookupMedia subpath mediabag
207− case mbcontents of
208− Nothing -> throwError $ PandocResourceNotFound $ T.pack subpath
209− Just (_, bs) -> do
210− report $ Extracting $ T.pack fullpath
211− liftIOError (createDirectoryIfMissing True) (takeDirectory fullpath)
212− logIOError $ BL.writeFile fullpath bs
213−
214−-- | If the given Inline element is an image with a @src@ path equal to
215−-- one in the list of @paths@, then prepends @dir@ to the image source;
216−-- returns the element unchanged otherwise.
217−adjustImagePath :: FilePath -> [FilePath] -> Inline -> Inline
218−adjustImagePath dir paths (Image attr lab (src, tit))
219− | T.unpack src `elem` paths = Image attr lab (T.pack dir <> "/" <> src, tit)
220−adjustImagePath _ _ x = x
src/Text/Pandoc/Error.hs+2
@@ -47,6 +47,7 @@ data PandocError = PandocIOError Text IOError
47 | PandocPDFProgramNotFoundError Text
47 | PandocPDFProgramNotFoundError Text
48 | PandocPDFError Text
48 | PandocPDFError Text
49 | PandocFilterError Text Text
49 | PandocFilterError Text Text
50+ | PandocLuaError Text
50 | PandocCouldNotFindDataFileError Text
51 | PandocCouldNotFindDataFileError Text
51 | PandocResourceNotFound Text
52 | PandocResourceNotFound Text
52 | PandocTemplateError Text
53 | PandocTemplateError Text
@@ -100,6 +101,7 @@ handleError (Left e) =
100 PandocPDFError logmsg -> err 43 $ "Error producing PDF.\n" <> logmsg
101 PandocPDFError logmsg -> err 43 $ "Error producing PDF.\n" <> logmsg
101 PandocFilterError filtername msg -> err 83 $ "Error running filter " <>
102 PandocFilterError filtername msg -> err 83 $ "Error running filter " <>
102 filtername <> ":\n" <> msg
103 filtername <> ":\n" <> msg
104+ PandocLuaError msg -> err 84 $ "Error running Lua:\n" <> msg
103 PandocCouldNotFindDataFileError fn -> err 97 $
105 PandocCouldNotFindDataFileError fn -> err 97 $
104 "Could not find data file " <> fn
106 "Could not find data file " <> fn
105 PandocResourceNotFound fn -> err 99 $
107 PandocResourceNotFound fn -> err 99 $
src/Text/Pandoc/Filter/JSON.hs+3−3
@@ -23,7 +23,6 @@ import System.Directory (executable, doesFileExist, findExecutable,
23import System.Environment (getEnvironment)
23import System.Environment (getEnvironment)
24import System.Exit (ExitCode (..))
24import System.Exit (ExitCode (..))
25import System.FilePath ((</>), takeExtension)
25import System.FilePath ((</>), takeExtension)
26−import Text.Pandoc.Class.PandocIO (PandocIO)
27import Text.Pandoc.Error (PandocError (PandocFilterError))
26import Text.Pandoc.Error (PandocError (PandocFilterError))
28import Text.Pandoc.Definition (Pandoc)
27import Text.Pandoc.Definition (Pandoc)
29import Text.Pandoc.Options (ReaderOptions)
28import Text.Pandoc.Options (ReaderOptions)
@@ -32,11 +31,12 @@ import Text.Pandoc.Shared (pandocVersion, tshow)
32import qualified Control.Exception as E
31import qualified Control.Exception as E
33import qualified Text.Pandoc.UTF8 as UTF8
32import qualified Text.Pandoc.UTF8 as UTF8
34
33
34+apply :: MonadIO m
35−apply :: ReaderOptions
35+ => ReaderOptions
36 -> [String]
36 -> [String]
37 -> FilePath
37 -> FilePath
38 -> Pandoc
38 -> Pandoc
39− -> PandocIO Pandoc
39+ -> m Pandoc
40apply ropts args f = liftIO . externalFilter ropts f args
40apply ropts args f = liftIO . externalFilter ropts f args
41
41
42externalFilter :: MonadIO m
42externalFilter :: MonadIO m
src/Text/Pandoc/Filter/Lua.hs+4−5
@@ -17,8 +17,7 @@ import qualified Data.Text as T
17import Text.Pandoc.Class.PandocIO (PandocIO)
17import Text.Pandoc.Class.PandocIO (PandocIO)
18import Text.Pandoc.Definition (Pandoc)
18import Text.Pandoc.Definition (Pandoc)
19import Text.Pandoc.Error (PandocError (PandocFilterError))
19import Text.Pandoc.Error (PandocError (PandocFilterError))
20−import Text.Pandoc.Lua (Global (..), LuaException (..),
21− runLua, runFilterFile, setGlobals)
20+import Text.Pandoc.Lua (Global (..), runLua, runFilterFile, setGlobals)
22import Text.Pandoc.Options (ReaderOptions)
21import Text.Pandoc.Options (ReaderOptions)
23
22
24-- | Run the Lua filter in @filterPath@ for a transformation to the
23-- | Run the Lua filter in @filterPath@ for a transformation to the
@@ -40,7 +39,7 @@ apply ropts args fp doc = do
40 ]
39 ]
41 runFilterFile fp doc
40 runFilterFile fp doc
42
41
43−forceResult :: FilePath -> Either LuaException Pandoc -> PandocIO Pandoc
42+forceResult :: FilePath -> Either PandocError Pandoc -> PandocIO Pandoc
44forceResult fp eitherResult = case eitherResult of
43forceResult fp eitherResult = case eitherResult of
45− Right x -> return x
44+ Right x -> return x
46− Left (LuaException s) -> throw (PandocFilterError (T.pack fp) s)
45+ Left err -> throw (PandocFilterError (T.pack fp) (T.pack $ show err))
src/Text/Pandoc/Lua.hs+1−2
@@ -10,7 +10,6 @@ Running pandoc Lua filters.
10-}
10-}
11module Text.Pandoc.Lua
11module Text.Pandoc.Lua
12 ( runLua
12 ( runLua
13− , LuaException (..)
14 -- * Lua globals
13 -- * Lua globals
15 , Global (..)
14 , Global (..)
16 , setGlobals
15 , setGlobals
@@ -20,5 +19,5 @@ module Text.Pandoc.Lua
20
19
21import Text.Pandoc.Lua.Filter (runFilterFile)
20import Text.Pandoc.Lua.Filter (runFilterFile)
22import Text.Pandoc.Lua.Global (Global (..), setGlobals)
21import Text.Pandoc.Lua.Global (Global (..), setGlobals)
23−import Text.Pandoc.Lua.Init (LuaException (..), runLua)
22+import Text.Pandoc.Lua.Init (runLua)
24import Text.Pandoc.Lua.Marshaling ()
23import Text.Pandoc.Lua.Marshaling ()
src/Text/Pandoc/Lua/ErrorConversion.hsnew file+61
@@ -0,0 +1,61 @@
1+{-# LANGUAGE LambdaCase #-}
2+{-# LANGUAGE RankNTypes #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{- |
5+ Module : Text.Pandoc.Lua.ErrorConversion
6+ Copyright : © 2020 Albert Krewinkel
7+ License : GNU GPL, version 2 or above
8+
9+ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
10+ Stability : alpha
11+
12+Define how Lua errors are converted into @'PandocError'@ Haskell
13+exceptions, and /vice versa/.
14+-}
15+module Text.Pandoc.Lua.ErrorConversion
16+ ( errorConversion
17+ ) where
18+
19+import Foreign.Lua (Lua (..), NumResults)
20+import Text.Pandoc.Error (PandocError (PandocLuaError))
21+import Text.Pandoc.Lua.Marshaling.PandocError (pushPandocError, peekPandocError)
22+
23+import qualified Control.Monad.Catch as Catch
24+import qualified Data.Text as T
25+import qualified Foreign.Lua as Lua
26+
27+-- | Conversions between Lua errors and Haskell exceptions, assuming
28+-- that all exceptions are of type @'PandocError'@.
29+errorConversion :: Lua.ErrorConversion
30+errorConversion = Lua.ErrorConversion
31+ { Lua.addContextToException = addContextToException
32+ , Lua.alternative = alternative
33+ , Lua.errorToException = errorToException
34+ , Lua.exceptionToError = exceptionToError
35+ }
36+
37+-- | Convert a Lua error, which must be at the top of the stack, into a
38+-- @'PandocError'@, popping the value from the stack.
39+errorToException :: forall a . Lua.State -> IO a
40+errorToException l = Lua.unsafeRunWith l $ do
41+ err <- peekPandocError Lua.stackTop
42+ Lua.pop 1
43+ Catch.throwM err
44+
45+-- | Try the first op -- if it doesn't succeed, run the second.
46+alternative :: forall a . Lua a -> Lua a -> Lua a
47+alternative x y = Catch.try x >>= \case
48+ Left (_ :: PandocError) -> y
49+ Right x' -> return x'
50+
51+-- | Add more context to an error
52+addContextToException :: forall a . String -> Lua a -> Lua a
53+addContextToException ctx op = op `Catch.catch` \case
54+ PandocLuaError msg -> Catch.throwM $ PandocLuaError (T.pack ctx <> msg)
55+ e -> Catch.throwM e
56+
57+-- | Catch a @'PandocError'@ exception and raise it as a Lua error.
58+exceptionToError :: Lua NumResults -> Lua NumResults
59+exceptionToError op = op `Catch.catch` \e -> do
60+ pushPandocError e
61+ Lua.error
src/Text/Pandoc/Lua/Filter.hs+13−7
@@ -18,14 +18,15 @@ module Text.Pandoc.Lua.Filter ( LuaFilterFunction
18 ) where
18 ) where
19import Control.Applicative ((<|>))
19import Control.Applicative ((<|>))
20import Control.Monad (mplus, (>=>))
20import Control.Monad (mplus, (>=>))
21−import Control.Monad.Catch (finally)
21+import Control.Monad.Catch (finally, try)
22import Data.Data (Data, DataType, dataTypeConstrs, dataTypeName, dataTypeOf,
22import Data.Data (Data, DataType, dataTypeConstrs, dataTypeName, dataTypeOf,
23 showConstr, toConstr, tyconUQname)
23 showConstr, toConstr, tyconUQname)
24import Data.Foldable (foldrM)
24import Data.Foldable (foldrM)
25import Data.Map (Map)
25import Data.Map (Map)
26import Data.Maybe (fromMaybe)
26import Data.Maybe (fromMaybe)
27−import Foreign.Lua (Lua, Peekable, Pushable)
27+import Foreign.Lua (Lua, Peekable, Pushable, StackIndex)
28import Text.Pandoc.Definition
28import Text.Pandoc.Definition
29+import Text.Pandoc.Error (PandocError)
29import Text.Pandoc.Lua.Marshaling ()
30import Text.Pandoc.Lua.Marshaling ()
30import Text.Pandoc.Lua.Marshaling.List (List (..))
31import Text.Pandoc.Lua.Marshaling.List (List (..))
31import Text.Pandoc.Lua.Walk (SingletonsList (..))
32import Text.Pandoc.Lua.Walk (SingletonsList (..))
@@ -102,7 +103,7 @@ elementOrList x = do
102 if elementUnchanged
103 if elementUnchanged
103 then [x] <$ Lua.pop 1
104 then [x] <$ Lua.pop 1
104 else do
105 else do
105− mbres <- Lua.peekEither topOfStack
106+ mbres <- peekEither topOfStack
106 case mbres of
107 case mbres of
107 Right res -> [res] <$ Lua.pop 1
108 Right res -> [res] <$ Lua.pop 1
108 Left _ -> Lua.peekList topOfStack `finally` Lua.pop 1
109 Left _ -> Lua.peekList topOfStack `finally` Lua.pop 1
@@ -234,11 +235,16 @@ singleElement x = do
234 if elementUnchanged
235 if elementUnchanged
235 then x <$ Lua.pop 1
236 then x <$ Lua.pop 1
236 else do
237 else do
237− mbres <- Lua.peekEither (-1)
238+ mbres <- peekEither (-1)
238 case mbres of
239 case mbres of
239 Right res -> res <$ Lua.pop 1
240 Right res -> res <$ Lua.pop 1
240 Left err -> do
241 Left err -> do
241 Lua.pop 1
242 Lua.pop 1
242− Lua.throwException $
243+ Lua.throwMessage
243− "Error while trying to get a filter's return " ++
244+ ("Error while trying to get a filter's return " <>
244− "value from lua stack.\n" ++ err
245+ "value from Lua stack.\n" <> show err)
246+
247+-- | Try to convert the value at the given stack index to a Haskell value.
248+-- Returns @Left@ with an error message on failure.
249+peekEither :: Peekable a => StackIndex -> Lua (Either PandocError a)
250+peekEither = try . Lua.peek
src/Text/Pandoc/Lua/Init.hs+26−65
@@ -9,96 +9,57 @@
9Functions to initialize the Lua interpreter.
9Functions to initialize the Lua interpreter.
10-}
10-}
11module Text.Pandoc.Lua.Init
11module Text.Pandoc.Lua.Init
12− ( LuaException (..)
13− , LuaPackageParams (..)
14− , runLua
12+ ( runLua
15− , luaPackageParams
16 ) where
13 ) where
17
14
15+import Control.Monad.Catch (try)
18import Control.Monad.Trans (MonadIO (..))
16import Control.Monad.Trans (MonadIO (..))
19import Data.Data (Data, dataTypeConstrs, dataTypeOf, showConstr)
17import Data.Data (Data, dataTypeConstrs, dataTypeOf, showConstr)
20import Foreign.Lua (Lua)
18import Foreign.Lua (Lua)
21import GHC.IO.Encoding (getForeignEncoding, setForeignEncoding, utf8)
19import GHC.IO.Encoding (getForeignEncoding, setForeignEncoding, utf8)
22import Text.Pandoc.Class.PandocIO (PandocIO)
20import Text.Pandoc.Class.PandocIO (PandocIO)
23−import Text.Pandoc.Class.PandocMonad (getCommonState, getUserDataDir,
21+import Text.Pandoc.Error (PandocError)
24− putCommonState)
25−import Text.Pandoc.Lua.Global (Global (..), setGlobals)
26−import Text.Pandoc.Lua.Packages (LuaPackageParams (..),
27− installPandocPackageSearcher)
22+import Text.Pandoc.Lua.Packages (installPandocPackageSearcher)
23+import Text.Pandoc.Lua.PandocLua (PandocLua, liftPandocLua,
28−import Text.Pandoc.Lua.Util (loadScriptFromDataDir)
24+ loadScriptFromDataDir, runPandocLua)
29
25
30−import qualified Data.Text as Text
31import qualified Foreign.Lua as Lua
26import qualified Foreign.Lua as Lua
32−import qualified Foreign.Lua.Module.Text as Lua
33import qualified Text.Pandoc.Definition as Pandoc
27import qualified Text.Pandoc.Definition as Pandoc
34import qualified Text.Pandoc.Lua.Module.Pandoc as ModulePandoc
28import qualified Text.Pandoc.Lua.Module.Pandoc as ModulePandoc
35
29
36−-- | Lua error message
37−newtype LuaException = LuaException Text.Text deriving (Show)
38−
39-- | Run the lua interpreter, using pandoc's default way of environment
30-- | Run the lua interpreter, using pandoc's default way of environment
40-- initialization.
31-- initialization.
41−runLua :: Lua a -> PandocIO (Either LuaException a)
32+runLua :: Lua a -> PandocIO (Either PandocError a)
42runLua luaOp = do
33runLua luaOp = do
43− luaPkgParams <- luaPackageParams
44− globals <- defaultGlobals
45 enc <- liftIO $ getForeignEncoding <* setForeignEncoding utf8
34 enc <- liftIO $ getForeignEncoding <* setForeignEncoding utf8
46− res <- liftIO . Lua.runEither $ do
35+ res <- runPandocLua . try $ do
47− setGlobals globals
48− initLuaState luaPkgParams
36+ initLuaState
49− -- run the given Lua operation
37+ liftPandocLua luaOp
50− opResult <- luaOp
51− -- get the (possibly modified) state back
52− Lua.getglobal "PANDOC_STATE"
53− st <- Lua.peek Lua.stackTop
54− Lua.pop 1
55− -- done
56− return (opResult, st)
57 liftIO $ setForeignEncoding enc
38 liftIO $ setForeignEncoding enc
58− case res of
39+ return res
59− Left (Lua.Exception msg) -> return $ Left (LuaException $ Text.pack msg)
60− Right (x, newState) -> do
61− putCommonState newState
62− return $ Right x
63−
64−-- | Global variables which should always be set.
65−defaultGlobals :: PandocIO [Global]
66−defaultGlobals = do
67− commonState <- getCommonState
68− return
69− [ PANDOC_API_VERSION
70− , PANDOC_STATE commonState
71− , PANDOC_VERSION
72− ]
73−
74−-- | Generate parameters required to setup pandoc's lua environment.
75−luaPackageParams :: PandocIO LuaPackageParams
76−luaPackageParams = do
77− datadir <- getUserDataDir
78− return LuaPackageParams { luaPkgDataDir = datadir }
79
40
80-- | Initialize the lua state with all required values
41-- | Initialize the lua state with all required values
81−initLuaState :: LuaPackageParams -> Lua ()
42+initLuaState :: PandocLua ()
82−initLuaState pkgParams = do
43+initLuaState = do
83− Lua.openlibs
44+ liftPandocLua Lua.openlibs
84− Lua.preloadTextModule "text"
85− installPandocPackageSearcher pkgParams
45+ installPandocPackageSearcher
86 initPandocModule
46 initPandocModule
87− loadScriptFromDataDir (luaPkgDataDir pkgParams) "init.lua"
47+ loadScriptFromDataDir "init.lua"
88 where
48 where
89− initPandocModule :: Lua ()
49+ initPandocModule :: PandocLua ()
90 initPandocModule = do
50 initPandocModule = do
91 -- Push module table
51 -- Push module table
92− ModulePandoc.pushModule (luaPkgDataDir pkgParams)
52+ ModulePandoc.pushModule
93 -- register as loaded module
53 -- register as loaded module
54+ liftPandocLua $ do
94− Lua.pushvalue Lua.stackTop
55+ Lua.pushvalue Lua.stackTop
95− Lua.getfield Lua.registryindex Lua.loadedTableRegistryField
56+ Lua.getfield Lua.registryindex Lua.loadedTableRegistryField
96− Lua.setfield (Lua.nthFromTop 2) "pandoc"
57+ Lua.setfield (Lua.nthFromTop 2) "pandoc"
97− Lua.pop 1
58+ Lua.pop 1
98 -- copy constructors into registry
59 -- copy constructors into registry
99 putConstructorsInRegistry
60 putConstructorsInRegistry
100 -- assign module to global variable
61 -- assign module to global variable
101− Lua.setglobal "pandoc"
62+ liftPandocLua $ Lua.setglobal "pandoc"
102
63
103-- | AST elements are marshaled via normal constructor functions in the
64-- | AST elements are marshaled via normal constructor functions in the
104-- @pandoc@ module. However, accessing Lua globals from Haskell is
65-- @pandoc@ module. However, accessing Lua globals from Haskell is
@@ -108,8 +69,8 @@ initLuaState pkgParams = do
108--
69--
109-- This function expects the @pandoc@ module to be at the top of the
70-- This function expects the @pandoc@ module to be at the top of the
110-- stack.
71-- stack.
111−putConstructorsInRegistry :: Lua ()
72+putConstructorsInRegistry :: PandocLua ()
112−putConstructorsInRegistry = do
73+putConstructorsInRegistry = liftPandocLua $ do
113 constrsToReg $ Pandoc.Pandoc mempty mempty
74 constrsToReg $ Pandoc.Pandoc mempty mempty
114 constrsToReg $ Pandoc.Str mempty
75 constrsToReg $ Pandoc.Str mempty
115 constrsToReg $ Pandoc.Para mempty
76 constrsToReg $ Pandoc.Para mempty
src/Text/Pandoc/Lua/Marshaling.hs+2−1
@@ -13,6 +13,7 @@ module Text.Pandoc.Lua.Marshaling () where
13
13
14import Text.Pandoc.Lua.Marshaling.AST ()
14import Text.Pandoc.Lua.Marshaling.AST ()
15import Text.Pandoc.Lua.Marshaling.CommonState ()
15import Text.Pandoc.Lua.Marshaling.CommonState ()
16−import Text.Pandoc.Lua.Marshaling.ReaderOptions ()
17import Text.Pandoc.Lua.Marshaling.Context ()
16import Text.Pandoc.Lua.Marshaling.Context ()
17+import Text.Pandoc.Lua.Marshaling.PandocError()
18+import Text.Pandoc.Lua.Marshaling.ReaderOptions ()
18import Text.Pandoc.Lua.Marshaling.Version ()
19import Text.Pandoc.Lua.Marshaling.Version ()
src/Text/Pandoc/Lua/Marshaling/AST.hs+10−5
@@ -19,9 +19,11 @@ module Text.Pandoc.Lua.Marshaling.AST
19import Control.Applicative ((<|>))
19import Control.Applicative ((<|>))
20import Foreign.Lua (Lua, Peekable, Pushable, StackIndex)
20import Foreign.Lua (Lua, Peekable, Pushable, StackIndex)
21import Text.Pandoc.Definition
21import Text.Pandoc.Definition
22+import Text.Pandoc.Error (PandocError)
22import Text.Pandoc.Lua.Util (defineHowTo, pushViaConstructor)
23import Text.Pandoc.Lua.Util (defineHowTo, pushViaConstructor)
23import Text.Pandoc.Lua.Marshaling.CommonState ()
24import Text.Pandoc.Lua.Marshaling.CommonState ()
24
25
26+import qualified Control.Monad.Catch as Catch
25import qualified Foreign.Lua as Lua
27import qualified Foreign.Lua as Lua
26import qualified Text.Pandoc.Lua.Util as LuaUtil
28import qualified Text.Pandoc.Lua.Util as LuaUtil
27
29
@@ -131,7 +133,7 @@ peekMetaValue idx = defineHowTo "get MetaValue" $ do
131 Lua.TypeBoolean -> MetaBool <$> Lua.peek idx
133 Lua.TypeBoolean -> MetaBool <$> Lua.peek idx
132 Lua.TypeString -> MetaString <$> Lua.peek idx
134 Lua.TypeString -> MetaString <$> Lua.peek idx
133 Lua.TypeTable -> do
135 Lua.TypeTable -> do
134− tag <- Lua.try $ LuaUtil.getTag idx
136+ tag <- try $ LuaUtil.getTag idx
135 case tag of
137 case tag of
136 Right "MetaBlocks" -> MetaBlocks <$> elementContent
138 Right "MetaBlocks" -> MetaBlocks <$> elementContent
137 Right "MetaBool" -> MetaBool <$> elementContent
139 Right "MetaBool" -> MetaBool <$> elementContent
@@ -139,7 +141,7 @@ peekMetaValue idx = defineHowTo "get MetaValue" $ do
139 Right "MetaInlines" -> MetaInlines <$> elementContent
141 Right "MetaInlines" -> MetaInlines <$> elementContent
140 Right "MetaList" -> MetaList <$> elementContent
142 Right "MetaList" -> MetaList <$> elementContent
141 Right "MetaString" -> MetaString <$> elementContent
143 Right "MetaString" -> MetaString <$> elementContent
142− Right t -> Lua.throwException ("Unknown meta tag: " <> t)
144+ Right t -> Lua.throwMessage ("Unknown meta tag: " <> t)
143 Left _ -> do
145 Left _ -> do
144 -- no meta value tag given, try to guess.
146 -- no meta value tag given, try to guess.
145 len <- Lua.rawlen idx
147 len <- Lua.rawlen idx
@@ -148,7 +150,7 @@ peekMetaValue idx = defineHowTo "get MetaValue" $ do
148 else (MetaInlines <$> Lua.peek idx)
150 else (MetaInlines <$> Lua.peek idx)
149 <|> (MetaBlocks <$> Lua.peek idx)
151 <|> (MetaBlocks <$> Lua.peek idx)
150 <|> (MetaList <$> Lua.peek idx)
152 <|> (MetaList <$> Lua.peek idx)
151− _ -> Lua.throwException "could not get meta value"
153+ _ -> Lua.throwMessage "could not get meta value"
152
154
153-- | Push a block element to the top of the Lua stack.
155-- | Push a block element to the top of the Lua stack.
154pushBlock :: Block -> Lua ()
156pushBlock :: Block -> Lua ()
@@ -199,7 +201,7 @@ peekBlock idx = defineHowTo "get Block value" $ do
199 tbodies
201 tbodies
200 tfoot)
202 tfoot)
201 <$> elementContent
203 <$> elementContent
202− _ -> Lua.throwException ("Unknown block type: " <> tag)
204+ _ -> Lua.throwMessage ("Unknown block type: " <> tag)
203 where
205 where
204 -- Get the contents of an AST element.
206 -- Get the contents of an AST element.
205 elementContent :: Peekable a => Lua a
207 elementContent :: Peekable a => Lua a
@@ -344,12 +346,15 @@ peekInline idx = defineHowTo "get Inline value" $ do
344 "Strong" -> Strong <$> elementContent
346 "Strong" -> Strong <$> elementContent
345 "Subscript" -> Subscript <$> elementContent
347 "Subscript" -> Subscript <$> elementContent
346 "Superscript"-> Superscript <$> elementContent
348 "Superscript"-> Superscript <$> elementContent
347− _ -> Lua.throwException ("Unknown inline type: " <> tag)
349+ _ -> Lua.throwMessage ("Unknown inline type: " <> tag)
348 where
350 where
349 -- Get the contents of an AST element.
351 -- Get the contents of an AST element.
350 elementContent :: Peekable a => Lua a
352 elementContent :: Peekable a => Lua a
351 elementContent = LuaUtil.rawField idx "c"
353 elementContent = LuaUtil.rawField idx "c"
352
354
355+try :: Lua a -> Lua (Either PandocError a)
356+try = Catch.try
357+
353withAttr :: (Attr -> a -> b) -> (LuaAttr, a) -> b
358withAttr :: (Attr -> a -> b) -> (LuaAttr, a) -> b
354withAttr f (attributes, x) = f (fromLuaAttr attributes) x
359withAttr f (attributes, x) = f (fromLuaAttr attributes) x
355
360
src/Text/Pandoc/Lua/Marshaling/PandocError.hsnew file+65
@@ -0,0 +1,65 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{- |
6+ Module : Text.Pandoc.Lua.Marshaling.PandocError
7+ Copyright : © 2020 Albert Krewinkel
8+ License : GNU GPL, version 2 or above
9+
10+ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
11+ Stability : alpha
12+
13+Marshaling of @'PandocError'@ values.
14+-}
15+module Text.Pandoc.Lua.Marshaling.PandocError
16+ ( peekPandocError
17+ , pushPandocError
18+ )
19+ where
20+
21+import Foreign.Lua (Lua, Peekable, Pushable, StackIndex)
22+import Text.Pandoc.Error (PandocError (PandocLuaError))
23+
24+import qualified Foreign.Lua as Lua
25+import qualified Foreign.Lua.Userdata as Lua
26+import qualified Text.Pandoc.Lua.Util as LuaUtil
27+import qualified Text.Pandoc.UTF8 as UTF8
28+
29+-- | Userdata name used by Lua for the @PandocError@ type.
30+pandocErrorName :: String
31+pandocErrorName = "pandoc error"
32+
33+-- | Peek a @'PandocError'@ element to the Lua stack.
34+pushPandocError :: PandocError -> Lua ()
35+pushPandocError = Lua.pushAnyWithMetatable pushPandocErrorMT
36+ where
37+ pushPandocErrorMT = Lua.ensureUserdataMetatable pandocErrorName $
38+ LuaUtil.addFunction "__tostring" __tostring
39+
40+-- | Retrieve a @'PandocError'@ from the Lua stack.
41+peekPandocError :: StackIndex -> Lua PandocError
42+peekPandocError idx = Lua.ltype idx >>= \case
43+ Lua.TypeUserdata -> do
44+ errMb <- Lua.toAnyWithName idx pandocErrorName
45+ return $ case errMb of
46+ Just err -> err
47+ Nothing -> PandocLuaError "could not retrieve original error"
48+ _ -> do
49+ Lua.pushvalue idx
50+ msg <- Lua.state >>= \l -> Lua.liftIO (Lua.errorMessage l)
51+ return $ PandocLuaError (UTF8.toText msg)
52+
53+-- | Convert to string.
54+__tostring :: PandocError -> Lua String
55+__tostring = return . show
56+
57+--
58+-- Instances
59+--
60+
61+instance Pushable PandocError where
62+ push = pushPandocError
63+
64+instance Peekable PandocError where
65+ peek = peekPandocError
src/Text/Pandoc/Lua/Marshaling/Version.hs+2−2
@@ -57,7 +57,7 @@ peekVersion idx = Lua.ltype idx >>= \case
57 let parses = readP_to_S parseVersion versionStr
57 let parses = readP_to_S parseVersion versionStr
58 case lastMay parses of
58 case lastMay parses of
59 Just (v, "") -> return v
59 Just (v, "") -> return v
60− _ -> Lua.throwException $ "could not parse as Version: " ++ versionStr
60+ _ -> Lua.throwMessage $ "could not parse as Version: " ++ versionStr
61
61
62 Lua.TypeUserdata ->
62 Lua.TypeUserdata ->
63 reportValueOnFailure versionTypeName
63 reportValueOnFailure versionTypeName
@@ -71,7 +71,7 @@ peekVersion idx = Lua.ltype idx >>= \case
71 makeVersion <$> Lua.peek idx
71 makeVersion <$> Lua.peek idx
72
72
73 _ ->
73 _ ->
74− Lua.throwException "could not peek Version"
74+ Lua.throwMessage "could not peek Version"
75
75
76instance Peekable Version where
76instance Peekable Version where
77 peek = peekVersion
77 peek = peekVersion
src/Text/Pandoc/Lua/Module/MediaBag.hs+26−51
@@ -14,13 +14,13 @@ module Text.Pandoc.Lua.Module.MediaBag
14 ) where
14 ) where
15
15
16import Control.Monad (zipWithM_)
16import Control.Monad (zipWithM_)
17−import Foreign.Lua (Lua, NumResults, Optional, liftIO)
17+import Foreign.Lua (Lua, NumResults, Optional)
18import Text.Pandoc.Class.CommonState (CommonState (..))
18import Text.Pandoc.Class.CommonState (CommonState (..))
19−import Text.Pandoc.Class.PandocIO (runIOorExplode)
19+import Text.Pandoc.Class.PandocMonad (fetchItem, getMediaBag, modifyCommonState,
20−import Text.Pandoc.Class.PandocMonad (fetchItem, putCommonState, setMediaBag)
20+ setMediaBag)
21import Text.Pandoc.Lua.Marshaling ()
21import Text.Pandoc.Lua.Marshaling ()
22import Text.Pandoc.Lua.Marshaling.MediaBag (pushIterator)
22import Text.Pandoc.Lua.Marshaling.MediaBag (pushIterator)
23−import Text.Pandoc.Lua.Util (addFunction)
23+import Text.Pandoc.Lua.PandocLua (PandocLua (..), liftPandocLua, addFunction)
24import Text.Pandoc.MIME (MimeType)
24import Text.Pandoc.MIME (MimeType)
25
25
26import qualified Data.ByteString.Lazy as BL
26import qualified Data.ByteString.Lazy as BL
@@ -31,9 +31,9 @@ import qualified Text.Pandoc.MediaBag as MB
31--
31--
32-- MediaBag submodule
32-- MediaBag submodule
33--
33--
34−pushModule :: Lua NumResults
34+pushModule :: PandocLua NumResults
35pushModule = do
35pushModule = do
36− Lua.newtable
36+ liftPandocLua Lua.newtable
37 addFunction "delete" delete
37 addFunction "delete" delete
38 addFunction "empty" empty
38 addFunction "empty" empty
39 addFunction "insert" insertMediaFn
39 addFunction "insert" insertMediaFn
@@ -43,66 +43,46 @@ pushModule = do
43 addFunction "fetch" fetch
43 addFunction "fetch" fetch
44 return 1
44 return 1
45
45
46−--
47−-- Port functions from Text.Pandoc.Class to the Lua monad.
48−-- TODO: reuse existing functions.
49−
50−-- Get the current CommonState.
51−getCommonState :: Lua CommonState
52−getCommonState = do
53− Lua.getglobal "PANDOC_STATE"
54− Lua.peek Lua.stackTop
55−
56−-- Replace MediaBag in CommonState.
57−setCommonState :: CommonState -> Lua ()
58−setCommonState st = do
59− Lua.push st
60− Lua.setglobal "PANDOC_STATE"
61−
62−modifyCommonState :: (CommonState -> CommonState) -> Lua ()
63−modifyCommonState f = getCommonState >>= setCommonState . f
64−
65-- | Delete a single item from the media bag.
46-- | Delete a single item from the media bag.
66−delete :: FilePath -> Lua NumResults
47+delete :: FilePath -> PandocLua NumResults
67delete fp = 0 <$ modifyCommonState
48delete fp = 0 <$ modifyCommonState
68 (\st -> st { stMediaBag = MB.deleteMedia fp (stMediaBag st) })
49 (\st -> st { stMediaBag = MB.deleteMedia fp (stMediaBag st) })
69
50
70-- | Delete all items from the media bag.
51-- | Delete all items from the media bag.
71−empty :: Lua NumResults
52+empty :: PandocLua NumResults
72empty = 0 <$ modifyCommonState (\st -> st { stMediaBag = mempty })
53empty = 0 <$ modifyCommonState (\st -> st { stMediaBag = mempty })
73
54
74-- | Insert a new item into the media bag.
55-- | Insert a new item into the media bag.
75insertMediaFn :: FilePath
56insertMediaFn :: FilePath
76 -> Optional MimeType
57 -> Optional MimeType
77 -> BL.ByteString
58 -> BL.ByteString
78− -> Lua NumResults
59+ -> PandocLua NumResults
79insertMediaFn fp optionalMime contents = do
60insertMediaFn fp optionalMime contents = do
80− modifyCommonState $ \st ->
61+ mb <- getMediaBag
81− let mb = MB.insertMedia fp (Lua.fromOptional optionalMime) contents
62+ setMediaBag $ MB.insertMedia fp (Lua.fromOptional optionalMime) contents mb
82− (stMediaBag st)
83− in st { stMediaBag = mb }
84− return 0
63+ return (Lua.NumResults 0)
85
64
86-- | Returns iterator values to be used with a Lua @for@ loop.
65-- | Returns iterator values to be used with a Lua @for@ loop.
87−items :: Lua NumResults
66+items :: PandocLua NumResults
88−items = getCommonState >>= pushIterator . stMediaBag
67+items = getMediaBag >>= liftPandocLua . pushIterator
89
68
90lookupMediaFn :: FilePath
69lookupMediaFn :: FilePath
91− -> Lua NumResults
70+ -> PandocLua NumResults
92lookupMediaFn fp = do
71lookupMediaFn fp = do
93− res <- MB.lookupMedia fp . stMediaBag <$> getCommonState
72+ res <- MB.lookupMedia fp <$> getMediaBag
94− case res of
73+ liftPandocLua $ case res of
95 Nothing -> 1 <$ Lua.pushnil
74 Nothing -> 1 <$ Lua.pushnil
96 Just (mimeType, contents) -> do
75 Just (mimeType, contents) -> do
97 Lua.push mimeType
76 Lua.push mimeType
98 Lua.push contents
77 Lua.push contents
99 return 2
78 return 2
100
79
101−mediaDirectoryFn :: Lua NumResults
80+mediaDirectoryFn :: PandocLua NumResults
102mediaDirectoryFn = do
81mediaDirectoryFn = do
103− dirContents <- MB.mediaDirectory . stMediaBag <$> getCommonState
82+ dirContents <- MB.mediaDirectory <$> getMediaBag
83+ liftPandocLua $ do
104− Lua.newtable
84+ Lua.newtable
105− zipWithM_ addEntry [1..] dirContents
85+ zipWithM_ addEntry [1..] dirContents
106 return 1
86 return 1
107 where
87 where
108 addEntry :: Lua.Integer -> (FilePath, MimeType, Int) -> Lua ()
88 addEntry :: Lua.Integer -> (FilePath, MimeType, Int) -> Lua ()
@@ -114,14 +94,9 @@ mediaDirectoryFn = do
114 Lua.rawseti (-2) idx
94 Lua.rawseti (-2) idx
115
95
116fetch :: T.Text
96fetch :: T.Text
117− -> Lua NumResults
97+ -> PandocLua NumResults
118fetch src = do
98fetch src = do
119− commonState <- getCommonState
120− let mediaBag = stMediaBag commonState
121− (bs, mimeType) <- liftIO . runIOorExplode $ do
122− putCommonState commonState
123− setMediaBag mediaBag
124− fetchItem src
99+ (bs, mimeType) <- fetchItem src
125− Lua.push $ maybe "" T.unpack mimeType
100+ liftPandocLua . Lua.push $ maybe "" T.unpack mimeType
126− Lua.push bs
101+ liftPandocLua $ Lua.push bs
127 return 2 -- returns 2 values: contents, mimetype
102 return 2 -- returns 2 values: contents, mimetype
src/Text/Pandoc/Lua/Module/Pandoc.hs+17−15
@@ -24,6 +24,8 @@ import Text.Pandoc.Class.PandocIO (runIO)
24import Text.Pandoc.Definition (Block, Inline)
24import Text.Pandoc.Definition (Block, Inline)
25import Text.Pandoc.Lua.Filter (walkInlines, walkBlocks, LuaFilter, SingletonsList (..))
25import Text.Pandoc.Lua.Filter (walkInlines, walkBlocks, LuaFilter, SingletonsList (..))
26import Text.Pandoc.Lua.Marshaling ()
26import Text.Pandoc.Lua.Marshaling ()
27+import Text.Pandoc.Lua.PandocLua (PandocLua, addFunction, liftPandocLua,
28+ loadScriptFromDataDir)
27import Text.Pandoc.Walk (Walkable)
29import Text.Pandoc.Walk (Walkable)
28import Text.Pandoc.Options (ReaderOptions (readerExtensions))
30import Text.Pandoc.Options (ReaderOptions (readerExtensions))
29import Text.Pandoc.Process (pipeProcess)
31import Text.Pandoc.Process (pipeProcess)
@@ -38,28 +40,28 @@ import Text.Pandoc.Error
38
40
39-- | Push the "pandoc" on the lua stack. Requires the `list` module to be
41-- | Push the "pandoc" on the lua stack. Requires the `list` module to be
40-- loaded.
42-- loaded.
41−pushModule :: Maybe FilePath -> Lua NumResults
43+pushModule :: PandocLua NumResults
42−pushModule datadir = do
44+pushModule = do
43− LuaUtil.loadScriptFromDataDir datadir "pandoc.lua"
45+ loadScriptFromDataDir "pandoc.lua"
44− LuaUtil.addFunction "read" readDoc
46+ addFunction "read" readDoc
45− LuaUtil.addFunction "pipe" pipeFn
47+ addFunction "pipe" pipeFn
46− LuaUtil.addFunction "walk_block" walkBlock
48+ addFunction "walk_block" walkBlock
47− LuaUtil.addFunction "walk_inline" walkInline
49+ addFunction "walk_inline" walkInline
48 return 1
50 return 1
49
51
50walkElement :: (Walkable (SingletonsList Inline) a,
52walkElement :: (Walkable (SingletonsList Inline) a,
51 Walkable (SingletonsList Block) a)
53 Walkable (SingletonsList Block) a)
52− => a -> LuaFilter -> Lua a
54+ => a -> LuaFilter -> PandocLua a
53−walkElement x f = walkInlines f x >>= walkBlocks f
55+walkElement x f = liftPandocLua $ walkInlines f x >>= walkBlocks f
54
56
55−walkInline :: Inline -> LuaFilter -> Lua Inline
57+walkInline :: Inline -> LuaFilter -> PandocLua Inline
56walkInline = walkElement
58walkInline = walkElement
57
59
58−walkBlock :: Block -> LuaFilter -> Lua Block
60+walkBlock :: Block -> LuaFilter -> PandocLua Block
59walkBlock = walkElement
61walkBlock = walkElement
60
62
61−readDoc :: T.Text -> Optional T.Text -> Lua NumResults
63+readDoc :: T.Text -> Optional T.Text -> PandocLua NumResults
62−readDoc content formatSpecOrNil = do
64+readDoc content formatSpecOrNil = liftPandocLua $ do
63 let formatSpec = fromMaybe "markdown" (Lua.fromOptional formatSpecOrNil)
65 let formatSpec = fromMaybe "markdown" (Lua.fromOptional formatSpecOrNil)
64 res <- Lua.liftIO . runIO $
66 res <- Lua.liftIO . runIO $
65 getReader formatSpec >>= \(rdr,es) ->
67 getReader formatSpec >>= \(rdr,es) ->
@@ -80,8 +82,8 @@ readDoc content formatSpecOrNil = do
80pipeFn :: String
82pipeFn :: String
81 -> [String]
83 -> [String]
82 -> BL.ByteString
84 -> BL.ByteString
83− -> Lua NumResults
85+ -> PandocLua NumResults
84−pipeFn command args input = do
86+pipeFn command args input = liftPandocLua $ do
85 (ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input
87 (ec, output) <- Lua.liftIO $ pipeProcess Nothing command args input
86 case ec of
88 case ec of
87 ExitSuccess -> 1 <$ Lua.push output
89 ExitSuccess -> 1 <$ Lua.push output
src/Text/Pandoc/Lua/Module/Utils.hs+26−31
@@ -1,3 +1,4 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
1{- |
2{- |
2 Module : Text.Pandoc.Lua.Module.Utils
3 Module : Text.Pandoc.Lua.Module.Utils
3 Copyright : Copyright © 2017-2020 Albert Krewinkel
4 Copyright : Copyright © 2017-2020 Albert Krewinkel
@@ -13,15 +14,15 @@ module Text.Pandoc.Lua.Module.Utils
13 ) where
14 ) where
14
15
15import Control.Applicative ((<|>))
16import Control.Applicative ((<|>))
17+import Control.Monad.Catch (try)
16import Data.Default (def)
18import Data.Default (def)
17import Data.Version (Version)
19import Data.Version (Version)
18import Foreign.Lua (Peekable, Lua, NumResults)
20import Foreign.Lua (Peekable, Lua, NumResults)
19−import Text.Pandoc.Class.PandocIO (runIO)
20−import Text.Pandoc.Class.PandocMonad (setUserDataDir)
21import Text.Pandoc.Definition ( Pandoc, Meta, MetaValue (..), Block, Inline
21import Text.Pandoc.Definition ( Pandoc, Meta, MetaValue (..), Block, Inline
22 , Citation, Attr, ListAttributes)
22 , Citation, Attr, ListAttributes)
23+import Text.Pandoc.Error (PandocError)
23import Text.Pandoc.Lua.Marshaling ()
24import Text.Pandoc.Lua.Marshaling ()
24−import Text.Pandoc.Lua.Util (addFunction)
25+import Text.Pandoc.Lua.PandocLua (PandocLua, addFunction, liftPandocLua)
25
26
26import qualified Data.Digest.Pure.SHA as SHA
27import qualified Data.Digest.Pure.SHA as SHA
27import qualified Data.ByteString.Lazy as BSL
28import qualified Data.ByteString.Lazy as BSL
@@ -32,14 +33,14 @@ import qualified Text.Pandoc.Filter.JSON as JSONFilter
32import qualified Text.Pandoc.Shared as Shared
33import qualified Text.Pandoc.Shared as Shared
33
34
34-- | Push the "pandoc.utils" module to the lua stack.
35-- | Push the "pandoc.utils" module to the lua stack.
35−pushModule :: Maybe FilePath -> Lua NumResults
36+pushModule :: PandocLua NumResults
36−pushModule mbDatadir = do
37+pushModule = do
37− Lua.newtable
38+ liftPandocLua Lua.newtable
38 addFunction "blocks_to_inlines" blocksToInlines
39 addFunction "blocks_to_inlines" blocksToInlines
39 addFunction "equals" equals
40 addFunction "equals" equals
40 addFunction "make_sections" makeSections
41 addFunction "make_sections" makeSections
41 addFunction "normalize_date" normalizeDate
42 addFunction "normalize_date" normalizeDate
42− addFunction "run_json_filter" (runJSONFilter mbDatadir)
43+ addFunction "run_json_filter" runJSONFilter
43 addFunction "sha1" sha1
44 addFunction "sha1" sha1
44 addFunction "stringify" stringify
45 addFunction "stringify" stringify
45 addFunction "to_roman_numeral" toRomanNumeral
46 addFunction "to_roman_numeral" toRomanNumeral
@@ -47,8 +48,8 @@ pushModule mbDatadir = do
47 return 1
48 return 1
48
49
49-- | Squashes a list of blocks into inlines.
50-- | Squashes a list of blocks into inlines.
50−blocksToInlines :: [Block] -> Lua.Optional [Inline] -> Lua [Inline]
51+blocksToInlines :: [Block] -> Lua.Optional [Inline] -> PandocLua [Inline]
51−blocksToInlines blks optSep = do
52+blocksToInlines blks optSep = liftPandocLua $ do
52 let sep = case Lua.fromOptional optSep of
53 let sep = case Lua.fromOptional optSep of
53 Just x -> B.fromList x
54 Just x -> B.fromList x
54 Nothing -> Shared.defaultBlocksSeparator
55 Nothing -> Shared.defaultBlocksSeparator
@@ -67,23 +68,17 @@ normalizeDate :: T.Text -> Lua (Lua.Optional T.Text)
67normalizeDate = return . Lua.Optional . Shared.normalizeDate
68normalizeDate = return . Lua.Optional . Shared.normalizeDate
68
69
69-- | Run a JSON filter on the given document.
70-- | Run a JSON filter on the given document.
70−runJSONFilter :: Maybe FilePath
71+runJSONFilter :: Pandoc
71− -> Pandoc
72 -> FilePath
72 -> FilePath
73 -> Lua.Optional [String]
73 -> Lua.Optional [String]
74− -> Lua NumResults
74+ -> PandocLua Pandoc
75−runJSONFilter mbDatadir doc filterFile optArgs = do
75+runJSONFilter doc filterFile optArgs = do
76 args <- case Lua.fromOptional optArgs of
76 args <- case Lua.fromOptional optArgs of
77 Just x -> return x
77 Just x -> return x
78− Nothing -> do
78+ Nothing -> liftPandocLua $ do
79 Lua.getglobal "FORMAT"
79 Lua.getglobal "FORMAT"
80 (:[]) <$> Lua.popValue
80 (:[]) <$> Lua.popValue
81− filterRes <- Lua.liftIO . runIO $ do
82− setUserDataDir mbDatadir
83− JSONFilter.apply def args filterFile doc
81+ JSONFilter.apply def args filterFile doc
84− case filterRes of
85− Left err -> Lua.raiseError (show err)
86− Right d -> (1 :: NumResults) <$ Lua.push d
87
82
88-- | Calculate the hash of the given contents.
83-- | Calculate the hash of the given contents.
89sha1 :: BSL.ByteString
84sha1 :: BSL.ByteString
@@ -93,7 +88,7 @@ sha1 = return . T.pack . SHA.showDigest . SHA.sha1
93-- | Convert pandoc structure to a string with formatting removed.
88-- | Convert pandoc structure to a string with formatting removed.
94-- Footnotes are skipped (since we don't want their contents in link
89-- Footnotes are skipped (since we don't want their contents in link
95-- labels).
90-- labels).
96−stringify :: AstElement -> Lua T.Text
91+stringify :: AstElement -> PandocLua T.Text
97stringify el = return $ case el of
92stringify el = return $ case el of
98 PandocElement pd -> Shared.stringify pd
93 PandocElement pd -> Shared.stringify pd
99 InlineElement i -> Shared.stringify i
94 InlineElement i -> Shared.stringify i
@@ -109,7 +104,7 @@ stringifyMetaValue mv = case mv of
109 MetaString s -> s
104 MetaString s -> s
110 _ -> Shared.stringify mv
105 _ -> Shared.stringify mv
111
106
112−equals :: AstElement -> AstElement -> Lua Bool
107+equals :: AstElement -> AstElement -> PandocLua Bool
113equals e1 e2 = return (e1 == e2)
108equals e1 e2 = return (e1 == e2)
114
109
115data AstElement
110data AstElement
@@ -125,18 +120,18 @@ data AstElement
125
120
126instance Peekable AstElement where
121instance Peekable AstElement where
127 peek idx = do
122 peek idx = do
128− res <- Lua.try $ (PandocElement <$> Lua.peek idx)
123+ res <- try $ (PandocElement <$> Lua.peek idx)
129− <|> (InlineElement <$> Lua.peek idx)
124+ <|> (InlineElement <$> Lua.peek idx)
130− <|> (BlockElement <$> Lua.peek idx)
125+ <|> (BlockElement <$> Lua.peek idx)
131− <|> (AttrElement <$> Lua.peek idx)
126+ <|> (AttrElement <$> Lua.peek idx)
132− <|> (ListAttributesElement <$> Lua.peek idx)
127+ <|> (ListAttributesElement <$> Lua.peek idx)
133− <|> (MetaElement <$> Lua.peek idx)
128+ <|> (MetaElement <$> Lua.peek idx)
134− <|> (MetaValueElement <$> Lua.peek idx)
129+ <|> (MetaValueElement <$> Lua.peek idx)
135 case res of
130 case res of
136 Right x -> return x
131 Right x -> return x
137− Left _ -> Lua.throwException
132+ Left (_ :: PandocError) -> Lua.throwMessage
138 "Expected an AST element, but could not parse value as such."
133 "Expected an AST element, but could not parse value as such."
139
134
140-- | Convert a number < 4000 to uppercase roman numeral.
135-- | Convert a number < 4000 to uppercase roman numeral.
141−toRomanNumeral :: Lua.Integer -> Lua T.Text
136+toRomanNumeral :: Lua.Integer -> PandocLua T.Text
142toRomanNumeral = return . Shared.toRomanNumeral . fromIntegral
137toRomanNumeral = return . Shared.toRomanNumeral . fromIntegral
src/Text/Pandoc/Lua/Packages.hs+23−41
@@ -8,37 +8,32 @@
8 Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
8 Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
9 Stability : alpha
9 Stability : alpha
10
10
11−Pandoc module for lua.
11+Pandoc module for Lua.
12-}
12-}
13module Text.Pandoc.Lua.Packages
13module Text.Pandoc.Lua.Packages
14− ( LuaPackageParams (..)
15− , installPandocPackageSearcher
14+ ( installPandocPackageSearcher
16 ) where
15 ) where
17
16
18import Control.Monad (forM_)
17import Control.Monad (forM_)
19import Data.ByteString (ByteString)
18import Data.ByteString (ByteString)
20−import Foreign.Lua (Lua, NumResults, liftIO)
19+import Foreign.Lua (Lua, NumResults)
21−import Text.Pandoc.Class.PandocIO (runIO)
22−import Text.Pandoc.Class.PandocMonad (readDataFile, setUserDataDir)
20+import Text.Pandoc.Class.PandocMonad (readDataFile)
21+import Text.Pandoc.Lua.PandocLua (PandocLua, liftPandocLua)
23
22
24import qualified Foreign.Lua as Lua
23import qualified Foreign.Lua as Lua
24+import qualified Foreign.Lua.Module.Text as Text
25−import Text.Pandoc.Lua.Module.Pandoc as Pandoc
25+import qualified Text.Pandoc.Lua.Module.Pandoc as Pandoc
26−import Text.Pandoc.Lua.Module.MediaBag as MediaBag
26+import qualified Text.Pandoc.Lua.Module.MediaBag as MediaBag
27−import Text.Pandoc.Lua.Module.System as System
27+import qualified Text.Pandoc.Lua.Module.System as System
28−import Text.Pandoc.Lua.Module.Types as Types
28+import qualified Text.Pandoc.Lua.Module.Types as Types
29−import Text.Pandoc.Lua.Module.Utils as Utils
29+import qualified Text.Pandoc.Lua.Module.Utils as Utils
30−
31−-- | Parameters used to create lua packages/modules.
32−data LuaPackageParams = LuaPackageParams
33− { luaPkgDataDir :: Maybe FilePath
34− }
35
30
36-- | Insert pandoc's package loader as the first loader, making it the default.
31-- | Insert pandoc's package loader as the first loader, making it the default.
37−installPandocPackageSearcher :: LuaPackageParams -> Lua ()
32+installPandocPackageSearcher :: PandocLua ()
38−installPandocPackageSearcher luaPkgParams = do
33+installPandocPackageSearcher = liftPandocLua $ do
39 Lua.getglobal' "package.searchers"
34 Lua.getglobal' "package.searchers"
40 shiftArray
35 shiftArray
41− Lua.pushHaskellFunction (pandocPackageSearcher luaPkgParams)
36+ Lua.pushHaskellFunction pandocPackageSearcher
42 Lua.rawseti (Lua.nthFromTop 2) 1
37 Lua.rawseti (Lua.nthFromTop 2) 1
43 Lua.pop 1 -- remove 'package.searchers' from stack
38 Lua.pop 1 -- remove 'package.searchers' from stack
44 where
39 where
@@ -47,29 +42,24 @@ installPandocPackageSearcher luaPkgParams = do
47 Lua.rawseti (-2) (i + 1)
42 Lua.rawseti (-2) (i + 1)
48
43
49-- | Load a pandoc module.
44-- | Load a pandoc module.
50−pandocPackageSearcher :: LuaPackageParams -> String -> Lua NumResults
45+pandocPackageSearcher :: String -> PandocLua NumResults
51−pandocPackageSearcher pkgParams pkgName =
46+pandocPackageSearcher pkgName =
52 case pkgName of
47 case pkgName of
53− "pandoc" -> let datadir = luaPkgDataDir pkgParams
54− in pushWrappedHsFun (Pandoc.pushModule datadir)
48+ "pandoc" -> pushWrappedHsFun Pandoc.pushModule
55 "pandoc.mediabag" -> pushWrappedHsFun MediaBag.pushModule
49 "pandoc.mediabag" -> pushWrappedHsFun MediaBag.pushModule
56 "pandoc.system" -> pushWrappedHsFun System.pushModule
50 "pandoc.system" -> pushWrappedHsFun System.pushModule
57 "pandoc.types" -> pushWrappedHsFun Types.pushModule
51 "pandoc.types" -> pushWrappedHsFun Types.pushModule
58− "pandoc.utils" -> let datadir = luaPkgDataDir pkgParams
59− in pushWrappedHsFun (Utils.pushModule datadir)
52+ "pandoc.utils" -> pushWrappedHsFun Utils.pushModule
53+ "text" -> pushWrappedHsFun Text.pushModule
60− _ -> searchPureLuaLoader
54+ _ -> searchPureLuaLoader
61 where
55 where
62− pushWrappedHsFun f = do
56+ pushWrappedHsFun f = liftPandocLua $ do
63 Lua.pushHaskellFunction f
57 Lua.pushHaskellFunction f
64 return 1
58 return 1
65 searchPureLuaLoader = do
59 searchPureLuaLoader = do
66 let filename = pkgName ++ ".lua"
60 let filename = pkgName ++ ".lua"
67− modScript <- liftIO (dataDirScript (luaPkgDataDir pkgParams) filename)
61+ script <- readDataFile filename
68− case modScript of
69− Just script -> pushWrappedHsFun (loadStringAsPackage pkgName script)
62+ pushWrappedHsFun (loadStringAsPackage pkgName script)
70− Nothing -> do
71− Lua.push ("\n\tno file '" ++ filename ++ "' in pandoc's datadir")
72− return 1
73
63
74loadStringAsPackage :: String -> ByteString -> Lua NumResults
64loadStringAsPackage :: String -> ByteString -> Lua NumResults
75loadStringAsPackage pkgName script = do
65loadStringAsPackage pkgName script = do
@@ -79,11 +69,3 @@ loadStringAsPackage pkgName script = do
79 else do
69 else do
80 msg <- Lua.popValue
70 msg <- Lua.popValue
81 Lua.raiseError ("Error while loading `" <> pkgName <> "`.\n" <> msg)
71 Lua.raiseError ("Error while loading `" <> pkgName <> "`.\n" <> msg)
82−
83−-- | Get the ByteString representation of the pandoc module.
84−dataDirScript :: Maybe FilePath -> FilePath -> IO (Maybe ByteString)
85−dataDirScript datadir moduleFile = do
86− res <- runIO $ setUserDataDir datadir >> readDataFile moduleFile
87− return $ case res of
88− Left _ -> Nothing
89− Right s -> Just s
src/Text/Pandoc/Lua/PandocLua.hsnew file+134
@@ -0,0 +1,134 @@
1+{-# LANGUAGE DeriveFunctor #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5+{-# LANGUAGE MultiParamTypeClasses #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE RankNTypes #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# OPTIONS_GHC -fno-warn-orphans #-}
10+{- |
11+ Module : Text.Pandoc.Lua.PandocLua
12+ Copyright : Copyright © 2020 Albert Krewinkel
13+ License : GNU GPL, version 2 or above
14+
15+ Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
16+ Stability : alpha
17+
18+PandocMonad instance which allows execution of Lua operations and which
19+uses Lua to handle state.
20+-}
21+module Text.Pandoc.Lua.PandocLua
22+ ( PandocLua (..)
23+ , runPandocLua
24+ , liftPandocLua
25+ , addFunction
26+ , loadScriptFromDataDir
27+ ) where
28+
29+import Control.Monad (when)
30+import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
31+import Control.Monad.Except (MonadError (catchError, throwError))
32+import Control.Monad.IO.Class (MonadIO (liftIO))
33+import Foreign.Lua (Lua (..), NumResults, Pushable, ToHaskellFunction)
34+import Text.Pandoc.Class.PandocIO (PandocIO)
35+import Text.Pandoc.Class.PandocMonad (PandocMonad (..), readDataFile)
36+import Text.Pandoc.Error (PandocError)
37+import Text.Pandoc.Lua.Global (Global (..), setGlobals)
38+import Text.Pandoc.Lua.ErrorConversion (errorConversion)
39+
40+import qualified Control.Monad.Catch as Catch
41+import qualified Foreign.Lua as Lua
42+import qualified Text.Pandoc.Class.IO as IO
43+import qualified Text.Pandoc.Lua.Util as LuaUtil
44+
45+-- | Type providing access to both, pandoc and Lua operations.
46+newtype PandocLua a = PandocLua { unPandocLua :: Lua a }
47+ deriving
48+ ( Applicative
49+ , Functor
50+ , Monad
51+ , MonadCatch
52+ , MonadIO
53+ , MonadMask
54+ , MonadThrow
55+ )
56+
57+-- | Lift a @'Lua'@ operation into the @'PandocLua'@ type.
58+liftPandocLua :: Lua a -> PandocLua a
59+liftPandocLua = PandocLua
60+
61+-- | Evaluate a @'PandocLua'@ computation, running all contained Lua
62+-- operations..
63+runPandocLua :: PandocLua a -> PandocIO a
64+runPandocLua pLua = do
65+ origState <- getCommonState
66+ globals <- defaultGlobals
67+ (result, newState) <- liftIO . Lua.run' errorConversion . unPandocLua $ do
68+ putCommonState origState
69+ liftPandocLua $ setGlobals globals
70+ r <- pLua
71+ c <- getCommonState
72+ return (r, c)
73+ putCommonState newState
74+ return result
75+
76+instance {-# OVERLAPPING #-} ToHaskellFunction (PandocLua NumResults) where
77+ toHsFun _narg = unPandocLua
78+
79+instance Pushable a => ToHaskellFunction (PandocLua a) where
80+ toHsFun _narg x = 1 <$ (unPandocLua x >>= Lua.push)
81+
82+-- | Add a function to the table at the top of the stack, using the given name.
83+addFunction :: ToHaskellFunction a => String -> a -> PandocLua ()
84+addFunction name fn = liftPandocLua $ do
85+ Lua.push name
86+ Lua.pushHaskellFunction fn
87+ Lua.rawset (-3)
88+
89+-- | Load a file from pandoc's data directory.
90+loadScriptFromDataDir :: FilePath -> PandocLua ()
91+loadScriptFromDataDir scriptFile = do
92+ script <- readDataFile scriptFile
93+ status <- liftPandocLua $ Lua.dostring script
94+ when (status /= Lua.OK) . liftPandocLua $
95+ LuaUtil.throwTopMessageAsError'
96+ (("Couldn't load '" ++ scriptFile ++ "'.\n") ++)
97+
98+-- | Global variables which should always be set.
99+defaultGlobals :: PandocIO [Global]
100+defaultGlobals = do
101+ commonState <- getCommonState
102+ return
103+ [ PANDOC_API_VERSION
104+ , PANDOC_STATE commonState
105+ , PANDOC_VERSION
106+ ]
107+
108+instance MonadError PandocError PandocLua where
109+ catchError = Catch.catch
110+ throwError = Catch.throwM
111+
112+instance PandocMonad PandocLua where
113+ lookupEnv = IO.lookupEnv
114+ getCurrentTime = IO.getCurrentTime
115+ getCurrentTimeZone = IO.getCurrentTimeZone
116+ newStdGen = IO.newStdGen
117+ newUniqueHash = IO.newUniqueHash
118+
119+ openURL = IO.openURL
120+
121+ readFileLazy = IO.readFileLazy
122+ readFileStrict = IO.readFileStrict
123+
124+ glob = IO.glob
125+ fileExists = IO.fileExists
126+ getDataFileName = IO.getDataFileName
127+ getModificationTime = IO.getModificationTime
128+
129+ getCommonState = PandocLua $ do
130+ Lua.getglobal "PANDOC_STATE"
131+ Lua.peek Lua.stackTop
132+ putCommonState = PandocLua . setGlobals . (:[]) . PANDOC_STATE
133+
134+ logOutput = IO.logOutput
src/Text/Pandoc/Lua/Util.hs+7−17
@@ -19,7 +19,6 @@ module Text.Pandoc.Lua.Util
19 , addFunction
19 , addFunction
20 , addValue
20 , addValue
21 , pushViaConstructor
21 , pushViaConstructor
22− , loadScriptFromDataDir
23 , defineHowTo
22 , defineHowTo
24 , throwTopMessageAsError'
23 , throwTopMessageAsError'
25 , callWithTraceback
24 , callWithTraceback
@@ -27,13 +26,11 @@ module Text.Pandoc.Lua.Util
27 ) where
26 ) where
28
27
29import Control.Monad (unless, when)
28import Control.Monad (unless, when)
29+import Data.Text (Text)
30import Foreign.Lua ( Lua, NumArgs, NumResults, Peekable, Pushable, StackIndex
30import Foreign.Lua ( Lua, NumArgs, NumResults, Peekable, Pushable, StackIndex
31 , Status, ToHaskellFunction )
31 , Status, ToHaskellFunction )
32−import Text.Pandoc.Class.PandocIO (runIOorExplode)
33−import Text.Pandoc.Class.PandocMonad (readDataFile, setUserDataDir)
34import qualified Foreign.Lua as Lua
32import qualified Foreign.Lua as Lua
35import qualified Text.Pandoc.UTF8 as UTF8
33import qualified Text.Pandoc.UTF8 as UTF8
36−import Data.Text (Text)
37
34
38-- | Get value behind key from table at given index.
35-- | Get value behind key from table at given index.
39rawField :: Peekable a => StackIndex -> String -> Lua a
36rawField :: Peekable a => StackIndex -> String -> Lua a
@@ -87,15 +84,6 @@ pushViaCall fn = pushViaCall' fn (return ()) 0
87pushViaConstructor :: PushViaCall a => String -> a
84pushViaConstructor :: PushViaCall a => String -> a
88pushViaConstructor pandocFn = pushViaCall ("pandoc." ++ pandocFn)
85pushViaConstructor pandocFn = pushViaCall ("pandoc." ++ pandocFn)
89
86
90−-- | Load a file from pandoc's data directory.
91−loadScriptFromDataDir :: Maybe FilePath -> FilePath -> Lua ()
92−loadScriptFromDataDir datadir scriptFile = do
93− script <- Lua.liftIO . runIOorExplode $
94− setUserDataDir datadir >> readDataFile scriptFile
95− status <- Lua.dostring script
96− when (status /= Lua.OK) $
97− throwTopMessageAsError' (("Couldn't load '" ++ scriptFile ++ "'.\n") ++)
98−
99-- | Get the tag of a value. This is an optimized and specialized version of
87-- | Get the tag of a value. This is an optimized and specialized version of
100-- @Lua.getfield idx "tag"@. It only checks for the field on the table at index
88-- @Lua.getfield idx "tag"@. It only checks for the field on the table at index
101-- @idx@ and on its metatable, also ignoring any @__index@ value on the
89-- @idx@ and on its metatable, also ignoring any @__index@ value on the
@@ -107,7 +95,7 @@ getTag idx = do
107 Lua.push ("tag" :: Text)
95 Lua.push ("tag" :: Text)
108 Lua.rawget (Lua.nthFromTop 2)
96 Lua.rawget (Lua.nthFromTop 2)
109 Lua.tostring Lua.stackTop <* Lua.pop 2 >>= \case
97 Lua.tostring Lua.stackTop <* Lua.pop 2 >>= \case
110− Nothing -> Lua.throwException "untagged value"
98+ Nothing -> Lua.throwMessage "untagged value"
111 Just x -> return (UTF8.toString x)
99 Just x -> return (UTF8.toString x)
112
100
113-- | Modify the message at the top of the stack before throwing it as an
101-- | Modify the message at the top of the stack before throwing it as an
@@ -116,11 +104,12 @@ throwTopMessageAsError' :: (String -> String) -> Lua a
116throwTopMessageAsError' modifier = do
104throwTopMessageAsError' modifier = do
117 msg <- Lua.tostring' Lua.stackTop
105 msg <- Lua.tostring' Lua.stackTop
118 Lua.pop 2 -- remove error and error string pushed by tostring'
106 Lua.pop 2 -- remove error and error string pushed by tostring'
119− Lua.throwException (modifier (UTF8.toString msg))
107+ Lua.throwMessage (modifier (UTF8.toString msg))
120
108
121-- | Mark the context of a Lua computation for better error reporting.
109-- | Mark the context of a Lua computation for better error reporting.
122defineHowTo :: String -> Lua a -> Lua a
110defineHowTo :: String -> Lua a -> Lua a
123−defineHowTo ctx = Lua.withExceptionMessage (("Could not " <> ctx <> ": ") <>)
111+defineHowTo ctx op = Lua.errorConversion >>= \ec ->
112+ Lua.addContextToException ec ("Could not " <> ctx <> ": ") op
124
113
125-- | Like @'Lua.pcall'@, but uses a predefined error handler which adds a
114-- | Like @'Lua.pcall'@, but uses a predefined error handler which adds a
126-- traceback on error.
115-- traceback on error.
@@ -143,7 +132,8 @@ pcallWithTraceback nargs nresults = do
143callWithTraceback :: NumArgs -> NumResults -> Lua ()
132callWithTraceback :: NumArgs -> NumResults -> Lua ()
144callWithTraceback nargs nresults = do
133callWithTraceback nargs nresults = do
145 result <- pcallWithTraceback nargs nresults
134 result <- pcallWithTraceback nargs nresults
135+ when (result /= Lua.OK)
146− when (result /= Lua.OK) Lua.throwTopMessage
136+ Lua.throwTopMessage
147
137
148-- | Run the given string as a Lua program, while also adding a traceback to the
138-- | Run the given string as a Lua program, while also adding a traceback to the
149-- error message if an error occurs.
139-- error message if an error occurs.
src/Text/Pandoc/Writers/Custom.hs+9−18
@@ -20,17 +20,14 @@ import Data.List (intersperse)
20import qualified Data.Map as M
20import qualified Data.Map as M
21import qualified Data.Text as T
21import qualified Data.Text as T
22import Data.Text (Text, pack)
22import Data.Text (Text, pack)
23−import Data.Typeable
24import Foreign.Lua (Lua, Pushable)
23import Foreign.Lua (Lua, Pushable)
25import Text.DocLayout (render, literal)
24import Text.DocLayout (render, literal)
26import Text.Pandoc.Class.PandocIO (PandocIO)
25import Text.Pandoc.Class.PandocIO (PandocIO)
27import Text.Pandoc.Definition
26import Text.Pandoc.Definition
28−import Text.Pandoc.Lua (Global (..), LuaException (LuaException),
29− runLua, setGlobals)
27+import Text.Pandoc.Lua (Global (..), runLua, setGlobals)
30import Text.Pandoc.Lua.Util (addField, dofileWithTraceback)
28import Text.Pandoc.Lua.Util (addField, dofileWithTraceback)
31import Text.Pandoc.Options
29import Text.Pandoc.Options
32import Text.Pandoc.Templates (renderTemplate)
30import Text.Pandoc.Templates (renderTemplate)
33−import qualified Text.Pandoc.UTF8 as UTF8
34import Text.Pandoc.Writers.Shared
31import Text.Pandoc.Writers.Shared
35
32
36import qualified Foreign.Lua as Lua
33import qualified Foreign.Lua as Lua
@@ -81,11 +78,6 @@ instance (Pushable a, Pushable b) => Pushable (KeyValue a b) where
81 Lua.push v
78 Lua.push v
82 Lua.rawset (Lua.nthFromTop 3)
79 Lua.rawset (Lua.nthFromTop 3)
83
80
84−data PandocLuaException = PandocLuaException Text
85− deriving (Show, Typeable)
86−
87−instance Exception PandocLuaException
88−
89-- | Convert Pandoc to custom markup.
81-- | Convert Pandoc to custom markup.
90writeCustom :: FilePath -> WriterOptions -> Pandoc -> PandocIO Text
82writeCustom :: FilePath -> WriterOptions -> Pandoc -> PandocIO Text
91writeCustom luaFile opts doc@(Pandoc meta _) = do
83writeCustom luaFile opts doc@(Pandoc meta _) = do
@@ -97,21 +89,20 @@ writeCustom luaFile opts doc@(Pandoc meta _) = do
97 stat <- dofileWithTraceback luaFile
89 stat <- dofileWithTraceback luaFile
98 -- check for error in lua script (later we'll change the return type
90 -- check for error in lua script (later we'll change the return type
99 -- to handle this more gracefully):
91 -- to handle this more gracefully):
100− when (stat /= Lua.OK) $
92+ when (stat /= Lua.OK)
101− Lua.tostring' (-1) >>= throw . PandocLuaException . UTF8.toText
93+ Lua.throwTopMessage
102 rendered <- docToCustom opts doc
94 rendered <- docToCustom opts doc
103 context <- metaToContext opts
95 context <- metaToContext opts
104 (fmap (literal . pack) . blockListToCustom)
96 (fmap (literal . pack) . blockListToCustom)
105 (fmap (literal . pack) . inlineListToCustom)
97 (fmap (literal . pack) . inlineListToCustom)
106 meta
98 meta
107 return (pack rendered, context)
99 return (pack rendered, context)
108− let (body, context) = case res of
100+ case res of
109− Left (LuaException msg) -> throw (PandocLuaException msg)
101+ Left msg -> throw msg
110− Right x -> x
111− return $
102+ Right (body, context) -> return $
112− case writerTemplate opts of
103+ case writerTemplate opts of
113− Nothing -> body
104+ Nothing -> body
114− Just tpl -> render Nothing $
105+ Just tpl -> render Nothing $
115 renderTemplate tpl $ setField "body" body context
106 renderTemplate tpl $ setField "body" body context
116
107
117docToCustom :: WriterOptions -> Pandoc -> Lua String
108docToCustom :: WriterOptions -> Pandoc -> Lua String
stack.yaml+1
@@ -23,6 +23,7 @@ extra-deps:
23- regex-pcre-builtin-0.95.0.8.8.35
23- regex-pcre-builtin-0.95.0.8.8.35
24- doclayout-0.3
24- doclayout-0.3
25- emojis-0.1
25- emojis-0.1
26+- hslua-1.1.0
26- jira-wiki-markup-1.3.0
27- jira-wiki-markup-1.3.0
27- HsYAML-0.2.0.0
28- HsYAML-0.2.0.0
28- HsYAML-aeson-0.2.0.0
29- HsYAML-aeson-0.2.0.0
test/Tests/Lua.hs+8−7
@@ -1,5 +1,6 @@
1{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
3{- |
4{- |
4 Module : Tests.Lua
5 Module : Tests.Lua
5 Copyright : © 2017-2020 Albert Krewinkel
6 Copyright : © 2017-2020 Albert Krewinkel
@@ -28,11 +29,13 @@ import Text.Pandoc.Builder (bulletList, definitionList, displayMath, divWith,
28import Text.Pandoc.Class (runIOorExplode, setUserDataDir)
29import Text.Pandoc.Class (runIOorExplode, setUserDataDir)
29import Text.Pandoc.Definition (Block (BlockQuote, Div, Para), Inline (Emph, Str),
30import Text.Pandoc.Definition (Block (BlockQuote, Div, Para), Inline (Emph, Str),
30 Attr, Meta, Pandoc, pandocTypesVersion)
31 Attr, Meta, Pandoc, pandocTypesVersion)
32+import Text.Pandoc.Error (PandocError (PandocLuaError))
31import Text.Pandoc.Filter (Filter (LuaFilter), applyFilters)
33import Text.Pandoc.Filter (Filter (LuaFilter), applyFilters)
32import Text.Pandoc.Lua (runLua)
34import Text.Pandoc.Lua (runLua)
33import Text.Pandoc.Options (def)
35import Text.Pandoc.Options (def)
34import Text.Pandoc.Shared (pandocVersion)
36import Text.Pandoc.Shared (pandocVersion)
35
37
38+import qualified Control.Monad.Catch as Catch
36import qualified Foreign.Lua as Lua
39import qualified Foreign.Lua as Lua
37import qualified Data.Text as T
40import qualified Data.Text as T
38import qualified Data.Text.Encoding as TE
41import qualified Data.Text.Encoding as TE
@@ -197,12 +200,13 @@ tests = map (localOption (QuickCheckTests 20))
197
200
198 , testCase "informative error messages" . runLuaTest $ do
201 , testCase "informative error messages" . runLuaTest $ do
199 Lua.pushboolean True
202 Lua.pushboolean True
200− err <- Lua.peekEither Lua.stackTop
203+ eitherPandoc <- Catch.try (Lua.peek Lua.stackTop :: Lua.Lua Pandoc)
201− case (err :: Either String Pandoc) of
204+ case eitherPandoc of
202− Left msg -> do
205+ Left (PandocLuaError msg) -> do
203 let expectedMsg = "Could not get Pandoc value: "
206 let expectedMsg = "Could not get Pandoc value: "
204 <> "table expected, got boolean"
207 <> "table expected, got boolean"
205 Lua.liftIO $ assertEqual "unexpected error message" expectedMsg msg
208 Lua.liftIO $ assertEqual "unexpected error message" expectedMsg msg
209+ Left e -> error ("Expected a Lua error, but got " <> show e)
206 Right _ -> error "Getting a Pandoc element from a bool should fail."
210 Right _ -> error "Getting a Pandoc element from a bool should fail."
207 ]
211 ]
208
212
@@ -223,10 +227,7 @@ roundtripEqual x = (x ==) <$> roundtripped
223 size <- Lua.gettop
227 size <- Lua.gettop
224 when (size - oldSize /= 1) $
228 when (size - oldSize /= 1) $
225 error ("not exactly one additional element on the stack: " ++ show size)
229 error ("not exactly one additional element on the stack: " ++ show size)
226− res <- Lua.peekEither (-1)
230+ Lua.peek (-1)
227− case res of
228− Left e -> error (show e)
229− Right y -> return y
230
231
231runLuaTest :: Lua.Lua a -> IO a
232runLuaTest :: Lua.Lua a -> IO a
232runLuaTest op = runIOorExplode $ do
233runLuaTest op = runIOorExplode $ do
test/lua/module/pandoc-types.lua+3−4
@@ -26,10 +26,9 @@ return {
26 )
26 )
27 end),
27 end),
28 test('non-version string is rejected', function ()
28 test('non-version string is rejected', function ()
29− assert.error_matches(
30− function () Version '11friends' end,
29+ local success, msg = pcall(function () Version '11friends' end)
31− '11friends'
30+ assert.is_falsy(success)
32− )
31+ assert.is_truthy(tostring(msg):match('11friends'))
33 end)
32 end)
34 },
33 },
35
34