@@ -1,7 +1,6 @@
1{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE CPP #-}
2{-# LANGUAGE CPP #-}
3{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
4−{-# LANGUAGE TupleSections #-}
6Copyright (C) 2006-2018 John MacFarlane <jgm@berkeley.edu>
5Copyright (C) 2006-2018 John MacFarlane <jgm@berkeley.edu>
@@ -44,12 +43,10 @@ module Text.Pandoc.App (
44import Prelude
43import Prelude
45import qualified Control.Exception as E
44import qualified Control.Exception as E
46import Control.Monad
45import Control.Monad
47−import Control.Monad.Except (catchError, throwError)
48import Control.Monad.Trans
46import Control.Monad.Trans
49import qualified Data.ByteString as BS
47import qualified Data.ByteString as BS
50import qualified Data.ByteString.Lazy as B
48import qualified Data.ByteString.Lazy as B
51import Data.Char (toLower)
49import Data.Char (toLower)
52−import Data.List (find, isPrefixOf, isSuffixOf)
53import Data.Maybe (fromMaybe, isJust, isNothing)
50import Data.Maybe (fromMaybe, isJust, isNothing)
54import qualified Data.Set as Set
51import qualified Data.Set as Set
55import Data.Text (Text)
52import Data.Text (Text)
@@ -59,8 +56,6 @@ import qualified Data.Text.Lazy.Encoding as TE
59import qualified Data.Text.Encoding.Error as TE
56import qualified Data.Text.Encoding.Error as TE
60import qualified Data.YAML as YAML
57import qualified Data.YAML as YAML
61import Network.URI (URI (..), parseURI)
58import Network.URI (URI (..), parseURI)
62−import Skylighting (defaultSyntaxMap)
63−import Skylighting.Parser (addSyntaxDefinition, parseSyntaxDefinition)
64import System.Directory (getAppUserDataDirectory)
59import System.Directory (getAppUserDataDirectory)
65import System.Exit (exitSuccess)
60import System.Exit (exitSuccess)
66import System.FilePath
61import System.FilePath
@@ -68,7 +63,8 @@ import System.IO (nativeNewline, stdout)
68import qualified System.IO as IO (Newline (..))
63import qualified System.IO as IO (Newline (..))
69import Text.Pandoc
64import Text.Pandoc
70import Text.Pandoc.App.CommandLineOptions (Opt (..), LineEnding (..),
65import Text.Pandoc.App.CommandLineOptions (Opt (..), LineEnding (..),
71− defaultOpts, engines, parseOptions, options)
66+ defaultOpts, parseOptions, options)
67+import Text.Pandoc.App.OutputSettings (OutputSettings (..), optToOutputSettings)
72import Text.Pandoc.BCP47 (Lang (..), parseBCP47)
68import Text.Pandoc.BCP47 (Lang (..), parseBCP47)
73import Text.Pandoc.Builder (setMeta, deleteMeta)
69import Text.Pandoc.Builder (setMeta, deleteMeta)
74import Text.Pandoc.Filter (Filter (JSONFilter, LuaFilter), applyFilters)
70import Text.Pandoc.Filter (Filter (JSONFilter, LuaFilter), applyFilters)
@@ -83,41 +79,6 @@ import System.Posix.IO (stdOutput)
83import System.Posix.Terminal (queryTerminal)
79import System.Posix.Terminal (queryTerminal)
86−pdfIsNoWriterErrorMsg :: String
87−pdfIsNoWriterErrorMsg =
88− "To create a pdf using pandoc, use " ++
89− "-t latex|beamer|context|ms|html5" ++
90− "\nand specify an output file with " ++
91− ".pdf extension (-o filename.pdf)."
93−pdfWriterAndProg :: Maybe String -- ^ user-specified writer name
94− -> Maybe String -- ^ user-specified pdf-engine
95− -> IO (String, Maybe String) -- ^ IO (writerName, maybePdfEngineProg)
96−pdfWriterAndProg mWriter mEngine = do
97− let panErr msg = liftIO $ E.throwIO $ PandocAppError msg
98− case go mWriter mEngine of
99− Right (writ, prog) -> return (writ, Just prog)
100− Left err -> panErr err
102− go Nothing Nothing = Right ("latex", "pdflatex")
103− go (Just writer) Nothing = (writer,) <$> engineForWriter writer
104− go Nothing (Just engine) = (,engine) <$> writerForEngine (takeBaseName engine)
105− go (Just writer) (Just engine) =
106− case find (== (baseWriterName writer, takeBaseName engine)) engines of
107− Just _ -> Right (writer, engine)
108− Nothing -> Left $ "pdf-engine " ++ engine ++
109− " is not compatible with output format " ++ writer
111− writerForEngine eng = case [f | (f,e) <- engines, e == eng] of
112− fmt : _ -> Right fmt
114− "pdf-engine " ++ eng ++ " not known"
116− engineForWriter "pdf" = Left pdfIsNoWriterErrorMsg
117− engineForWriter w = case [e | (f,e) <- engines, f == baseWriterName w] of
118− eng : _ -> Right eng
120− "cannot produce pdf output from " ++ w
122convertWithOpts :: Opt -> IO ()
83convertWithOpts :: Opt -> IO ()
123convertWithOpts opts = do
84convertWithOpts opts = do
@@ -130,10 +91,6 @@ convertWithOpts opts = do
130 mapM_ (UTF8.hPutStrLn stdout) (optInputFiles opts)
91 mapM_ (UTF8.hPutStrLn stdout) (optInputFiles opts)
131 exitSuccess
92 exitSuccess
133− epubMetadata <- case optEpubMetadata opts of
134− Nothing -> return Nothing
135− Just fp -> Just <$> UTF8.readFile fp
137 let isPandocCiteproc (JSONFilter f) = takeBaseName f == "pandoc-citeproc"
94 let isPandocCiteproc (JSONFilter f) = takeBaseName f == "pandoc-citeproc"
138 isPandocCiteproc _ = False
95 isPandocCiteproc _ = False
139 -- --bibliography implies -F pandoc-citeproc for backwards compatibility:
96 -- --bibliography implies -F pandoc-citeproc for backwards compatibility:
@@ -165,30 +122,6 @@ convertWithOpts opts = do
166 let pdfOutput = map toLower (takeExtension outputFile) == ".pdf"
123 let pdfOutput = map toLower (takeExtension outputFile) == ".pdf"
168− (writerName, maybePdfProg) <-
170− then pdfWriterAndProg (optWriter opts) (optPdfEngine opts)
171− else case optWriter opts of
173− return (formatFromFilePaths "html" [outputFile], Nothing)
174− Just f -> return (f, Nothing)
176− let format = map toLower $ baseWriterName
177− $ takeFileName writerName -- in case path to lua script
179− -- disabling the custom writer for now
180− (writer, writerExts) <-
181− if ".lua" `isSuffixOf` format
182− then return (TextWriter
183− (\o d -> writeCustom writerName o d)
184− :: Writer PandocIO, mempty)
185− else case getWriter (map toLower writerName) of
186− Left e -> E.throwIO $ PandocAppError $
188− then e ++ "\n" ++ pdfIsNoWriterErrorMsg
190− Right (w, es) -> return (w :: Writer PandocIO, es)
192 -- TODO: we have to get the input and the output into the state for
125 -- TODO: we have to get the input and the output into the state for
193 -- the sake of the text2tags reader.
126 -- the sake of the text2tags reader.
194 (reader, readerExts) <-
127 (reader, readerExts) <-
@@ -202,34 +135,6 @@ convertWithOpts opts = do
202 "\nPandoc can convert from DOCX, but not from DOC.\nTry using Word to save your DOC file as DOCX, and convert that with pandoc."
135 "\nPandoc can convert from DOCX, but not from DOC.\nTry using Word to save your DOC file as DOCX, and convert that with pandoc."
205− let standalone = optStandalone opts || not (isTextFormat format) || pdfOutput
206− let addStringAsVariable varname s vars = return $ (varname, s) : vars
208− let addSyntaxMap existingmap f = do
209− res <- parseSyntaxDefinition f
211− Left errstr -> E.throwIO $ PandocSyntaxMapError errstr
212− Right syn -> return $ addSyntaxDefinition syn existingmap
214− syntaxMap <- foldM addSyntaxMap defaultSyntaxMap
215− (optSyntaxDefinitions opts)
217− -- We don't want to send output to the terminal if the user
218− -- does 'pandoc -t docx input.txt'; though we allow them to
219− -- force this with '-o -'. On posix systems, we detect
220− -- when stdout is being piped and allow output to stdout
221− -- in that case, but on Windows we can't.
225− istty <- queryTerminal stdOutput
227− when (not (isTextFormat format) && istty && isNothing ( optOutputFile opts)) $
228− E.throwIO $ PandocAppError $
229− "Cannot write " ++ format ++ " output to terminal.\n" ++
230− "Specify an output file using the -o option, or " ++
231− "use '-o -' to force output to stdout."
233 let convertTabs = tabFilter (if optPreserveTabs opts ||
138 let convertTabs = tabFilter (if optPreserveTabs opts ||
234 readerName == "t2t" ||
139 readerName == "t2t" ||
235 readerName == "man"
140 readerName == "man"
@@ -261,80 +166,41 @@ convertWithOpts opts = do
261 LF -> IO.LF
166 LF -> IO.LF
262 Native -> nativeNewline
167 Native -> nativeNewline
264− -- note: this reverses the list constructed in option parsing,
265− -- which in turn was reversed from the command-line order,
266− -- so we end up with the correct order in the variable list:
267− let withList _ [] vars = return vars
268− withList f (x:xs) vars = f x vars >>= withList f xs
270− let addContentsAsVariable varname fp vars = do
271− s <- UTF8.toString <$> readFileStrict fp
272− return $ (varname, s) : vars
274 runIO' $ do
169 runIO' $ do
275 setUserDataDir datadir
170 setUserDataDir datadir
276 setInputFiles (optInputFiles opts)
171 setInputFiles (optInputFiles opts)
277 setOutputFile (optOutputFile opts)
172 setOutputFile (optOutputFile opts)
279− variables <-
174+ outputSettings <- optToOutputSettings opts
280− withList (addStringAsVariable "sourcefile")
175+ let format = outputFormat outputSettings
281− (reverse $ optInputFiles opts)
176+ let writer = outputWriter outputSettings
282− (("outputfile", fromMaybe "-" (optOutputFile opts))
177+ let writerName = outputWriterName outputSettings
283− : optVariables opts)
178+ let writerOptions = outputWriterOptions outputSettings
284− -- we reverse this list because, unlike
179+
285− -- the other option lists here, it is
180+ let standalone = optStandalone opts || not (isTextFormat format) || pdfOutput
286− -- not reversed when parsed from CLI arguments.
181+
287− -- See withList, above.
182+ -- We don't want to send output to the terminal if the user
288− >>=
183+ -- does 'pandoc -t docx input.txt'; though we allow them to
289− withList (addContentsAsVariable "include-before")
184+ -- force this with '-o -'. On posix systems, we detect
290− (optIncludeBeforeBody opts)
185+ -- when stdout is being piped and allow output to stdout
291− >>=
186+ -- in that case, but on Windows we can't.
292− withList (addContentsAsVariable "include-after")
187+#ifdef _WINDOWS
293− (optIncludeAfterBody opts)
188+ let istty = True
295− withList (addContentsAsVariable "header-includes")
296− (optIncludeInHeader opts)
298− withList (addStringAsVariable "css") (optCss opts)
300− maybe return (addStringAsVariable "title-prefix")
301− (optTitlePrefix opts)
303− maybe return (addStringAsVariable "epub-cover-image")
304− (optEpubCoverImage opts)
306− (\vars -> if format == "dzslides"
308− dztempl <- UTF8.toString <$> readDataFile
309− ("dzslides" </> "template.html")
310− let dzline = "<!-- {{{{ dzslides core"
311− let dzcore = unlines
312− $ dropWhile (not . (dzline `isPrefixOf`))
314− return $ ("dzslides-core", dzcore) : vars
315− else return vars)
189+#else
190+ istty <- liftIO $ queryTerminal stdOutput
192+ when (not (isTextFormat format) && istty && isNothing ( optOutputFile opts)) $
193+ liftIO $ E.throwIO $ PandocAppError $
194+ "Cannot write " ++ format ++ " output to terminal.\n" ++
195+ "Specify an output file using the -o option, or " ++
196+ "use '-o -' to force output to stdout."
317 abbrevs <- Set.fromList . filter (not . null) . lines <$>
199 abbrevs <- Set.fromList . filter (not . null) . lines <$>
318 case optAbbreviations opts of
200 case optAbbreviations opts of
319 Nothing -> UTF8.toString <$> readDataFile "abbreviations"
201 Nothing -> UTF8.toString <$> readDataFile "abbreviations"
320 Just f -> UTF8.toString <$> readFileStrict f
202 Just f -> UTF8.toString <$> readFileStrict f
322− templ <- case optTemplate opts of
323− _ | not standalone -> return Nothing
324− Nothing -> Just <$> getDefaultTemplate format
326− -- strip off extensions
327− let tp' = case takeExtension tp of
330− Just . UTF8.toString <$>
331− ((fst <$> fetchItem tp') `catchError`
334− PandocResourceNotFound _ ->
335− readDataFile ("templates" </> tp')
338 metadata <- if format == "jats" &&
204 metadata <- if format == "jats" &&
339 isNothing (lookup "csl" (optMetadata opts)) &&
205 isNothing (lookup "csl" (optMetadata opts)) &&
340 isNothing (lookup "citation-style" (optMetadata opts))
206 isNothing (lookup "citation-style" (optMetadata opts))
@@ -355,41 +221,6 @@ convertWithOpts opts = do
355 Right l' -> setTranslations l'
221 Right l' -> setTranslations l'
356 Nothing -> setTranslations $ Lang "en" "" "US" []
222 Nothing -> setTranslations $ Lang "en" "" "US" []
358− let writerOptions = def {
359− writerTemplate = templ
360− , writerVariables = variables
361− , writerTabStop = optTabStop opts
362− , writerTableOfContents = optTableOfContents opts
363− , writerHTMLMathMethod = optHTMLMathMethod opts
364− , writerIncremental = optIncremental opts
365− , writerCiteMethod = optCiteMethod opts
366− , writerNumberSections = optNumberSections opts
367− , writerNumberOffset = optNumberOffset opts
368− , writerSectionDivs = optSectionDivs opts
369− , writerExtensions = writerExts
370− , writerReferenceLinks = optReferenceLinks opts
371− , writerReferenceLocation = optReferenceLocation opts
372− , writerDpi = optDpi opts
373− , writerWrapText = optWrapText opts
374− , writerColumns = optColumns opts
375− , writerEmailObfuscation = optEmailObfuscation opts
376− , writerIdentifierPrefix = optIdentifierPrefix opts
377− , writerHtmlQTags = optHtmlQTags opts
378− , writerTopLevelDivision = optTopLevelDivision opts
379− , writerListings = optListings opts
380− , writerSlideLevel = optSlideLevel opts
381− , writerHighlightStyle = optHighlightStyle opts
382− , writerSetextHeaders = optSetextHeaders opts
383− , writerEpubSubdirectory = optEpubSubdirectory opts
384− , writerEpubMetadata = epubMetadata
385− , writerEpubFonts = optEpubFonts opts
386− , writerEpubChapterLevel = optEpubChapterLevel opts
387− , writerTOCDepth = optTOCDepth opts
388− , writerReferenceDoc = optReferenceDoc opts
389− , writerSyntaxMap = syntaxMap
390− , writerPreferAscii = optAscii opts
393 let readerOpts = def{
224 let readerOpts = def{
394 readerStandalone = standalone
225 readerStandalone = standalone
395 , readerColumns = optColumns opts
226 , readerColumns = optColumns opts
@@ -412,7 +243,7 @@ convertWithOpts opts = do
412 (if extensionEnabled Ext_east_asian_line_breaks
243 (if extensionEnabled Ext_east_asian_line_breaks
413 readerExts &&
244 readerExts &&
414 not (extensionEnabled Ext_east_asian_line_breaks
245 not (extensionEnabled Ext_east_asian_line_breaks
415− writerExts &&
246+ (writerExtensions writerOptions) &&
416 writerWrapText writerOptions == WrapPreserve)
247 writerWrapText writerOptions == WrapPreserve)
417 then (eastAsianLineBreakFilter :)
248 then (eastAsianLineBreakFilter :)
418 else id) $
249 else id) $
@@ -450,7 +281,7 @@ convertWithOpts opts = do
451 case writer of
282 case writer of
452 ByteStringWriter f -> f writerOptions doc >>= writeFnBinary outputFile
283 ByteStringWriter f -> f writerOptions doc >>= writeFnBinary outputFile
453− TextWriter f -> case maybePdfProg of
284+ TextWriter f -> case outputPdfProgram outputSettings of
454 Just pdfProg -> do
285 Just pdfProg -> do
455 res <- makePDF pdfProg (optPdfEngineArgs opts) f
286 res <- makePDF pdfProg (optPdfEngineArgs opts) f
456 writerOptions doc
287 writerOptions doc
@@ -594,7 +425,3 @@ writerFn :: MonadIO m => IO.Newline -> FilePath -> Text -> m ()
594-- TODO this implementation isn't maximally efficient:
425-- TODO this implementation isn't maximally efficient:
595writerFn eol "-" = liftIO . UTF8.putStrWith eol . T.unpack
426writerFn eol "-" = liftIO . UTF8.putStrWith eol . T.unpack
596writerFn eol f = liftIO . UTF8.writeFileWith eol f . T.unpack
427writerFn eol f = liftIO . UTF8.writeFileWith eol f . T.unpack
599−baseWriterName :: String -> String
600−baseWriterName = takeWhile (\c -> c /= '+' && c /= '-')