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.
This commit is contained in:
John MacFarlane
2017-04-02 23:02:55 +02:00
parent 9e78a9d26b
commit 913db947a9
6 changed files with 72 additions and 57 deletions
-1
View File
@@ -296,7 +296,6 @@ Library
texmath >= 0.9.4 && < 0.10,
xml >= 1.3.12 && < 1.4,
random >= 1 && < 1.2,
extensible-exceptions >= 0.1 && < 0.2,
pandoc-types >= 1.17 && < 1.18,
aeson >= 0.7 && < 1.2,
aeson-pretty >= 0.8 && < 0.9,
+4 -1
View File
@@ -34,6 +34,9 @@ writers.
-}
module Main where
import Text.Pandoc.App (convertWithOpts, defaultOpts, options, parseOptions)
import Text.Pandoc.Error (handleError, PandocError)
import qualified Control.Exception as E
main :: IO ()
main = parseOptions options defaultOpts >>= convertWithOpts
main = E.catch (parseOptions options defaultOpts >>= convertWithOpts)
(\(e :: PandocError) -> handleError (Left e))
+53 -41
View File
@@ -39,7 +39,6 @@ module Text.Pandoc.App (
) where
import Control.Applicative ((<|>))
import qualified Control.Exception as E
import Control.Exception.Extensible (throwIO)
import Control.Monad
import Control.Monad.Trans
import Data.Aeson (eitherDecode', encode)
@@ -68,6 +67,7 @@ import System.FilePath
import System.IO (stderr, stdout)
import System.IO.Error (isDoesNotExistError)
import Text.Pandoc
import Text.Pandoc.Error (PandocError(..))
import Text.Pandoc.Builder (setMeta)
import Text.Pandoc.Class (PandocIO, getLog, withMediaBag)
import Text.Pandoc.Highlighting (highlightingStyles)
@@ -76,7 +76,7 @@ import Text.Pandoc.MediaBag (MediaBag, extractMediaBag, mediaDirectory)
import Text.Pandoc.PDF (makePDF)
import Text.Pandoc.Process (pipeProcess)
import Text.Pandoc.SelfContained (makeSelfContained, makeDataURI)
import Text.Pandoc.Shared (err, headerShift, openURL, readDataFile,
import Text.Pandoc.Shared (headerShift, openURL, readDataFile,
readDataFileUTF8, safeRead, tabFilter)
import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.Walk (walk)
@@ -98,7 +98,8 @@ parseOptions options' defaults = do
let unknownOptionErrors = foldr handleUnrecognizedOption [] unrecognizedOpts
unless (null errors && null unknownOptionErrors) $
err 2 $ concat errors ++ unlines unknownOptionErrors ++
E.throwIO $ PandocAppError 2 $
concat errors ++ unlines unknownOptionErrors ++
("Try " ++ prg ++ " --help for more information.")
-- thread option data structure through all supplied option actions
@@ -175,7 +176,7 @@ convertWithOpts opts = do
(\o d -> liftIO $ writeCustom writerName o d)
:: Writer PandocIO)
else case getWriter writerName of
Left e -> err 9 $
Left e -> E.throwIO $ PandocAppError 9 $
if format == "pdf"
then e ++
"\nTo create a pdf with pandoc, use " ++
@@ -189,7 +190,7 @@ convertWithOpts opts = do
-- the sake of the text2tags reader.
reader <- case getReader readerName of
Right r -> return (r :: Reader PandocIO)
Left e -> err 7 e'
Left e -> E.throwIO $ PandocAppError 7 e'
where e' = case readerName of
"pdf" -> e ++
"\nPandoc can convert to PDF, but not from PDF."
@@ -204,7 +205,7 @@ convertWithOpts opts = do
Nothing -> do
deftemp <- getDefaultTemplate datadir format
case deftemp of
Left e -> throwIO e
Left e -> E.throwIO e
Right t -> return (Just t)
Just tp -> do
-- strip off extensions
@@ -217,8 +218,8 @@ convertWithOpts opts = do
(readDataFileUTF8 datadir
("templates" </> tp'))
(\e' -> let _ = (e' :: E.SomeException)
in throwIO e')
else throwIO e)
in E.throwIO e')
else E.throwIO e)
let addStringAsVariable varname s vars = return $ (varname, s) : vars
@@ -304,7 +305,7 @@ convertWithOpts opts = do
let addSyntaxMap existingmap f = do
res <- parseSyntaxDefinition f
case res of
Left errstr -> err 67 errstr
Left errstr -> E.throwIO $ PandocAppError 67 errstr
Right syn -> return $ addSyntaxDefinition syn existingmap
syntaxMap <- foldM addSyntaxMap defaultSyntaxMap
@@ -312,7 +313,8 @@ convertWithOpts opts = do
case missingIncludes (M.elems syntaxMap) of
[] -> return ()
xs -> err 73 $ "Missing syntax definitions:\n" ++
xs -> E.throwIO $ PandocAppError 73 $
"Missing syntax definitions:\n" ++
unlines (map
(\(syn,dep) -> (T.unpack syn ++ " requires " ++
T.unpack dep ++ " through IncludeRules.")) xs)
@@ -358,7 +360,8 @@ convertWithOpts opts = do
istty <- queryTerminal stdOutput
#endif
when (istty && not (isTextFormat format) && outputFile == "-") $
err 5 $ "Cannot write " ++ format ++ " output to stdout.\n" ++
E.throwIO $ PandocAppError 5 $
"Cannot write " ++ format ++ " output to stdout.\n" ++
"Specify an output file using the -o option."
@@ -386,7 +389,8 @@ convertWithOpts opts = do
Just logfile -> B.writeFile logfile (encodeLogMessages reports)
let isWarning msg = messageVerbosity msg == WARNING
when (optFailIfWarnings opts && any isWarning reports) $
err 3 "Failing because there were warnings."
E.throwIO $
PandocAppError 3 "Failing because there were warnings."
return res
let sourceToDoc :: [FilePath] -> PandocIO (Pandoc, MediaBag)
@@ -429,8 +433,8 @@ convertWithOpts opts = do
-- make sure writer is latex, beamer, context, html5 or ms
unless (laTeXOutput || conTeXtOutput || html5Output ||
msOutput) $
err 47 $ "cannot produce pdf output with " ++ format ++
" writer"
liftIO $ E.throwIO $ PandocAppError 47 $
"cannot produce pdf output with " ++ format ++ " writer"
let pdfprog = case () of
_ | conTeXtOutput -> "context"
@@ -441,7 +445,8 @@ convertWithOpts opts = do
-- check for pdf creating program
mbPdfProg <- liftIO $ findExecutable pdfprog
when (isNothing mbPdfProg) $
err 41 $ pdfprog ++ " not found. " ++
liftIO $ E.throwIO $ PandocAppError 41 $
pdfprog ++ " not found. " ++
pdfprog ++ " is needed for pdf output."
res <- makePDF pdfprog f writerOptions verbosity media doc'
@@ -450,7 +455,7 @@ convertWithOpts opts = do
Left err' -> liftIO $ do
B.hPutStr stderr err'
B.hPut stderr $ B.pack [10]
err 43 "Error producing PDF"
E.throwIO $ PandocAppError 43 "Error producing PDF"
| otherwise -> do
let htmlFormat = format `elem`
["html","html4","html5","s5","slidy","slideous","dzslides","revealjs"]
@@ -492,19 +497,21 @@ externalFilter f args' d = liftIO $ do
unless (exists && isExecutable) $ do
mbExe <- findExecutable f'
when (isNothing mbExe) $
err 83 $ "Error running filter " ++ f ++ ":\n" ++
"Could not find executable '" ++ f' ++ "'."
E.throwIO $ PandocAppError 83 $
"Error running filter " ++ f ++ ":\n" ++
"Could not find executable '" ++ f' ++ "'."
env <- getEnvironment
let env' = Just $ ("PANDOC_VERSION", pandocVersion) : env
(exitcode, outbs) <- E.handle filterException $
pipeProcess env' f' args'' $ encode d
case exitcode of
ExitSuccess -> return $ either error id $ eitherDecode' outbs
ExitFailure ec -> err 83 $ "Error running filter " ++ f ++ "\n" ++
"Filter returned error status " ++ show ec
ExitFailure ec -> E.throwIO $ PandocAppError 83 $
"Error running filter " ++ f ++ "\n" ++
"Filter returned error status " ++ show ec
where filterException :: E.SomeException -> IO a
filterException e = err 83 $ "Error running filter " ++ f ++ "\n" ++
show e
filterException e = E.throwIO $ PandocAppError 83 $
"Error running filter " ++ f ++ "\n" ++ show e
-- | Data structure for command line options.
data Opt = Opt
@@ -806,12 +813,14 @@ lookupHighlightStyle (Just s)
| takeExtension s == ".theme" = -- attempt to load KDE theme
do contents <- B.readFile s
case parseTheme contents of
Left _ -> err 69 $ "Could not read highlighting theme " ++ s
Left _ -> E.throwIO $ PandocAppError 69 $
"Could not read highlighting theme " ++ s
Right sty -> return (Just sty)
| otherwise =
case lookup (map toLower s) highlightingStyles of
Just sty -> return (Just sty)
Nothing -> err 68 $ "Unknown highlight-style " ++ s
Nothing -> E.throwIO $ PandocAppError 68 $
"Unknown highlight-style " ++ s
-- | A list of functions, each transforming the options data structure
-- in response to a command-line option.
@@ -847,8 +856,8 @@ options =
case safeRead arg of
Just t | t > 0 && t < 6 ->
return opt{ optBaseHeaderLevel = t }
_ -> err 19
"base-header-level must be 1-5")
_ -> E.throwIO $ PandocAppError 19
"base-header-level must be 1-5")
"NUMBER")
"" -- "Headers base level"
@@ -881,8 +890,8 @@ options =
(\arg opt ->
case safeRead arg of
Just t | t > 0 -> return opt { optTabStop = t }
_ -> err 31
"tab-stop must be a number greater than 0")
_ -> E.throwIO $ PandocAppError 31
"tab-stop must be a number greater than 0")
"NUMBER")
"" -- "Tab stop (default 4)"
@@ -893,7 +902,7 @@ options =
"accept" -> return AcceptChanges
"reject" -> return RejectChanges
"all" -> return AllChanges
_ -> err 6
_ -> E.throwIO $ PandocAppError 6
("Unknown option for track-changes: " ++ arg)
return opt { optTrackChanges = action })
"accept|reject|all")
@@ -964,7 +973,7 @@ options =
(\arg opt ->
case safeRead arg of
Just t | t > 0 -> return opt { optDpi = t }
_ -> err 31
_ -> E.throwIO $ PandocAppError 31
"dpi must be a number greater than 0")
"NUMBER")
"" -- "Dpi (default 96)"
@@ -974,7 +983,8 @@ options =
(\arg opt ->
case safeRead ("Wrap" ++ uppercaseFirstLetter arg) of
Just o -> return opt { optWrapText = o }
Nothing -> err 77 "--wrap must be auto, none, or preserve")
Nothing -> E.throwIO $ PandocAppError 77
"--wrap must be auto, none, or preserve")
"auto|none|preserve")
"" -- "Option for wrapping text in output"
@@ -983,7 +993,7 @@ options =
(\arg opt ->
case safeRead arg of
Just t | t > 0 -> return opt { optColumns = t }
_ -> err 33
_ -> E.throwIO $ PandocAppError 33
"columns must be a number greater than 0")
"NUMBER")
"" -- "Length of line in characters"
@@ -999,7 +1009,7 @@ options =
case safeRead arg of
Just t | t >= 1 && t <= 6 ->
return opt { optTOCDepth = t }
_ -> err 57
_ -> E.throwIO $ PandocAppError 57
"TOC level must be a number between 1 and 6")
"NUMBER")
"" -- "Number of levels to include in TOC"
@@ -1075,7 +1085,7 @@ options =
"block" -> return EndOfBlock
"section" -> return EndOfSection
"document" -> return EndOfDocument
_ -> err 6
_ -> E.throwIO $ PandocAppError 6
("Unknown option for reference-location: " ++ arg)
return opt { optReferenceLocation = action })
"block|section|document")
@@ -1092,8 +1102,9 @@ options =
let tldName = "TopLevel" ++ uppercaseFirstLetter arg
case safeRead tldName of
Just tlDiv -> return opt { optTopLevelDivision = tlDiv }
_ -> err 76 ("Top-level division must be " ++
"section, chapter, part, or default"))
_ -> E.throwIO $ PandocAppError 76
("Top-level division must be " ++
"section, chapter, part, or default"))
"section|chapter|part")
"" -- "Use top-level division type in LaTeX, ConTeXt, DocBook"
@@ -1108,7 +1119,8 @@ options =
case safeRead ('[':arg ++ "]") of
Just ns -> return opt { optNumberOffset = ns,
optNumberSections = True }
_ -> err 57 "could not parse number-offset")
_ -> E.throwIO $ PandocAppError 57
"could not parse number-offset")
"NUMBERS")
"" -- "Starting number for sections, subsections, etc."
@@ -1128,7 +1140,7 @@ options =
case safeRead arg of
Just t | t >= 1 && t <= 6 ->
return opt { optSlideLevel = Just t }
_ -> err 39
_ -> E.throwIO $ PandocAppError 39
"slide level must be a number between 1 and 6")
"NUMBER")
"" -- "Force header level for slides"
@@ -1151,7 +1163,7 @@ options =
"references" -> return ReferenceObfuscation
"javascript" -> return JavascriptObfuscation
"none" -> return NoObfuscation
_ -> err 6
_ -> E.throwIO $ PandocAppError 6
("Unknown obfuscation method: " ++ arg)
return opt { optEmailObfuscation = method })
"none|javascript|references")
@@ -1213,7 +1225,7 @@ options =
case safeRead arg of
Just t | t >= 1 && t <= 6 ->
return opt { optEpubChapterLevel = t }
_ -> err 59
_ -> E.throwIO $ PandocAppError 59
"chapter level must be a number between 1 and 6")
"NUMBER")
"" -- "Header level at which to split chapters in EPUB"
@@ -1224,7 +1236,7 @@ options =
let b = takeBaseName arg
if b `elem` ["pdflatex", "lualatex", "xelatex"]
then return opt { optLaTeXEngine = arg }
else err 45 "latex-engine must be pdflatex, lualatex, or xelatex.")
else E.throwIO $ PandocAppError 45 "latex-engine must be pdflatex, lualatex, or xelatex.")
"PROGRAM")
"" -- "Name of latex program to use in generating PDF"
+10 -1
View File
@@ -37,9 +37,11 @@ module Text.Pandoc.Error (
import Control.Exception (Exception)
import Data.Generics (Typeable)
import GHC.Generics (Generic)
import Text.Pandoc.Shared (err)
import Text.Parsec.Error
import Text.Parsec.Pos hiding (Line)
import qualified Text.Pandoc.UTF8 as UTF8
import System.Exit (exitWith, ExitCode(..))
import System.IO (stderr)
type Input = String
@@ -49,6 +51,7 @@ data PandocError = PandocIOError String IOError
| PandocParseError String
| PandocParsecError Input ParseError
| PandocMakePDFError String
| PandocAppError Int String
deriving (Show, Typeable, Generic)
instance Exception PandocError
@@ -74,4 +77,10 @@ handleError (Left e) =
else ""
in err 65 $ "\nError at " ++ show err' ++ errorInFile
PandocMakePDFError s -> err 65 s
PandocAppError ec s -> err ec s
err :: Int -> String -> IO a
err exitCode msg = do
UTF8.hPutStrLn stderr msg
exitWith $ ExitFailure exitCode
return undefined
+4 -12
View File
@@ -82,7 +82,6 @@ module Text.Pandoc.Shared (
collapseFilePath,
filteredFilesFromArchive,
-- * Error handling
err,
mapLeft,
-- * for squashing blocks
blocksToInlines,
@@ -99,7 +98,6 @@ import Text.Pandoc.Walk
import Text.Pandoc.Builder (Inlines, Blocks, ToMetaValue(..))
import qualified Text.Pandoc.Builder as B
import qualified Text.Pandoc.UTF8 as UTF8
import System.Exit (exitWith, ExitCode(..))
import Data.Char ( toLower, isLower, isUpper, isAlpha,
isLetter, isDigit, isSpace )
import Data.List ( find, stripPrefix, intercalate )
@@ -112,16 +110,15 @@ import System.Directory
import System.FilePath (splitDirectories, isPathSeparator)
import qualified System.FilePath.Posix as Posix
import Text.Pandoc.MIME (MimeType)
import Text.Pandoc.Error (PandocError(..))
import System.FilePath ( (</>) )
import Data.Generics (Typeable, Data)
import qualified Control.Monad.State as S
import Control.Monad.Trans (MonadIO (..))
import qualified Control.Exception as E
import Control.Monad (msum, unless, MonadPlus(..))
import Text.Pandoc.Pretty (charWidth)
import Text.Pandoc.Compat.Time
import Data.Time.Clock.POSIX
import System.IO (stderr)
import System.IO.Error
import System.IO.Temp
import Text.HTML.TagSoup (renderTagsOptions, RenderOptions(..), Tag(..),
@@ -677,7 +674,8 @@ readDefaultDataFile "reference.odt" =
readDefaultDataFile fname =
#ifdef EMBED_DATA_FILES
case lookup (makeCanonical fname) dataFiles of
Nothing -> err 97 $ "Could not find data file " ++ fname
Nothing -> E.throwIO $ PandocAppError 97 $
"Could not find data file " ++ fname
Just contents -> return contents
where makeCanonical = Posix.joinPath . transformPathParts . splitDirectories
transformPathParts = reverse . foldl go []
@@ -693,7 +691,7 @@ checkExistence fn = do
exists <- doesFileExist fn
if exists
then return fn
else err 97 ("Could not find data file " ++ fn)
else E.throwIO $ PandocAppError 97 ("Could not find data file " ++ fn)
#endif
-- | Read file from specified user data directory or, if not found there, from
@@ -759,12 +757,6 @@ openURL u
-- Error reporting
--
err :: MonadIO m => Int -> String -> m a
err exitCode msg = liftIO $ do
UTF8.hPutStrLn stderr msg
exitWith $ ExitFailure exitCode
return undefined
mapLeft :: (a -> b) -> Either a c -> Either b c
mapLeft f (Left x) = Left (f x)
mapLeft _ (Right x) = Right x
+1 -1
View File
@@ -41,7 +41,7 @@ module Text.Pandoc.Templates ( renderTemplate
, Template
, getDefaultTemplate ) where
import qualified Control.Exception.Extensible as E (IOException, try)
import qualified Control.Exception as E (IOException, try)
import Data.Aeson (ToJSON (..))
import qualified Data.Text as T
import System.FilePath ((<.>), (</>))