Raise error on unsupported extensions. Closes #4338.

+ An error is now raised if you try to specify (enable or
  disable) an extension that does not affect the given
  format, e.g. `docx+pipe_tables`.

+ The `--list-extensions[=FORMAT]` option now lists only
  extensions that affect the given FORMAT.

+ Text.Pandoc.Error: Add constructors `PandocUnknownReaderError`,
  `PandocUnknownWriterError`, `PandocUnsupportedExtensionError`.
  [API change]

+ Text.Pandoc.Extensions now exports `getAllExtensions`,
  which returns the extensions that affect a given format
  (whether enabled by default or not). [API change]

+ Text.Pandoc.Extensions: change type of `parseFormatSpec`
  from `Either ParseError (String, Extensions -> Extensions)`
  to `Either ParseError (String, [Extension], [Extension])`
  [API change].

+ Text.Pandoc.Readers: change type of `getReader` so it returns
  a value in the PandocMonad instance rather than an Either
  [API change].  Exceptions for unknown formats and unsupported
  extensions are now raised by this function and need not be handled by
  the calling function.

+ Text.Pandoc.Writers: change type of `getWriter` so it returns
  a value in the PandocMonad instance rather than an Either
  [API change].  Exceptions for unknown formats and unsupported
  extensions are now raised by this function and need not be handled by
  the calling function.
This commit is contained in:
John MacFarlane 2019-09-28 14:47:41 -07:00
parent 03d4e6b9ef
commit 746c92a41a
9 changed files with 263 additions and 72 deletions

View file

@ -399,7 +399,7 @@ General options {.options}
`--list-extensions`[`=`*FORMAT*] `--list-extensions`[`=`*FORMAT*]
: List supported extensions, one per line, preceded : List supported extensions for *FORMAT*, one per line, preceded
by a `+` or `-` indicating whether it is enabled by default by a `+` or `-` indicating whether it is enabled by default
in *FORMAT*. If *FORMAT* is not specified, defaults for in *FORMAT*. If *FORMAT* is not specified, defaults for
pandoc's Markdown are given. pandoc's Markdown are given.
@ -1391,6 +1391,9 @@ Nonzero exit codes have the following meanings:
4 PandocAppError 4 PandocAppError
5 PandocTemplateError 5 PandocTemplateError
6 PandocOptionError 6 PandocOptionError
21 PandocUnknownReaderError
22 PandocUnknownWriterError
23 PandocUnsupportedExtensionError
31 PandocEpubSubdirectoryError 31 PandocEpubSubdirectoryError
43 PandocPDFError 43 PandocPDFError
47 PandocPDFProgramNotFoundError 47 PandocPDFProgramNotFoundError

View file

@ -155,16 +155,7 @@ convertWithOpts opts = do
<> "` instead of `pandoc " <> inputFile <> " -o " <> outputFile <> "`." <> "` instead of `pandoc " <> inputFile <> " -o " <> outputFile <> "`."
_ -> return () _ -> return ()
(reader, readerExts) <- (reader :: Reader PandocIO, readerExts) <- getReader readerName
case getReader readerName of
Right (r, es) -> return (r :: Reader PandocIO, es)
Left e -> throwError $ PandocAppError e'
where e' = case readerName of
"pdf" -> e ++
"\nPandoc can convert to PDF, but not from PDF."
"doc" -> e ++
"\nPandoc can convert from DOCX, but not from DOC.\nTry using Word to save your DOC file as DOCX, and convert that with pandoc."
_ -> e
let convertTabs = tabFilter (if optPreserveTabs opts || let convertTabs = tabFilter (if optPreserveTabs opts ||
readerName == "t2t" || readerName == "t2t" ||

View file

@ -768,12 +768,25 @@ options =
, Option "" ["list-extensions"] , Option "" ["list-extensions"]
(OptArg (OptArg
(\arg _ -> do (\arg _ -> do
let exts = getDefaultExtensions (fromMaybe "markdown" arg) let extList :: [Extension]
let showExt x = (if extensionEnabled x exts extList = [minBound..maxBound]
then '+' let allExts =
else '-') : drop 4 (show x) case arg of
Nothing -> extensionsFromList extList
Just fmt -> getAllExtensions fmt
let defExts =
case arg of
Nothing -> getDefaultExtensions
"markdown"
Just fmt -> getDefaultExtensions fmt
let showExt x =
(if extensionEnabled x defExts
then '+'
else if extensionEnabled x allExts
then '-'
else ' ') : drop 4 (show x)
mapM_ (UTF8.hPutStrLn stdout . showExt) mapM_ (UTF8.hPutStrLn stdout . showExt)
([minBound..maxBound] :: [Extension]) [ex | ex <- extList, extensionEnabled ex allExts]
exitSuccess ) exitSuccess )
"FORMAT") "FORMAT")
"" ""

View file

@ -85,18 +85,12 @@ optToOutputSettings opts = do
then writerName then writerName
else map toLower $ baseWriterName writerName else map toLower $ baseWriterName writerName
(writer, writerExts) <- (writer :: Writer PandocIO, writerExts) <-
if ".lua" `isSuffixOf` format if ".lua" `isSuffixOf` format
then return (TextWriter then return (TextWriter
(\o d -> writeCustom writerName o d) (\o d -> writeCustom writerName o d)
:: Writer PandocIO, mempty) :: Writer PandocIO, mempty)
else case getWriter (map toLower writerName) of else getWriter (map toLower writerName)
Left e -> throwError $ PandocAppError $
if format == "pdf"
then e ++ "\n" ++ pdfIsNoWriterErrorMsg
else e
Right (w, es) -> return (w :: Writer PandocIO, es)
let standalone = optStandalone opts || not (isTextFormat format) || pdfOutput let standalone = optStandalone opts || not (isTextFormat format) || pdfOutput
@ -249,13 +243,6 @@ optToOutputSettings opts = do
baseWriterName :: String -> String baseWriterName :: String -> String
baseWriterName = takeWhile (\c -> c /= '+' && c /= '-') baseWriterName = takeWhile (\c -> c /= '+' && c /= '-')
pdfIsNoWriterErrorMsg :: String
pdfIsNoWriterErrorMsg =
"To create a pdf using pandoc, use " ++
"-t latex|beamer|context|ms|html5" ++
"\nand specify an output file with " ++
".pdf extension (-o filename.pdf)."
pdfWriterAndProg :: Maybe String -- ^ user-specified writer name pdfWriterAndProg :: Maybe String -- ^ user-specified writer name
-> Maybe String -- ^ user-specified pdf-engine -> Maybe String -- ^ user-specified pdf-engine
-> IO (String, Maybe String) -- ^ IO (writerName, maybePdfEngineProg) -> IO (String, Maybe String) -- ^ IO (writerName, maybePdfEngineProg)
@ -263,6 +250,8 @@ pdfWriterAndProg mWriter mEngine = do
let panErr msg = liftIO $ E.throwIO $ PandocAppError msg let panErr msg = liftIO $ E.throwIO $ PandocAppError msg
case go mWriter mEngine of case go mWriter mEngine of
Right (writ, prog) -> return (writ, Just prog) Right (writ, prog) -> return (writ, Just prog)
Left "pdf writer" -> liftIO $ E.throwIO $
PandocUnknownWriterError "pdf"
Left err -> panErr err Left err -> panErr err
where where
go Nothing Nothing = Right ("latex", "pdflatex") go Nothing Nothing = Right ("latex", "pdflatex")
@ -279,7 +268,7 @@ pdfWriterAndProg mWriter mEngine = do
[] -> Left $ [] -> Left $
"pdf-engine " ++ eng ++ " not known" "pdf-engine " ++ eng ++ " not known"
engineForWriter "pdf" = Left pdfIsNoWriterErrorMsg engineForWriter "pdf" = Left "pdf writer"
engineForWriter w = case [e | (f,e) <- engines, f == baseWriterName w] of engineForWriter w = case [e | (f,e) <- engines, f == baseWriterName w] of
eng : _ -> Right eng eng : _ -> Right eng
[] -> Left $ [] -> Left $

View file

@ -54,6 +54,9 @@ data PandocError = PandocIOError String IOError
| PandocMacroLoop String | PandocMacroLoop String
| PandocUTF8DecodingError String Int Word8 | PandocUTF8DecodingError String Int Word8
| PandocIpynbDecodingError String | PandocIpynbDecodingError String
| PandocUnknownReaderError String
| PandocUnknownWriterError String
| PandocUnsupportedExtensionError String String
deriving (Show, Typeable, Generic) deriving (Show, Typeable, Generic)
instance Exception PandocError instance Exception PandocError
@ -112,6 +115,26 @@ handleError (Left e) =
"The input must be a UTF-8 encoded text." "The input must be a UTF-8 encoded text."
PandocIpynbDecodingError w -> err 93 $ PandocIpynbDecodingError w -> err 93 $
"ipynb decoding error: " ++ w "ipynb decoding error: " ++ w
PandocUnknownReaderError r -> err 21 $
"Unknown input format " ++ r ++
case r of
"doc" -> "\nPandoc can convert from DOCX, but not from DOC." ++
"\nTry using Word to save your DOC file as DOCX," ++
" and convert that with pandoc."
"pdf" -> "\nPandoc can convert to PDF, but not from PDF."
_ -> ""
PandocUnknownWriterError w -> err 22 $
"Unknown output format " ++ w ++
case w of
"pdf" -> "To create a pdf using pandoc, use" ++
" -t latex|beamer|context|ms|html5" ++
"\nand specify an output file with " ++
".pdf extension (-o filename.pdf)."
"doc" -> "\nPandoc can convert to DOCX, but not from DOC."
_ -> ""
PandocUnsupportedExtensionError ext f -> err 23 $
"The extension " ++ ext ++ " is not supported " ++
"for " ++ f
err :: Int -> String -> IO a err :: Int -> String -> IO a
err exitCode msg = do err exitCode msg = do

View file

@ -26,6 +26,7 @@ module Text.Pandoc.Extensions ( Extension(..)
, enableExtension , enableExtension
, disableExtension , disableExtension
, getDefaultExtensions , getDefaultExtensions
, getAllExtensions
, pandocExtensions , pandocExtensions
, plainExtensions , plainExtensions
, strictExtensions , strictExtensions
@ -312,12 +313,12 @@ strictExtensions = extensionsFromList
-- | Default extensions from format-describing string. -- | Default extensions from format-describing string.
getDefaultExtensions :: String -> Extensions getDefaultExtensions :: String -> Extensions
getDefaultExtensions "markdown_strict" = strictExtensions getDefaultExtensions "markdown_strict" = strictExtensions
getDefaultExtensions "markdown_phpextra" = phpMarkdownExtraExtensions getDefaultExtensions "markdown_phpextra" = phpMarkdownExtraExtensions
getDefaultExtensions "markdown_mmd" = multimarkdownExtensions getDefaultExtensions "markdown_mmd" = multimarkdownExtensions
getDefaultExtensions "markdown_github" = githubMarkdownExtensions getDefaultExtensions "markdown_github" = githubMarkdownExtensions
getDefaultExtensions "markdown" = pandocExtensions getDefaultExtensions "markdown" = pandocExtensions
getDefaultExtensions "ipynb" = getDefaultExtensions "ipynb" =
extensionsFromList extensionsFromList
[ Ext_all_symbols_escapable [ Ext_all_symbols_escapable
, Ext_pipe_tables , Ext_pipe_tables
@ -379,16 +380,149 @@ getDefaultExtensions "opml" = pandocExtensions -- affects notes
getDefaultExtensions _ = extensionsFromList getDefaultExtensions _ = extensionsFromList
[Ext_auto_identifiers] [Ext_auto_identifiers]
-- | Parse a format-specifying string into a markup format and a function that allMarkdownExtensions :: Extensions
-- takes Extensions and enables and disables extensions as defined in the format allMarkdownExtensions =
-- spec. pandocExtensions <>
extensionsFromList
[ Ext_old_dashes
, Ext_angle_brackets_escapable
, Ext_lists_without_preceding_blankline
, Ext_four_space_rule
, Ext_spaced_reference_links
, Ext_hard_line_breaks
, Ext_ignore_line_breaks
, Ext_east_asian_line_breaks
, Ext_emoji
, Ext_tex_math_single_backslash
, Ext_tex_math_double_backslash
, Ext_markdown_attribute
, Ext_mmd_title_block
, Ext_abbreviations
, Ext_autolink_bare_uris
, Ext_mmd_link_attributes
, Ext_mmd_header_identifiers
, Ext_compact_definition_lists
, Ext_gutenberg
, Ext_smart
, Ext_literate_haskell
]
-- | Get all valid extensions for a format. This is used
-- mainly in checking format specifications for validity.
getAllExtensions :: String -> Extensions
getAllExtensions f = universalExtensions <> getAll f
where
autoIdExtensions = extensionsFromList
[ Ext_auto_identifiers
, Ext_gfm_auto_identifiers
, Ext_ascii_identifiers
]
universalExtensions = extensionsFromList
[ Ext_east_asian_line_breaks ]
getAll "markdown_strict" = allMarkdownExtensions
getAll "markdown_phpextra" = allMarkdownExtensions
getAll "markdown_mmd" = allMarkdownExtensions
getAll "markdown_github" = allMarkdownExtensions
getAll "markdown" = allMarkdownExtensions
getAll "ipynb" = allMarkdownExtensions
getAll "docx" = extensionsFromList
[ Ext_empty_paragraphs
, Ext_styles
]
getAll "opendocument" = extensionsFromList
[ Ext_empty_paragraphs
, Ext_native_numbering
]
getAll "odt" = getAll "opendocument" <> autoIdExtensions
getAll "muse" = autoIdExtensions <>
extensionsFromList
[ Ext_amuse ]
getAll "asciidoc" = autoIdExtensions
getAll "plain" = allMarkdownExtensions
getAll "gfm" = githubMarkdownExtensions <>
autoIdExtensions <>
extensionsFromList
[ Ext_raw_html
, Ext_raw_tex -- only supported in writer (for math)
, Ext_hard_line_breaks
, Ext_smart
]
getAll "commonmark" = getAll "gfm"
getAll "org" = autoIdExtensions <>
extensionsFromList
[ Ext_citations
, Ext_smart
]
getAll "html" = autoIdExtensions <>
extensionsFromList
[ Ext_native_divs
, Ext_line_blocks
, Ext_native_spans
, Ext_empty_paragraphs
, Ext_raw_html
, Ext_raw_tex
, Ext_task_lists
, Ext_tex_math_dollars
, Ext_tex_math_single_backslash
, Ext_tex_math_double_backslash
, Ext_literate_haskell
, Ext_epub_html_exts
]
getAll "html4" = getAll "html"
getAll "html5" = getAll "html"
getAll "epub" = getAll "html"
getAll "epub2" = getAll "epub"
getAll "epub3" = getAll "epub"
getAll "latex" = autoIdExtensions <>
extensionsFromList
[ Ext_smart
, Ext_latex_macros
, Ext_raw_tex
, Ext_task_lists
, Ext_literate_haskell
]
getAll "beamer" = getAll "latex"
getAll "context" = autoIdExtensions <>
extensionsFromList
[ Ext_smart
, Ext_raw_tex
, Ext_ntb
]
getAll "textile" = autoIdExtensions <>
extensionsFromList
[ Ext_old_dashes
, Ext_smart
, Ext_raw_tex
]
getAll "opml" = allMarkdownExtensions -- affects notes
getAll "twiki" = autoIdExtensions <>
extensionsFromList
[ Ext_smart ]
getAll "vimwiki" = autoIdExtensions
getAll "dokuwiki" = autoIdExtensions
getAll "tikiwiki" = autoIdExtensions
getAll "rst" = autoIdExtensions <>
extensionsFromList
[ Ext_smart
, Ext_literate_haskell
]
getAll "mediawiki" = autoIdExtensions <>
extensionsFromList
[ Ext_smart ]
getAll _ = mempty
-- | Parse a format-specifying string into a markup format,
-- a set of extensions to enable, and a set of extensions to disable.
parseFormatSpec :: String parseFormatSpec :: String
-> Either ParseError (String, Extensions -> Extensions) -> Either ParseError (String, [Extension], [Extension])
parseFormatSpec = parse formatSpec "" parseFormatSpec = parse formatSpec ""
where formatSpec = do where formatSpec = do
name <- formatName name <- formatName
extMods <- many extMod (extsToEnable, extsToDisable) <- foldl (flip ($)) ([],[]) <$>
return (name, \x -> foldl (flip ($)) x extMods) many extMod
return (name, reverse extsToEnable, reverse extsToDisable)
formatName = many1 $ noneOf "-+" formatName = many1 $ noneOf "-+"
extMod = do extMod = do
polarity <- oneOf "-+" polarity <- oneOf "-+"
@ -397,10 +531,12 @@ parseFormatSpec = parse formatSpec ""
Just n -> return n Just n -> return n
Nothing Nothing
| name == "lhs" -> return Ext_literate_haskell | name == "lhs" -> return Ext_literate_haskell
| otherwise -> Prelude.fail $ "Unknown extension: " ++ name | otherwise -> Prelude.fail $
return $ case polarity of "Unknown extension: " ++ name
'-' -> disableExtension ext return $ \(extsToEnable, extsToDisable) ->
_ -> enableExtension ext case polarity of
'+' -> (ext : extsToEnable, extsToDisable)
_ -> (extsToEnable, ext : extsToDisable)
#ifdef DERIVE_JSON_VIA_TH #ifdef DERIVE_JSON_VIA_TH
$(deriveJSON defaultOptions ''Extension) $(deriveJSON defaultOptions ''Extension)

View file

@ -16,6 +16,7 @@ module Text.Pandoc.Lua.Module.Pandoc
import Prelude import Prelude
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.Except (throwError)
import Data.Default (Default (..)) import Data.Default (Default (..))
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text (pack) import Data.Text (pack)
@ -34,6 +35,7 @@ import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.ByteString.Lazy.Char8 as BSL
import qualified Foreign.Lua as Lua import qualified Foreign.Lua as Lua
import qualified Text.Pandoc.Lua.Util as LuaUtil import qualified Text.Pandoc.Lua.Util as LuaUtil
import Text.Pandoc.Error
-- | Push the "pandoc" on the lua stack. Requires the `list` module to be -- | Push the "pandoc" on the lua stack. Requires the `list` module to be
-- loaded. -- loaded.
@ -60,17 +62,20 @@ walkBlock = walkElement
readDoc :: String -> Optional String -> Lua NumResults readDoc :: String -> Optional String -> Lua NumResults
readDoc content formatSpecOrNil = do readDoc content formatSpecOrNil = do
let formatSpec = fromMaybe "markdown" (Lua.fromOptional formatSpecOrNil) let formatSpec = fromMaybe "markdown" (Lua.fromOptional formatSpecOrNil)
case getReader formatSpec of res <- Lua.liftIO . runIO $
Left s -> Lua.raiseError s -- Unknown reader getReader formatSpec >>= \(rdr,es) ->
Right (reader, es) -> case rdr of
case reader of TextReader r ->
TextReader r -> do
res <- Lua.liftIO . runIO $
r def{ readerExtensions = es } (pack content) r def{ readerExtensions = es } (pack content)
case res of _ -> throwError $ PandocSomeError $
Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc "Only textual formats are supported"
Left s -> Lua.raiseError (show s) -- error while reading case res of
_ -> Lua.raiseError "Only string formats are supported at the moment." Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc
Left (PandocUnknownReaderError f) -> Lua.raiseError $
"Unknown reader: " ++ f
Left (PandocUnsupportedExtensionError e f) -> Lua.raiseError $
"Extension " ++ e ++ " not supported for " ++ f
Left e -> Lua.raiseError $ show e
-- | Pipes input through a command. -- | Pipes input through a command.
pipeFn :: String pipeFn :: String

View file

@ -55,6 +55,7 @@ module Text.Pandoc.Readers
) where ) where
import Prelude import Prelude
import Control.Monad (unless)
import Control.Monad.Except (throwError) import Control.Monad.Except (throwError)
import Data.Aeson import Data.Aeson
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
@ -134,15 +135,28 @@ readers = [ ("native" , TextReader readNative)
] ]
-- | Retrieve reader, extensions based on formatSpec (format+extensions). -- | Retrieve reader, extensions based on formatSpec (format+extensions).
getReader :: PandocMonad m => String -> Either String (Reader m, Extensions) getReader :: PandocMonad m => String -> m (Reader m, Extensions)
getReader s = getReader s =
case parseFormatSpec s of case parseFormatSpec s of
Left e -> Left $ intercalate "\n" [m | Message m <- errorMessages e] Left e -> throwError $ PandocAppError
Right (readerName, setExts) -> $ intercalate "\n" [m | Message m <- errorMessages e]
Right (readerName, extsToEnable, extsToDisable) ->
case lookup readerName readers of case lookup readerName readers of
Nothing -> Left $ "Unknown reader: " ++ readerName Nothing -> throwError $ PandocUnknownReaderError
Just r -> Right (r, setExts $ readerName
getDefaultExtensions readerName) Just r -> do
let allExts = getAllExtensions readerName
let exts = foldr disableExtension
(foldr enableExtension
(getDefaultExtensions readerName)
extsToEnable) extsToDisable
mapM_ (\ext ->
unless (extensionEnabled ext allExts) $
throwError $
PandocUnsupportedExtensionError
(drop 4 $ show ext) readerName)
(extsToEnable ++ extsToDisable)
return (r, exts)
-- | Read pandoc document from JSON format. -- | Read pandoc document from JSON format.
readJSON :: PandocMonad m readJSON :: PandocMonad m

View file

@ -70,6 +70,8 @@ module Text.Pandoc.Writers
) where ) where
import Prelude import Prelude
import Control.Monad.Except (throwError)
import Control.Monad (unless)
import Data.Aeson import Data.Aeson
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import Data.List (intercalate) import Data.List (intercalate)
@ -78,6 +80,7 @@ import Text.Pandoc.Class
import Text.Pandoc.Definition import Text.Pandoc.Definition
import Text.Pandoc.Options import Text.Pandoc.Options
import qualified Text.Pandoc.UTF8 as UTF8 import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.Error
import Text.Pandoc.Writers.AsciiDoc import Text.Pandoc.Writers.AsciiDoc
import Text.Pandoc.Writers.CommonMark import Text.Pandoc.Writers.CommonMark
import Text.Pandoc.Writers.ConTeXt import Text.Pandoc.Writers.ConTeXt
@ -176,15 +179,29 @@ writers = [
] ]
-- | Retrieve writer, extensions based on formatSpec (format+extensions). -- | Retrieve writer, extensions based on formatSpec (format+extensions).
getWriter :: PandocMonad m => String -> Either String (Writer m, Extensions) getWriter :: PandocMonad m => String -> m (Writer m, Extensions)
getWriter s getWriter s =
= case parseFormatSpec s of case parseFormatSpec s of
Left e -> Left $ intercalate "\n" [m | Message m <- errorMessages e] Left e -> throwError $ PandocAppError
Right (writerName, setExts) -> $ intercalate "\n" [m | Message m <- errorMessages e]
case lookup writerName writers of Right (writerName, extsToEnable, extsToDisable) ->
Nothing -> Left $ "Unknown writer: " ++ writerName case lookup writerName writers of
Just r -> Right (r, setExts $ Nothing -> throwError $
getDefaultExtensions writerName) PandocUnknownWriterError writerName
Just w -> do
let allExts = getAllExtensions writerName
let exts = foldr disableExtension
(foldr enableExtension
(getDefaultExtensions writerName)
extsToEnable) extsToDisable
mapM_ (\ext ->
unless (extensionEnabled ext allExts) $
throwError $
PandocUnsupportedExtensionError
(drop 4 $ show ext) writerName)
(extsToEnable ++ extsToDisable)
return (w, exts)
writeJSON :: PandocMonad m => WriterOptions -> Pandoc -> m Text writeJSON :: PandocMonad m => WriterOptions -> Pandoc -> m Text
writeJSON _ = return . UTF8.toText . BL.toStrict . encode writeJSON _ = return . UTF8.toText . BL.toStrict . encode