git.iko.soy/typeable/pandoc
on master help sign in
Text.Pandoc.App: Throw errors rather than exiting.

These are caught (and lead to exit) in pandoc.hs, but other uses of Text.Pandoc.App may want to recover in another way. Added PandocAppError to PandocError (API change). This is a stopgap: later we should have a separate constructor for each type of error. Also fixed uses of 'exit' in Shared.readDataFile, and removed 'err' from Shared (API change). Finally, removed the dependency on extensible-exceptions. See #3548.

commit913db947a9cb43b6f449db2cd4c85fd74aa1ac8f authorJohn MacFarlane <jgm@berkeley.edu> dateSun 2 Apr 2017, 23:02 (10 years ago) parent9e78a9d
6 files changed+72−57
pandoc.cabal−1 pandoc.hs+4−1 src/Text/Pandoc/App.hs+53−41 src/Text/Pandoc/Error.hs+10−1 src/Text/Pandoc/Shared.hs+4−12 src/Text/Pandoc/Templates.hs+1−1
pandoc.cabal−1
@@ -296,7 +296,6 @@ Library
296296 texmath >= 0.9.4 && < 0.10,
297297 xml >= 1.3.12 && < 1.4,
298298 random >= 1 && < 1.2,
299− extensible-exceptions >= 0.1 && < 0.2,
300299 pandoc-types >= 1.17 && < 1.18,
301300 aeson >= 0.7 && < 1.2,
302301 aeson-pretty >= 0.8 && < 0.9,
pandoc.hs+4−1
@@ -34,6 +34,9 @@ writers.
3434-}
3535module Main where
3636import Text.Pandoc.App (convertWithOpts, defaultOpts, options, parseOptions)
37+import Text.Pandoc.Error (handleError, PandocError)
38+import qualified Control.Exception as E
3739
3840main :: IO ()
39−main = parseOptions options defaultOpts >>= convertWithOpts
41+main = E.catch (parseOptions options defaultOpts >>= convertWithOpts)
42+ (\(e :: PandocError) -> handleError (Left e))
src/Text/Pandoc/App.hs+53−41
@@ -39,7 +39,6 @@ module Text.Pandoc.App (
3939 ) where
4040import Control.Applicative ((<|>))
4141import qualified Control.Exception as E
42−import Control.Exception.Extensible (throwIO)
4342import Control.Monad
4443import Control.Monad.Trans
4544import Data.Aeson (eitherDecode', encode)
@@ -68,6 +67,7 @@ import System.FilePath
6867import System.IO (stderr, stdout)
6968import System.IO.Error (isDoesNotExistError)
7069import Text.Pandoc
70+import Text.Pandoc.Error (PandocError(..))
7171import Text.Pandoc.Builder (setMeta)
7272import Text.Pandoc.Class (PandocIO, getLog, withMediaBag)
7373import Text.Pandoc.Highlighting (highlightingStyles)
@@ -76,7 +76,7 @@ import Text.Pandoc.MediaBag (MediaBag, extractMediaBag, mediaDirectory)
7676import Text.Pandoc.PDF (makePDF)
7777import Text.Pandoc.Process (pipeProcess)
7878import Text.Pandoc.SelfContained (makeSelfContained, makeDataURI)
79−import Text.Pandoc.Shared (err, headerShift, openURL, readDataFile,
79+import Text.Pandoc.Shared (headerShift, openURL, readDataFile,
8080 readDataFileUTF8, safeRead, tabFilter)
8181import qualified Text.Pandoc.UTF8 as UTF8
8282import Text.Pandoc.Walk (walk)
@@ -98,7 +98,8 @@ parseOptions options' defaults = do
9898 let unknownOptionErrors = foldr handleUnrecognizedOption [] unrecognizedOpts
9999
100100 unless (null errors && null unknownOptionErrors) $
101− err 2 $ concat errors ++ unlines unknownOptionErrors ++
101+ E.throwIO $ PandocAppError 2 $
102+ concat errors ++ unlines unknownOptionErrors ++
102103 ("Try " ++ prg ++ " --help for more information.")
103104
104105 -- thread option data structure through all supplied option actions
@@ -175,7 +176,7 @@ convertWithOpts opts = do
175176 (\o d -> liftIO $ writeCustom writerName o d)
176177 :: Writer PandocIO)
177178 else case getWriter writerName of
178− Left e -> err 9 $
179+ Left e -> E.throwIO $ PandocAppError 9 $
179180 if format == "pdf"
180181 then e ++
181182 "\nTo create a pdf with pandoc, use " ++
@@ -189,7 +190,7 @@ convertWithOpts opts = do
189190 -- the sake of the text2tags reader.
190191 reader <- case getReader readerName of
191192 Right r -> return (r :: Reader PandocIO)
192− Left e -> err 7 e'
193+ Left e -> E.throwIO $ PandocAppError 7 e'
193194 where e' = case readerName of
194195 "pdf" -> e ++
195196 "\nPandoc can convert to PDF, but not from PDF."
@@ -204,7 +205,7 @@ convertWithOpts opts = do
204205 Nothing -> do
205206 deftemp <- getDefaultTemplate datadir format
206207 case deftemp of
207− Left e -> throwIO e
208+ Left e -> E.throwIO e
208209 Right t -> return (Just t)
209210 Just tp -> do
210211 -- strip off extensions
@@ -217,8 +218,8 @@ convertWithOpts opts = do
217218 (readDataFileUTF8 datadir
218219 ("templates" </> tp'))
219220 (\e' -> let _ = (e' :: E.SomeException)
220− in throwIO e')
221− else throwIO e)
221+ in E.throwIO e')
222+ else E.throwIO e)
222223
223224 let addStringAsVariable varname s vars = return $ (varname, s) : vars
224225
@@ -304,7 +305,7 @@ convertWithOpts opts = do
304305 let addSyntaxMap existingmap f = do
305306 res <- parseSyntaxDefinition f
306307 case res of
307− Left errstr -> err 67 errstr
308+ Left errstr -> E.throwIO $ PandocAppError 67 errstr
308309 Right syn -> return $ addSyntaxDefinition syn existingmap
309310
310311 syntaxMap <- foldM addSyntaxMap defaultSyntaxMap
@@ -312,7 +313,8 @@ convertWithOpts opts = do
312313
313314 case missingIncludes (M.elems syntaxMap) of
314315 [] -> return ()
315− xs -> err 73 $ "Missing syntax definitions:\n" ++
316+ xs -> E.throwIO $ PandocAppError 73 $
317+ "Missing syntax definitions:\n" ++
316318 unlines (map
317319 (\(syn,dep) -> (T.unpack syn ++ " requires " ++
318320 T.unpack dep ++ " through IncludeRules.")) xs)
@@ -358,7 +360,8 @@ convertWithOpts opts = do
358360 istty <- queryTerminal stdOutput
359361#endif
360362 when (istty && not (isTextFormat format) && outputFile == "-") $
361− err 5 $ "Cannot write " ++ format ++ " output to stdout.\n" ++
363+ E.throwIO $ PandocAppError 5 $
364+ "Cannot write " ++ format ++ " output to stdout.\n" ++
362365 "Specify an output file using the -o option."
363366
364367
@@ -386,7 +389,8 @@ convertWithOpts opts = do
386389 Just logfile -> B.writeFile logfile (encodeLogMessages reports)
387390 let isWarning msg = messageVerbosity msg == WARNING
388391 when (optFailIfWarnings opts && any isWarning reports) $
389− err 3 "Failing because there were warnings."
392+ E.throwIO $
393+ PandocAppError 3 "Failing because there were warnings."
390394 return res
391395
392396 let sourceToDoc :: [FilePath] -> PandocIO (Pandoc, MediaBag)
@@ -429,8 +433,8 @@ convertWithOpts opts = do
429433 -- make sure writer is latex, beamer, context, html5 or ms
430434 unless (laTeXOutput || conTeXtOutput || html5Output ||
431435 msOutput) $
432− err 47 $ "cannot produce pdf output with " ++ format ++
433− " writer"
436+ liftIO $ E.throwIO $ PandocAppError 47 $
437+ "cannot produce pdf output with " ++ format ++ " writer"
434438
435439 let pdfprog = case () of
436440 _ | conTeXtOutput -> "context"
@@ -441,7 +445,8 @@ convertWithOpts opts = do
441445 -- check for pdf creating program
442446 mbPdfProg <- liftIO $ findExecutable pdfprog
443447 when (isNothing mbPdfProg) $
444− err 41 $ pdfprog ++ " not found. " ++
448+ liftIO $ E.throwIO $ PandocAppError 41 $
449+ pdfprog ++ " not found. " ++
445450 pdfprog ++ " is needed for pdf output."
446451
447452 res <- makePDF pdfprog f writerOptions verbosity media doc'
@@ -450,7 +455,7 @@ convertWithOpts opts = do
450455 Left err' -> liftIO $ do
451456 B.hPutStr stderr err'
452457 B.hPut stderr $ B.pack [10]
453− err 43 "Error producing PDF"
458+ E.throwIO $ PandocAppError 43 "Error producing PDF"
454459 | otherwise -> do
455460 let htmlFormat = format `elem`
456461 ["html","html4","html5","s5","slidy","slideous","dzslides","revealjs"]
@@ -492,19 +497,21 @@ externalFilter f args' d = liftIO $ do
492497 unless (exists && isExecutable) $ do
493498 mbExe <- findExecutable f'
494499 when (isNothing mbExe) $
495− err 83 $ "Error running filter " ++ f ++ ":\n" ++
496− "Could not find executable '" ++ f' ++ "'."
500+ E.throwIO $ PandocAppError 83 $
501+ "Error running filter " ++ f ++ ":\n" ++
502+ "Could not find executable '" ++ f' ++ "'."
497503 env <- getEnvironment
498504 let env' = Just $ ("PANDOC_VERSION", pandocVersion) : env
499505 (exitcode, outbs) <- E.handle filterException $
500506 pipeProcess env' f' args'' $ encode d
501507 case exitcode of
502508 ExitSuccess -> return $ either error id $ eitherDecode' outbs
503− ExitFailure ec -> err 83 $ "Error running filter " ++ f ++ "\n" ++
504− "Filter returned error status " ++ show ec
509+ ExitFailure ec -> E.throwIO $ PandocAppError 83 $
510+ "Error running filter " ++ f ++ "\n" ++
511+ "Filter returned error status " ++ show ec
505512 where filterException :: E.SomeException -> IO a
506− filterException e = err 83 $ "Error running filter " ++ f ++ "\n" ++
507− show e
513+ filterException e = E.throwIO $ PandocAppError 83 $
514+ "Error running filter " ++ f ++ "\n" ++ show e
508515
509516-- | Data structure for command line options.
510517data Opt = Opt
@@ -806,12 +813,14 @@ lookupHighlightStyle (Just s)
806813 | takeExtension s == ".theme" = -- attempt to load KDE theme
807814 do contents <- B.readFile s
808815 case parseTheme contents of
809− Left _ -> err 69 $ "Could not read highlighting theme " ++ s
816+ Left _ -> E.throwIO $ PandocAppError 69 $
817+ "Could not read highlighting theme " ++ s
810818 Right sty -> return (Just sty)
811819 | otherwise =
812820 case lookup (map toLower s) highlightingStyles of
813821 Just sty -> return (Just sty)
814− Nothing -> err 68 $ "Unknown highlight-style " ++ s
822+ Nothing -> E.throwIO $ PandocAppError 68 $
823+ "Unknown highlight-style " ++ s
815824
816825-- | A list of functions, each transforming the options data structure
817826-- in response to a command-line option.
@@ -847,8 +856,8 @@ options =
847856 case safeRead arg of
848857 Just t | t > 0 && t < 6 ->
849858 return opt{ optBaseHeaderLevel = t }
850− _ -> err 19
851− "base-header-level must be 1-5")
859+ _ -> E.throwIO $ PandocAppError 19
860+ "base-header-level must be 1-5")
852861 "NUMBER")
853862 "" -- "Headers base level"
854863
@@ -881,8 +890,8 @@ options =
881890 (\arg opt ->
882891 case safeRead arg of
883892 Just t | t > 0 -> return opt { optTabStop = t }
884− _ -> err 31
885− "tab-stop must be a number greater than 0")
893+ _ -> E.throwIO $ PandocAppError 31
894+ "tab-stop must be a number greater than 0")
886895 "NUMBER")
887896 "" -- "Tab stop (default 4)"
888897
@@ -893,7 +902,7 @@ options =
893902 "accept" -> return AcceptChanges
894903 "reject" -> return RejectChanges
895904 "all" -> return AllChanges
896− _ -> err 6
905+ _ -> E.throwIO $ PandocAppError 6
897906 ("Unknown option for track-changes: " ++ arg)
898907 return opt { optTrackChanges = action })
899908 "accept|reject|all")
@@ -964,7 +973,7 @@ options =
964973 (\arg opt ->
965974 case safeRead arg of
966975 Just t | t > 0 -> return opt { optDpi = t }
967− _ -> err 31
976+ _ -> E.throwIO $ PandocAppError 31
968977 "dpi must be a number greater than 0")
969978 "NUMBER")
970979 "" -- "Dpi (default 96)"
@@ -974,7 +983,8 @@ options =
974983 (\arg opt ->
975984 case safeRead ("Wrap" ++ uppercaseFirstLetter arg) of
976985 Just o -> return opt { optWrapText = o }
977− Nothing -> err 77 "--wrap must be auto, none, or preserve")
986+ Nothing -> E.throwIO $ PandocAppError 77
987+ "--wrap must be auto, none, or preserve")
978988 "auto|none|preserve")
979989 "" -- "Option for wrapping text in output"
980990
@@ -983,7 +993,7 @@ options =
983993 (\arg opt ->
984994 case safeRead arg of
985995 Just t | t > 0 -> return opt { optColumns = t }
986− _ -> err 33
996+ _ -> E.throwIO $ PandocAppError 33
987997 "columns must be a number greater than 0")
988998 "NUMBER")
989999 "" -- "Length of line in characters"
@@ -999,7 +1009,7 @@ options =
9991009 case safeRead arg of
10001010 Just t | t >= 1 && t <= 6 ->
10011011 return opt { optTOCDepth = t }
1002− _ -> err 57
1012+ _ -> E.throwIO $ PandocAppError 57
10031013 "TOC level must be a number between 1 and 6")
10041014 "NUMBER")
10051015 "" -- "Number of levels to include in TOC"
@@ -1075,7 +1085,7 @@ options =
10751085 "block" -> return EndOfBlock
10761086 "section" -> return EndOfSection
10771087 "document" -> return EndOfDocument
1078− _ -> err 6
1088+ _ -> E.throwIO $ PandocAppError 6
10791089 ("Unknown option for reference-location: " ++ arg)
10801090 return opt { optReferenceLocation = action })
10811091 "block|section|document")
@@ -1092,8 +1102,9 @@ options =
10921102 let tldName = "TopLevel" ++ uppercaseFirstLetter arg
10931103 case safeRead tldName of
10941104 Just tlDiv -> return opt { optTopLevelDivision = tlDiv }
1095− _ -> err 76 ("Top-level division must be " ++
1096− "section, chapter, part, or default"))
1105+ _ -> E.throwIO $ PandocAppError 76
1106+ ("Top-level division must be " ++
1107+ "section, chapter, part, or default"))
10971108 "section|chapter|part")
10981109 "" -- "Use top-level division type in LaTeX, ConTeXt, DocBook"
10991110
@@ -1108,7 +1119,8 @@ options =
11081119 case safeRead ('[':arg ++ "]") of
11091120 Just ns -> return opt { optNumberOffset = ns,
11101121 optNumberSections = True }
1111− _ -> err 57 "could not parse number-offset")
1122+ _ -> E.throwIO $ PandocAppError 57
1123+ "could not parse number-offset")
11121124 "NUMBERS")
11131125 "" -- "Starting number for sections, subsections, etc."
11141126
@@ -1128,7 +1140,7 @@ options =
11281140 case safeRead arg of
11291141 Just t | t >= 1 && t <= 6 ->
11301142 return opt { optSlideLevel = Just t }
1131− _ -> err 39
1143+ _ -> E.throwIO $ PandocAppError 39
11321144 "slide level must be a number between 1 and 6")
11331145 "NUMBER")
11341146 "" -- "Force header level for slides"
@@ -1151,7 +1163,7 @@ options =
11511163 "references" -> return ReferenceObfuscation
11521164 "javascript" -> return JavascriptObfuscation
11531165 "none" -> return NoObfuscation
1154− _ -> err 6
1166+ _ -> E.throwIO $ PandocAppError 6
11551167 ("Unknown obfuscation method: " ++ arg)
11561168 return opt { optEmailObfuscation = method })
11571169 "none|javascript|references")
@@ -1213,7 +1225,7 @@ options =
12131225 case safeRead arg of
12141226 Just t | t >= 1 && t <= 6 ->
12151227 return opt { optEpubChapterLevel = t }
1216− _ -> err 59
1228+ _ -> E.throwIO $ PandocAppError 59
12171229 "chapter level must be a number between 1 and 6")
12181230 "NUMBER")
12191231 "" -- "Header level at which to split chapters in EPUB"
@@ -1224,7 +1236,7 @@ options =
12241236 let b = takeBaseName arg
12251237 if b `elem` ["pdflatex", "lualatex", "xelatex"]
12261238 then return opt { optLaTeXEngine = arg }
1227− else err 45 "latex-engine must be pdflatex, lualatex, or xelatex.")
1239+ else E.throwIO $ PandocAppError 45 "latex-engine must be pdflatex, lualatex, or xelatex.")
12281240 "PROGRAM")
12291241 "" -- "Name of latex program to use in generating PDF"
12301242
src/Text/Pandoc/Error.hs+10−1
@@ -37,9 +37,11 @@ module Text.Pandoc.Error (
3737import Control.Exception (Exception)
3838import Data.Generics (Typeable)
3939import GHC.Generics (Generic)
40−import Text.Pandoc.Shared (err)
4140import Text.Parsec.Error
4241import Text.Parsec.Pos hiding (Line)
42+import qualified Text.Pandoc.UTF8 as UTF8
43+import System.Exit (exitWith, ExitCode(..))
44+import System.IO (stderr)
4345
4446type Input = String
4547
@@ -49,6 +51,7 @@ data PandocError = PandocIOError String IOError
4951 | PandocParseError String
5052 | PandocParsecError Input ParseError
5153 | PandocMakePDFError String
54+ | PandocAppError Int String
5255 deriving (Show, Typeable, Generic)
5356
5457instance Exception PandocError
@@ -74,4 +77,10 @@ handleError (Left e) =
7477 else ""
7578 in err 65 $ "\nError at " ++ show err' ++ errorInFile
7679 PandocMakePDFError s -> err 65 s
80+ PandocAppError ec s -> err ec s
7781
82+err :: Int -> String -> IO a
83+err exitCode msg = do
84+ UTF8.hPutStrLn stderr msg
85+ exitWith $ ExitFailure exitCode
86+ return undefined
src/Text/Pandoc/Shared.hs+4−12
@@ -82,7 +82,6 @@ module Text.Pandoc.Shared (
8282 collapseFilePath,
8383 filteredFilesFromArchive,
8484 -- * Error handling
85− err,
8685 mapLeft,
8786 -- * for squashing blocks
8887 blocksToInlines,
@@ -99,7 +98,6 @@ import Text.Pandoc.Walk
9998import Text.Pandoc.Builder (Inlines, Blocks, ToMetaValue(..))
10099import qualified Text.Pandoc.Builder as B
101100import qualified Text.Pandoc.UTF8 as UTF8
102−import System.Exit (exitWith, ExitCode(..))
103101import Data.Char ( toLower, isLower, isUpper, isAlpha,
104102 isLetter, isDigit, isSpace )
105103import Data.List ( find, stripPrefix, intercalate )
@@ -112,16 +110,15 @@ import System.Directory
112110import System.FilePath (splitDirectories, isPathSeparator)
113111import qualified System.FilePath.Posix as Posix
114112import Text.Pandoc.MIME (MimeType)
113+import Text.Pandoc.Error (PandocError(..))
115114import System.FilePath ( (</>) )
116115import Data.Generics (Typeable, Data)
117116import qualified Control.Monad.State as S
118−import Control.Monad.Trans (MonadIO (..))
119117import qualified Control.Exception as E
120118import Control.Monad (msum, unless, MonadPlus(..))
121119import Text.Pandoc.Pretty (charWidth)
122120import Text.Pandoc.Compat.Time
123121import Data.Time.Clock.POSIX
124−import System.IO (stderr)
125122import System.IO.Error
126123import System.IO.Temp
127124import Text.HTML.TagSoup (renderTagsOptions, RenderOptions(..), Tag(..),
@@ -677,7 +674,8 @@ readDefaultDataFile "reference.odt" =
677674readDefaultDataFile fname =
678675#ifdef EMBED_DATA_FILES
679676 case lookup (makeCanonical fname) dataFiles of
680− Nothing -> err 97 $ "Could not find data file " ++ fname
677+ Nothing -> E.throwIO $ PandocAppError 97 $
678+ "Could not find data file " ++ fname
681679 Just contents -> return contents
682680 where makeCanonical = Posix.joinPath . transformPathParts . splitDirectories
683681 transformPathParts = reverse . foldl go []
@@ -693,7 +691,7 @@ checkExistence fn = do
693691 exists <- doesFileExist fn
694692 if exists
695693 then return fn
696− else err 97 ("Could not find data file " ++ fn)
694+ else E.throwIO $ PandocAppError 97 ("Could not find data file " ++ fn)
697695#endif
698696
699697-- | Read file from specified user data directory or, if not found there, from
@@ -759,12 +757,6 @@ openURL u
759757-- Error reporting
760758--
761759
762−err :: MonadIO m => Int -> String -> m a
763−err exitCode msg = liftIO $ do
764− UTF8.hPutStrLn stderr msg
765− exitWith $ ExitFailure exitCode
766− return undefined
767−
768760mapLeft :: (a -> b) -> Either a c -> Either b c
769761mapLeft f (Left x) = Left (f x)
770762mapLeft _ (Right x) = Right x
src/Text/Pandoc/Templates.hs+1−1
@@ -41,7 +41,7 @@ module Text.Pandoc.Templates ( renderTemplate
4141 , Template
4242 , getDefaultTemplate ) where
4343
44−import qualified Control.Exception.Extensible as E (IOException, try)
44+import qualified Control.Exception as E (IOException, try)
4545import Data.Aeson (ToJSON (..))
4646import qualified Data.Text as T
4747import System.FilePath ((<.>), (</>))