git.iko.soy/typeable/pandoc
on master help sign in
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.

commit746c92a41a4f1df5ac97246fe69555cef5419d00 authorJohn MacFarlane <jgm@berkeley.edu> dateSat 28 Sep 2019, 14:47 (7 years ago) parent03d4e6b
9 files changed+263−72
MANUAL.txt+4−1 src/Text/Pandoc/App.hs+1−10 src/Text/Pandoc/App/CommandLineOptions.hs+18−5 src/Text/Pandoc/App/OutputSettings.hs+5−16 src/Text/Pandoc/Error.hs+23 src/Text/Pandoc/Extensions.hs+151−15 src/Text/Pandoc/Lua/Module/Pandoc.hs+15−10 src/Text/Pandoc/Readers.hs+20−6 src/Text/Pandoc/Writers.hs+26−9
MANUAL.txt+4−1
@@ -399,7 +399,7 @@ General options {.options}
399399
400400`--list-extensions`[`=`*FORMAT*]
401401
402−: List supported extensions, one per line, preceded
402+: List supported extensions for *FORMAT*, one per line, preceded
403403 by a `+` or `-` indicating whether it is enabled by default
404404 in *FORMAT*. If *FORMAT* is not specified, defaults for
405405 pandoc's Markdown are given.
@@ -1391,6 +1391,9 @@ Nonzero exit codes have the following meanings:
13911391 4 PandocAppError
13921392 5 PandocTemplateError
13931393 6 PandocOptionError
1394+ 21 PandocUnknownReaderError
1395+ 22 PandocUnknownWriterError
1396+ 23 PandocUnsupportedExtensionError
13941397 31 PandocEpubSubdirectoryError
13951398 43 PandocPDFError
13961399 47 PandocPDFProgramNotFoundError
src/Text/Pandoc/App.hs+1−10
@@ -155,16 +155,7 @@ convertWithOpts opts = do
155155 <> "` instead of `pandoc " <> inputFile <> " -o " <> outputFile <> "`."
156156 _ -> return ()
157157
158− (reader, readerExts) <-
159− case getReader readerName of
160− Right (r, es) -> return (r :: Reader PandocIO, es)
161− Left e -> throwError $ PandocAppError e'
162− where e' = case readerName of
163− "pdf" -> e ++
164− "\nPandoc can convert to PDF, but not from PDF."
165− "doc" -> e ++
166− "\nPandoc can convert from DOCX, but not from DOC.\nTry using Word to save your DOC file as DOCX, and convert that with pandoc."
167− _ -> e
158+ (reader :: Reader PandocIO, readerExts) <- getReader readerName
168159
169160 let convertTabs = tabFilter (if optPreserveTabs opts ||
170161 readerName == "t2t" ||
src/Text/Pandoc/App/CommandLineOptions.hs+18−5
@@ -768,12 +768,25 @@ options =
768768 , Option "" ["list-extensions"]
769769 (OptArg
770770 (\arg _ -> do
771− let exts = getDefaultExtensions (fromMaybe "markdown" arg)
772− let showExt x = (if extensionEnabled x exts
773− then '+'
774− else '-') : drop 4 (show x)
771+ let extList :: [Extension]
772+ extList = [minBound..maxBound]
773+ let allExts =
774+ case arg of
775+ Nothing -> extensionsFromList extList
776+ Just fmt -> getAllExtensions fmt
777+ let defExts =
778+ case arg of
779+ Nothing -> getDefaultExtensions
780+ "markdown"
781+ Just fmt -> getDefaultExtensions fmt
782+ let showExt x =
783+ (if extensionEnabled x defExts
784+ then '+'
785+ else if extensionEnabled x allExts
786+ then '-'
787+ else ' ') : drop 4 (show x)
775788 mapM_ (UTF8.hPutStrLn stdout . showExt)
776− ([minBound..maxBound] :: [Extension])
789+ [ex | ex <- extList, extensionEnabled ex allExts]
777790 exitSuccess )
778791 "FORMAT")
779792 ""
src/Text/Pandoc/App/OutputSettings.hs+5−16
@@ -85,18 +85,12 @@ optToOutputSettings opts = do
8585 then writerName
8686 else map toLower $ baseWriterName writerName
8787
88− (writer, writerExts) <-
88+ (writer :: Writer PandocIO, writerExts) <-
8989 if ".lua" `isSuffixOf` format
9090 then return (TextWriter
9191 (\o d -> writeCustom writerName o d)
9292 :: Writer PandocIO, mempty)
93− else case getWriter (map toLower writerName) of
94− Left e -> throwError $ PandocAppError $
95− if format == "pdf"
96− then e ++ "\n" ++ pdfIsNoWriterErrorMsg
97− else e
98− Right (w, es) -> return (w :: Writer PandocIO, es)
99−
93+ else getWriter (map toLower writerName)
10094
10195 let standalone = optStandalone opts || not (isTextFormat format) || pdfOutput
10296
@@ -249,13 +243,6 @@ optToOutputSettings opts = do
249243baseWriterName :: String -> String
250244baseWriterName = takeWhile (\c -> c /= '+' && c /= '-')
251245
252−pdfIsNoWriterErrorMsg :: String
253−pdfIsNoWriterErrorMsg =
254− "To create a pdf using pandoc, use " ++
255− "-t latex|beamer|context|ms|html5" ++
256− "\nand specify an output file with " ++
257− ".pdf extension (-o filename.pdf)."
258−
259246pdfWriterAndProg :: Maybe String -- ^ user-specified writer name
260247 -> Maybe String -- ^ user-specified pdf-engine
261248 -> IO (String, Maybe String) -- ^ IO (writerName, maybePdfEngineProg)
@@ -263,6 +250,8 @@ pdfWriterAndProg mWriter mEngine = do
263250 let panErr msg = liftIO $ E.throwIO $ PandocAppError msg
264251 case go mWriter mEngine of
265252 Right (writ, prog) -> return (writ, Just prog)
253+ Left "pdf writer" -> liftIO $ E.throwIO $
254+ PandocUnknownWriterError "pdf"
266255 Left err -> panErr err
267256 where
268257 go Nothing Nothing = Right ("latex", "pdflatex")
@@ -279,7 +268,7 @@ pdfWriterAndProg mWriter mEngine = do
279268 [] -> Left $
280269 "pdf-engine " ++ eng ++ " not known"
281270
282− engineForWriter "pdf" = Left pdfIsNoWriterErrorMsg
271+ engineForWriter "pdf" = Left "pdf writer"
283272 engineForWriter w = case [e | (f,e) <- engines, f == baseWriterName w] of
284273 eng : _ -> Right eng
285274 [] -> Left $
src/Text/Pandoc/Error.hs+23
@@ -54,6 +54,9 @@ data PandocError = PandocIOError String IOError
5454 | PandocMacroLoop String
5555 | PandocUTF8DecodingError String Int Word8
5656 | PandocIpynbDecodingError String
57+ | PandocUnknownReaderError String
58+ | PandocUnknownWriterError String
59+ | PandocUnsupportedExtensionError String String
5760 deriving (Show, Typeable, Generic)
5861
5962instance Exception PandocError
@@ -112,6 +115,26 @@ handleError (Left e) =
112115 "The input must be a UTF-8 encoded text."
113116 PandocIpynbDecodingError w -> err 93 $
114117 "ipynb decoding error: " ++ w
118+ PandocUnknownReaderError r -> err 21 $
119+ "Unknown input format " ++ r ++
120+ case r of
121+ "doc" -> "\nPandoc can convert from DOCX, but not from DOC." ++
122+ "\nTry using Word to save your DOC file as DOCX," ++
123+ " and convert that with pandoc."
124+ "pdf" -> "\nPandoc can convert to PDF, but not from PDF."
125+ _ -> ""
126+ PandocUnknownWriterError w -> err 22 $
127+ "Unknown output format " ++ w ++
128+ case w of
129+ "pdf" -> "To create a pdf using pandoc, use" ++
130+ " -t latex|beamer|context|ms|html5" ++
131+ "\nand specify an output file with " ++
132+ ".pdf extension (-o filename.pdf)."
133+ "doc" -> "\nPandoc can convert to DOCX, but not from DOC."
134+ _ -> ""
135+ PandocUnsupportedExtensionError ext f -> err 23 $
136+ "The extension " ++ ext ++ " is not supported " ++
137+ "for " ++ f
115138
116139err :: Int -> String -> IO a
117140err exitCode msg = do
src/Text/Pandoc/Extensions.hs+151−15
@@ -26,6 +26,7 @@ module Text.Pandoc.Extensions ( Extension(..)
2626 , enableExtension
2727 , disableExtension
2828 , getDefaultExtensions
29+ , getAllExtensions
2930 , pandocExtensions
3031 , plainExtensions
3132 , strictExtensions
@@ -312,12 +313,12 @@ strictExtensions = extensionsFromList
312313
313314-- | Default extensions from format-describing string.
314315getDefaultExtensions :: String -> Extensions
315−getDefaultExtensions "markdown_strict" = strictExtensions
316+getDefaultExtensions "markdown_strict" = strictExtensions
316317getDefaultExtensions "markdown_phpextra" = phpMarkdownExtraExtensions
317−getDefaultExtensions "markdown_mmd" = multimarkdownExtensions
318−getDefaultExtensions "markdown_github" = githubMarkdownExtensions
319−getDefaultExtensions "markdown" = pandocExtensions
320−getDefaultExtensions "ipynb" =
318+getDefaultExtensions "markdown_mmd" = multimarkdownExtensions
319+getDefaultExtensions "markdown_github" = githubMarkdownExtensions
320+getDefaultExtensions "markdown" = pandocExtensions
321+getDefaultExtensions "ipynb" =
321322 extensionsFromList
322323 [ Ext_all_symbols_escapable
323324 , Ext_pipe_tables
@@ -379,16 +380,149 @@ getDefaultExtensions "opml" = pandocExtensions -- affects notes
379380getDefaultExtensions _ = extensionsFromList
380381 [Ext_auto_identifiers]
381382
382−-- | Parse a format-specifying string into a markup format and a function that
383−-- takes Extensions and enables and disables extensions as defined in the format
384−-- spec.
383+allMarkdownExtensions :: Extensions
384+allMarkdownExtensions =
385+ pandocExtensions <>
386+ extensionsFromList
387+ [ Ext_old_dashes
388+ , Ext_angle_brackets_escapable
389+ , Ext_lists_without_preceding_blankline
390+ , Ext_four_space_rule
391+ , Ext_spaced_reference_links
392+ , Ext_hard_line_breaks
393+ , Ext_ignore_line_breaks
394+ , Ext_east_asian_line_breaks
395+ , Ext_emoji
396+ , Ext_tex_math_single_backslash
397+ , Ext_tex_math_double_backslash
398+ , Ext_markdown_attribute
399+ , Ext_mmd_title_block
400+ , Ext_abbreviations
401+ , Ext_autolink_bare_uris
402+ , Ext_mmd_link_attributes
403+ , Ext_mmd_header_identifiers
404+ , Ext_compact_definition_lists
405+ , Ext_gutenberg
406+ , Ext_smart
407+ , Ext_literate_haskell
408+ ]
409+
410+
411+-- | Get all valid extensions for a format. This is used
412+-- mainly in checking format specifications for validity.
413+getAllExtensions :: String -> Extensions
414+getAllExtensions f = universalExtensions <> getAll f
415+ where
416+ autoIdExtensions = extensionsFromList
417+ [ Ext_auto_identifiers
418+ , Ext_gfm_auto_identifiers
419+ , Ext_ascii_identifiers
420+ ]
421+ universalExtensions = extensionsFromList
422+ [ Ext_east_asian_line_breaks ]
423+ getAll "markdown_strict" = allMarkdownExtensions
424+ getAll "markdown_phpextra" = allMarkdownExtensions
425+ getAll "markdown_mmd" = allMarkdownExtensions
426+ getAll "markdown_github" = allMarkdownExtensions
427+ getAll "markdown" = allMarkdownExtensions
428+ getAll "ipynb" = allMarkdownExtensions
429+ getAll "docx" = extensionsFromList
430+ [ Ext_empty_paragraphs
431+ , Ext_styles
432+ ]
433+ getAll "opendocument" = extensionsFromList
434+ [ Ext_empty_paragraphs
435+ , Ext_native_numbering
436+ ]
437+ getAll "odt" = getAll "opendocument" <> autoIdExtensions
438+ getAll "muse" = autoIdExtensions <>
439+ extensionsFromList
440+ [ Ext_amuse ]
441+ getAll "asciidoc" = autoIdExtensions
442+ getAll "plain" = allMarkdownExtensions
443+ getAll "gfm" = githubMarkdownExtensions <>
444+ autoIdExtensions <>
445+ extensionsFromList
446+ [ Ext_raw_html
447+ , Ext_raw_tex -- only supported in writer (for math)
448+ , Ext_hard_line_breaks
449+ , Ext_smart
450+ ]
451+ getAll "commonmark" = getAll "gfm"
452+ getAll "org" = autoIdExtensions <>
453+ extensionsFromList
454+ [ Ext_citations
455+ , Ext_smart
456+ ]
457+ getAll "html" = autoIdExtensions <>
458+ extensionsFromList
459+ [ Ext_native_divs
460+ , Ext_line_blocks
461+ , Ext_native_spans
462+ , Ext_empty_paragraphs
463+ , Ext_raw_html
464+ , Ext_raw_tex
465+ , Ext_task_lists
466+ , Ext_tex_math_dollars
467+ , Ext_tex_math_single_backslash
468+ , Ext_tex_math_double_backslash
469+ , Ext_literate_haskell
470+ , Ext_epub_html_exts
471+ ]
472+ getAll "html4" = getAll "html"
473+ getAll "html5" = getAll "html"
474+ getAll "epub" = getAll "html"
475+ getAll "epub2" = getAll "epub"
476+ getAll "epub3" = getAll "epub"
477+ getAll "latex" = autoIdExtensions <>
478+ extensionsFromList
479+ [ Ext_smart
480+ , Ext_latex_macros
481+ , Ext_raw_tex
482+ , Ext_task_lists
483+ , Ext_literate_haskell
484+ ]
485+ getAll "beamer" = getAll "latex"
486+ getAll "context" = autoIdExtensions <>
487+ extensionsFromList
488+ [ Ext_smart
489+ , Ext_raw_tex
490+ , Ext_ntb
491+ ]
492+ getAll "textile" = autoIdExtensions <>
493+ extensionsFromList
494+ [ Ext_old_dashes
495+ , Ext_smart
496+ , Ext_raw_tex
497+ ]
498+ getAll "opml" = allMarkdownExtensions -- affects notes
499+ getAll "twiki" = autoIdExtensions <>
500+ extensionsFromList
501+ [ Ext_smart ]
502+ getAll "vimwiki" = autoIdExtensions
503+ getAll "dokuwiki" = autoIdExtensions
504+ getAll "tikiwiki" = autoIdExtensions
505+ getAll "rst" = autoIdExtensions <>
506+ extensionsFromList
507+ [ Ext_smart
508+ , Ext_literate_haskell
509+ ]
510+ getAll "mediawiki" = autoIdExtensions <>
511+ extensionsFromList
512+ [ Ext_smart ]
513+ getAll _ = mempty
514+
515+
516+-- | Parse a format-specifying string into a markup format,
517+-- a set of extensions to enable, and a set of extensions to disable.
385518parseFormatSpec :: String
386− -> Either ParseError (String, Extensions -> Extensions)
519+ -> Either ParseError (String, [Extension], [Extension])
387520parseFormatSpec = parse formatSpec ""
388521 where formatSpec = do
389522 name <- formatName
390− extMods <- many extMod
391− return (name, \x -> foldl (flip ($)) x extMods)
523+ (extsToEnable, extsToDisable) <- foldl (flip ($)) ([],[]) <$>
524+ many extMod
525+ return (name, reverse extsToEnable, reverse extsToDisable)
392526 formatName = many1 $ noneOf "-+"
393527 extMod = do
394528 polarity <- oneOf "-+"
@@ -397,10 +531,12 @@ parseFormatSpec = parse formatSpec ""
397531 Just n -> return n
398532 Nothing
399533 | name == "lhs" -> return Ext_literate_haskell
400− | otherwise -> Prelude.fail $ "Unknown extension: " ++ name
401− return $ case polarity of
402− '-' -> disableExtension ext
403− _ -> enableExtension ext
534+ | otherwise -> Prelude.fail $
535+ "Unknown extension: " ++ name
536+ return $ \(extsToEnable, extsToDisable) ->
537+ case polarity of
538+ '+' -> (ext : extsToEnable, extsToDisable)
539+ _ -> (extsToEnable, ext : extsToDisable)
404540
405541#ifdef DERIVE_JSON_VIA_TH
406542$(deriveJSON defaultOptions ''Extension)
src/Text/Pandoc/Lua/Module/Pandoc.hs+15−10
@@ -16,6 +16,7 @@ module Text.Pandoc.Lua.Module.Pandoc
1616
1717import Prelude
1818import Control.Monad (when)
19+import Control.Monad.Except (throwError)
1920import Data.Default (Default (..))
2021import Data.Maybe (fromMaybe)
2122import Data.Text (pack)
@@ -34,6 +35,7 @@ import qualified Data.ByteString.Lazy as BL
3435import qualified Data.ByteString.Lazy.Char8 as BSL
3536import qualified Foreign.Lua as Lua
3637import qualified Text.Pandoc.Lua.Util as LuaUtil
38+import Text.Pandoc.Error
3739
3840-- | Push the "pandoc" on the lua stack. Requires the `list` module to be
3941-- loaded.
@@ -60,17 +62,20 @@ walkBlock = walkElement
6062readDoc :: String -> Optional String -> Lua NumResults
6163readDoc content formatSpecOrNil = do
6264 let formatSpec = fromMaybe "markdown" (Lua.fromOptional formatSpecOrNil)
63− case getReader formatSpec of
64− Left s -> Lua.raiseError s -- Unknown reader
65− Right (reader, es) ->
66− case reader of
67− TextReader r -> do
68− res <- Lua.liftIO . runIO $
65+ res <- Lua.liftIO . runIO $
66+ getReader formatSpec >>= \(rdr,es) ->
67+ case rdr of
68+ TextReader r ->
6969 r def{ readerExtensions = es } (pack content)
70− case res of
71− Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc
72− Left s -> Lua.raiseError (show s) -- error while reading
73− _ -> Lua.raiseError "Only string formats are supported at the moment."
70+ _ -> throwError $ PandocSomeError $
71+ "Only textual formats are supported"
72+ case res of
73+ Right pd -> (1 :: NumResults) <$ Lua.push pd -- success, push Pandoc
74+ Left (PandocUnknownReaderError f) -> Lua.raiseError $
75+ "Unknown reader: " ++ f
76+ Left (PandocUnsupportedExtensionError e f) -> Lua.raiseError $
77+ "Extension " ++ e ++ " not supported for " ++ f
78+ Left e -> Lua.raiseError $ show e
7479
7580-- | Pipes input through a command.
7681pipeFn :: String
src/Text/Pandoc/Readers.hs+20−6
@@ -55,6 +55,7 @@ module Text.Pandoc.Readers
5555 ) where
5656
5757import Prelude
58+import Control.Monad (unless)
5859import Control.Monad.Except (throwError)
5960import Data.Aeson
6061import qualified Data.ByteString.Lazy as BL
@@ -134,15 +135,28 @@ readers = [ ("native" , TextReader readNative)
134135 ]
135136
136137-- | Retrieve reader, extensions based on formatSpec (format+extensions).
137−getReader :: PandocMonad m => String -> Either String (Reader m, Extensions)
138+getReader :: PandocMonad m => String -> m (Reader m, Extensions)
138139getReader s =
139140 case parseFormatSpec s of
140− Left e -> Left $ intercalate "\n" [m | Message m <- errorMessages e]
141− Right (readerName, setExts) ->
141+ Left e -> throwError $ PandocAppError
142+ $ intercalate "\n" [m | Message m <- errorMessages e]
143+ Right (readerName, extsToEnable, extsToDisable) ->
142144 case lookup readerName readers of
143− Nothing -> Left $ "Unknown reader: " ++ readerName
144− Just r -> Right (r, setExts $
145− getDefaultExtensions readerName)
145+ Nothing -> throwError $ PandocUnknownReaderError
146+ readerName
147+ Just r -> do
148+ let allExts = getAllExtensions readerName
149+ let exts = foldr disableExtension
150+ (foldr enableExtension
151+ (getDefaultExtensions readerName)
152+ extsToEnable) extsToDisable
153+ mapM_ (\ext ->
154+ unless (extensionEnabled ext allExts) $
155+ throwError $
156+ PandocUnsupportedExtensionError
157+ (drop 4 $ show ext) readerName)
158+ (extsToEnable ++ extsToDisable)
159+ return (r, exts)
146160
147161-- | Read pandoc document from JSON format.
148162readJSON :: PandocMonad m
src/Text/Pandoc/Writers.hs+26−9
@@ -70,6 +70,8 @@ module Text.Pandoc.Writers
7070 ) where
7171
7272import Prelude
73+import Control.Monad.Except (throwError)
74+import Control.Monad (unless)
7375import Data.Aeson
7476import qualified Data.ByteString.Lazy as BL
7577import Data.List (intercalate)
@@ -78,6 +80,7 @@ import Text.Pandoc.Class
7880import Text.Pandoc.Definition
7981import Text.Pandoc.Options
8082import qualified Text.Pandoc.UTF8 as UTF8
83+import Text.Pandoc.Error
8184import Text.Pandoc.Writers.AsciiDoc
8285import Text.Pandoc.Writers.CommonMark
8386import Text.Pandoc.Writers.ConTeXt
@@ -176,15 +179,29 @@ writers = [
176179 ]
177180
178181-- | Retrieve writer, extensions based on formatSpec (format+extensions).
179−getWriter :: PandocMonad m => String -> Either String (Writer m, Extensions)
180−getWriter s
181− = case parseFormatSpec s of
182− Left e -> Left $ intercalate "\n" [m | Message m <- errorMessages e]
183− Right (writerName, setExts) ->
184− case lookup writerName writers of
185− Nothing -> Left $ "Unknown writer: " ++ writerName
186− Just r -> Right (r, setExts $
187− getDefaultExtensions writerName)
182+getWriter :: PandocMonad m => String -> m (Writer m, Extensions)
183+getWriter s =
184+ case parseFormatSpec s of
185+ Left e -> throwError $ PandocAppError
186+ $ intercalate "\n" [m | Message m <- errorMessages e]
187+ Right (writerName, extsToEnable, extsToDisable) ->
188+ case lookup writerName writers of
189+ Nothing -> throwError $
190+ PandocUnknownWriterError writerName
191+ Just w -> do
192+ let allExts = getAllExtensions writerName
193+ let exts = foldr disableExtension
194+ (foldr enableExtension
195+ (getDefaultExtensions writerName)
196+ extsToEnable) extsToDisable
197+ mapM_ (\ext ->
198+ unless (extensionEnabled ext allExts) $
199+ throwError $
200+ PandocUnsupportedExtensionError
201+ (drop 4 $ show ext) writerName)
202+ (extsToEnable ++ extsToDisable)
203+ return (w, exts)
204+
188205
189206writeJSON :: PandocMonad m => WriterOptions -> Pandoc -> m Text
190207writeJSON _ = return . UTF8.toText . BL.toStrict . encode