git.iko.soy/typeable/pandoc
on 1.0 help sign in
614 lines 24 kB history raw
1{-# LANGUAGE CPP #-}
2{-
3Copyright (C) 2006-8 John MacFarlane <jgm@berkeley.edu>
5This program is free software; you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation; either version 2 of the License, or
8(at your option) any later version.
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18-}
20{- |
21 Module : Main
22 Copyright : Copyright (C) 2006-8 John MacFarlane
23 License : GNU GPL, version 2 or above
25 Maintainer : John MacFarlane <jgm@berkeley@edu>
26 Stability : alpha
27 Portability : portable
29Parses command-line options and calls the appropriate readers and
30writers.
31-}
32module Main where
33import Text.Pandoc
34import Text.Pandoc.ODT
35import Text.Pandoc.Shared ( HTMLMathMethod (..) )
36import Text.Pandoc.Highlighting ( languages )
37import System.Environment ( getArgs, getProgName, getEnvironment )
38import System.Exit ( exitWith, ExitCode (..) )
39import System.FilePath ( takeExtension, takeDirectory )
40import System.Console.GetOpt
41import Data.Maybe ( fromMaybe )
42import Data.Char ( toLower )
43import Data.List ( intercalate )
44import Prelude hiding ( putStrLn, writeFile, readFile, getContents )
45import System.IO ( stdout, stderr )
46import System.IO.UTF8
47#ifdef _CITEPROC
48import Text.CSL
49import Text.Pandoc.Biblio
50#endif
52copyrightMessage :: String
53copyrightMessage = "\nCopyright (C) 2006-7 John MacFarlane\n" ++
54 "Web: http://johnmacfarlane.net/pandoc\n" ++
55 "This is free software; see the source for copying conditions. There is no\n" ++
56 "warranty, not even for merchantability or fitness for a particular purpose."
58compileInfo :: String
59compileInfo =
60#ifdef _CITEPROC
61 " +citeproc" ++
62#else
63 " -citeproc" ++
64#endif
65#ifdef _HIGHLIGHTING
66 " +highlighting" ++
67#else
68 " -highlighting" ++
69#endif
70 if null languages
71 then "\n"
72 else "\nCompiled with syntax highlighting support for:\n" ++
73 (unlines $ map unwords $ chunk 5 $ map (\s -> s ++ replicate (15 - length s) ' ') languages)
75-- | Splits a list into groups of at most n.
76chunk :: Int -> [a] -> [[a]]
77chunk size lst =
78 let (next, rest) = splitAt size lst
79 in if null rest then [next] else next : chunk size rest
81-- | Association list of formats and readers.
82readers :: [(String, ParserState -> String -> Pandoc)]
83readers = [("native" , readPandoc)
84 ,("markdown" , readMarkdown)
85 ,("rst" , readRST)
86 ,("html" , readHtml)
87 ,("latex" , readLaTeX)
88 ]
90-- | Reader for native Pandoc format.
91readPandoc :: ParserState -> String -> Pandoc
92readPandoc _ input = read input
94-- | Association list of formats and pairs of writers and default headers.
95writers :: [ ( String, ( WriterOptions -> Pandoc -> String, String ) ) ]
96writers = [("native" , (writeDoc, ""))
97 ,("html" , (writeHtmlString, ""))
98 ,("s5" , (writeS5String, defaultS5Header))
99 ,("docbook" , (writeDocbook, defaultDocbookHeader))
100 ,("opendocument" , (writeOpenDocument, defaultOpenDocumentHeader))
101 ,("odt" , (writeOpenDocument, defaultOpenDocumentHeader))
102 ,("latex" , (writeLaTeX, defaultLaTeXHeader))
103 ,("context" , (writeConTeXt, defaultConTeXtHeader))
104 ,("texinfo" , (writeTexinfo, ""))
105 ,("man" , (writeMan, ""))
106 ,("markdown" , (writeMarkdown, ""))
107 ,("rst" , (writeRST, ""))
108 ,("mediawiki" , (writeMediaWiki, ""))
109 ,("rtf" , (writeRTF, defaultRTFHeader))
110 ]
112isNonTextOutput :: String -> Bool
113isNonTextOutput = (`elem` ["odt"])
115-- | Writer for Pandoc native format.
116writeDoc :: WriterOptions -> Pandoc -> String
117writeDoc _ = prettyPandoc
119-- | Data structure for command line options.
120data Opt = Opt
121 { optPreserveTabs :: Bool -- ^ Convert tabs to spaces
122 , optTabStop :: Int -- ^ Number of spaces per tab
123 , optStandalone :: Bool -- ^ Include header, footer
124 , optReader :: String -- ^ Reader format
125 , optWriter :: String -- ^ Writer format
126 , optParseRaw :: Bool -- ^ Parse unconvertable HTML and TeX
127 , optCSS :: [String] -- ^ CSS file to link to
128 , optTableOfContents :: Bool -- ^ Include table of contents
129 , optIncludeInHeader :: String -- ^ File to include in header
130 , optIncludeBeforeBody :: String -- ^ File to include at top of body
131 , optIncludeAfterBody :: String -- ^ File to include at end of body
132 , optCustomHeader :: String -- ^ Custom header to use, or "DEFAULT"
133 , optTitlePrefix :: String -- ^ Optional prefix for HTML title
134 , optOutputFile :: String -- ^ Name of output file
135 , optNumberSections :: Bool -- ^ Number sections in LaTeX
136 , optIncremental :: Bool -- ^ Use incremental lists in S5
137 , optSmart :: Bool -- ^ Use smart typography
138 , optHTMLMathMethod :: HTMLMathMethod -- ^ Method to print HTML math
139 , optDumpArgs :: Bool -- ^ Output command-line arguments
140 , optIgnoreArgs :: Bool -- ^ Ignore command-line arguments
141 , optStrict :: Bool -- ^ Use strict markdown syntax
142 , optReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
143 , optWrapText :: Bool -- ^ Wrap text
144 , optSanitizeHTML :: Bool -- ^ Sanitize HTML
145#ifdef _CITEPROC
146 , optModsFile :: String
147 , optCslFile :: String
148#endif
149 }
151-- | Defaults for command-line options.
152defaultOpts :: Opt
153defaultOpts = Opt
154 { optPreserveTabs = False
155 , optTabStop = 4
156 , optStandalone = False
157 , optReader = "" -- null for default reader
158 , optWriter = "" -- null for default writer
159 , optParseRaw = False
160 , optCSS = []
161 , optTableOfContents = False
162 , optIncludeInHeader = ""
163 , optIncludeBeforeBody = ""
164 , optIncludeAfterBody = ""
165 , optCustomHeader = "DEFAULT"
166 , optTitlePrefix = ""
167 , optOutputFile = "-" -- "-" means stdout
168 , optNumberSections = False
169 , optIncremental = False
170 , optSmart = False
171 , optHTMLMathMethod = PlainMath
172 , optDumpArgs = False
173 , optIgnoreArgs = False
174 , optStrict = False
175 , optReferenceLinks = False
176 , optWrapText = True
177 , optSanitizeHTML = False
178#ifdef _CITEPROC
179 , optModsFile = []
180 , optCslFile = []
181#endif
182 }
184-- | A list of functions, each transforming the options data structure
185-- in response to a command-line option.
186options :: [OptDescr (Opt -> IO Opt)]
187options =
188 [ Option "fr" ["from","read"]
189 (ReqArg
190 (\arg opt -> return opt { optReader = map toLower arg })
191 "FORMAT")
192 "" -- ("(" ++ (intercalate ", " $ map fst readers) ++ ")")
194 , Option "tw" ["to","write"]
195 (ReqArg
196 (\arg opt -> return opt { optWriter = map toLower arg })
197 "FORMAT")
198 "" -- ("(" ++ (intercalate ", " $ map fst writers) ++ ")")
200 , Option "s" ["standalone"]
201 (NoArg
202 (\opt -> return opt { optStandalone = True }))
203 "" -- "Include needed header and footer on output"
205 , Option "o" ["output"]
206 (ReqArg
207 (\arg opt -> return opt { optOutputFile = arg })
208 "FILENAME")
209 "" -- "Name of output file"
211 , Option "p" ["preserve-tabs"]
212 (NoArg
213 (\opt -> return opt { optPreserveTabs = True }))
214 "" -- "Preserve tabs instead of converting to spaces"
216 , Option "" ["tab-stop"]
217 (ReqArg
218 (\arg opt -> return opt { optTabStop = (read arg) } )
219 "TABSTOP")
220 "" -- "Tab stop (default 4)"
222 , Option "" ["strict"]
223 (NoArg
224 (\opt -> return opt { optStrict = True } ))
225 "" -- "Disable markdown syntax extensions"
227 , Option "" ["reference-links"]
228 (NoArg
229 (\opt -> return opt { optReferenceLinks = True } ))
230 "" -- "Use reference links in parsing HTML"
232 , Option "R" ["parse-raw"]
233 (NoArg
234 (\opt -> return opt { optParseRaw = True }))
235 "" -- "Parse untranslatable HTML codes and LaTeX environments as raw"
237 , Option "S" ["smart"]
238 (NoArg
239 (\opt -> return opt { optSmart = True }))
240 "" -- "Use smart quotes, dashes, and ellipses"
242 , Option "m" ["latexmathml", "asciimathml"]
243 (OptArg
244 (\arg opt -> return opt { optHTMLMathMethod =
245 LaTeXMathML arg })
246 "URL")
247 "" -- "Use LaTeXMathML script in html output"
249 , Option "" ["mimetex"]
250 (OptArg
251 (\arg opt -> return opt { optHTMLMathMethod = MimeTeX
252 (fromMaybe "/cgi-bin/mimetex.cgi" arg)})
253 "URL")
254 "" -- "Use mimetex for HTML math"
256 , Option "" ["gladtex"]
257 (NoArg
258 (\opt -> return opt { optHTMLMathMethod = GladTeX }))
259 "" -- "Use gladtex for HTML math"
261 , Option "i" ["incremental"]
262 (NoArg
263 (\opt -> return opt { optIncremental = True }))
264 "" -- "Make list items display incrementally in S5"
266 , Option "N" ["number-sections"]
267 (NoArg
268 (\opt -> return opt { optNumberSections = True }))
269 "" -- "Number sections in LaTeX"
271 , Option "" ["no-wrap"]
272 (NoArg
273 (\opt -> return opt { optWrapText = False }))
274 "" -- "Do not wrap text in output"
276 , Option "" ["sanitize-html"]
277 (NoArg
278 (\opt -> return opt { optSanitizeHTML = True }))
279 "" -- "Sanitize HTML"
281 , Option "" ["toc", "table-of-contents"]
282 (NoArg
283 (\opt -> return opt { optTableOfContents = True }))
284 "" -- "Include table of contents"
286 , Option "c" ["css"]
287 (ReqArg
288 (\arg opt -> do
289 let old = optCSS opt
290 return opt { optCSS = old ++ [arg],
291 optStandalone = True })
292 "CSS")
293 "" -- "Link to CSS style sheet"
295 , Option "H" ["include-in-header"]
296 (ReqArg
297 (\arg opt -> do
298 let old = optIncludeInHeader opt
299 text <- readFile arg
300 return opt { optIncludeInHeader = old ++ text,
301 optStandalone = True })
302 "FILENAME")
303 "" -- "File to include at end of header (implies -s)"
305 , Option "B" ["include-before-body"]
306 (ReqArg
307 (\arg opt -> do
308 let old = optIncludeBeforeBody opt
309 text <- readFile arg
310 return opt { optIncludeBeforeBody = old ++ text })
311 "FILENAME")
312 "" -- "File to include before document body"
314 , Option "A" ["include-after-body"]
315 (ReqArg
316 (\arg opt -> do
317 let old = optIncludeAfterBody opt
318 text <- readFile arg
319 return opt { optIncludeAfterBody = old ++ text })
320 "FILENAME")
321 "" -- "File to include after document body"
323 , Option "C" ["custom-header"]
324 (ReqArg
325 (\arg opt -> do
326 text <- readFile arg
327 return opt { optCustomHeader = text,
328 optStandalone = True })
329 "FILENAME")
330 "" -- "File to use for custom header (implies -s)"
332 , Option "T" ["title-prefix"]
333 (ReqArg
334 (\arg opt -> return opt { optTitlePrefix = arg,
335 optStandalone = True })
336 "STRING")
337 "" -- "String to prefix to HTML window title"
339 , Option "D" ["print-default-header"]
340 (ReqArg
341 (\arg _ -> do
342 let header = case (lookup arg writers) of
343 Just (_, h) -> h
344 Nothing -> error ("Unknown reader: " ++ arg)
345 hPutStr stdout header
346 exitWith ExitSuccess)
347 "FORMAT")
348 "" -- "Print default header for FORMAT"
349#ifdef _CITEPROC
350 , Option "" ["mods"]
351 (ReqArg
352 (\arg opt -> return opt { optModsFile = arg} )
353 "FILENAME")
354 ""
355 , Option "" ["csl"]
356 (ReqArg
357 (\arg opt -> return opt { optCslFile = arg} )
358 "FILENAME")
359 ""
360#endif
361 , Option "" ["dump-args"]
362 (NoArg
363 (\opt -> return opt { optDumpArgs = True }))
364 "" -- "Print output filename and arguments to stdout."
366 , Option "" ["ignore-args"]
367 (NoArg
368 (\opt -> return opt { optIgnoreArgs = True }))
369 "" -- "Ignore command-line arguments."
371 , Option "v" ["version"]
372 (NoArg
373 (\_ -> do
374 prg <- getProgName
375 hPutStrLn stderr (prg ++ " " ++ pandocVersion ++ compileInfo ++
376 copyrightMessage)
377 exitWith $ ExitFailure 4))
378 "" -- "Print version"
380 , Option "h" ["help"]
381 (NoArg
382 (\_ -> do
383 prg <- getProgName
384 hPutStr stderr (usageMessage prg options)
385 exitWith $ ExitFailure 2))
386 "" -- "Show help"
387 ]
389-- Returns usage message
390usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
391usageMessage programName opts = usageInfo
392 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
393 (intercalate ", " $ map fst readers) ++ "\nOutput formats: " ++
394 (intercalate ", " $ map fst writers) ++ "\nOptions:")
395 opts
397-- Determine default reader based on source file extensions
398defaultReaderName :: [FilePath] -> String
399defaultReaderName [] = "markdown"
400defaultReaderName (x:xs) =
401 case takeExtension (map toLower x) of
402 ".xhtml" -> "html"
403 ".html" -> "html"
404 ".htm" -> "html"
405 ".tex" -> "latex"
406 ".latex" -> "latex"
407 ".ltx" -> "latex"
408 ".rst" -> "rst"
409 ".native" -> "native"
410 _ -> defaultReaderName xs
412-- Determine default writer based on output file extension
413defaultWriterName :: FilePath -> String
414defaultWriterName "-" = "html" -- no output file
415defaultWriterName x =
416 case takeExtension (map toLower x) of
417 "" -> "markdown" -- empty extension
418 ".tex" -> "latex"
419 ".latex" -> "latex"
420 ".ltx" -> "latex"
421 ".context" -> "context"
422 ".ctx" -> "context"
423 ".rtf" -> "rtf"
424 ".rst" -> "rst"
425 ".s5" -> "s5"
426 ".native" -> "native"
427 ".txt" -> "markdown"
428 ".text" -> "markdown"
429 ".md" -> "markdown"
430 ".markdown" -> "markdown"
431 ".texi" -> "texinfo"
432 ".texinfo" -> "texinfo"
433 ".db" -> "docbook"
434 ".odt" -> "odt"
435 ['.',y] | y `elem` ['1'..'9'] -> "man"
436 _ -> "html"
438main :: IO ()
439main = do
441 rawArgs <- getArgs
442 prg <- getProgName
443 let compatMode = (prg == "hsmarkdown")
445 let (actions, args, errors) = if compatMode
446 then ([], rawArgs, [])
447 else getOpt Permute options rawArgs
449 if (not (null errors))
450 then do
451 name <- getProgName
452 mapM (\e -> hPutStrLn stderr e) errors
453 hPutStr stderr (usageMessage name options)
454 exitWith $ ExitFailure 2
455 else
456 return ()
458 let defaultOpts' = if compatMode
459 then defaultOpts { optReader = "markdown"
460 , optWriter = "html"
461 , optStrict = True }
462 else defaultOpts
464 -- thread option data structure through all supplied option actions
465 opts <- foldl (>>=) (return defaultOpts') actions
467 let Opt { optPreserveTabs = preserveTabs
468 , optTabStop = tabStop
469 , optStandalone = standalone
470 , optReader = readerName
471 , optWriter = writerName
472 , optParseRaw = parseRaw
473 , optCSS = css
474 , optTableOfContents = toc
475 , optIncludeInHeader = includeHeader
476 , optIncludeBeforeBody = includeBefore
477 , optIncludeAfterBody = includeAfter
478 , optCustomHeader = customHeader
479 , optTitlePrefix = titlePrefix
480 , optOutputFile = outputFile
481 , optNumberSections = numberSections
482 , optIncremental = incremental
483 , optSmart = smart
484 , optHTMLMathMethod = mathMethod
485 , optDumpArgs = dumpArgs
486 , optIgnoreArgs = ignoreArgs
487 , optStrict = strict
488 , optReferenceLinks = referenceLinks
489 , optWrapText = wrap
490 , optSanitizeHTML = sanitize
491#ifdef _CITEPROC
492 , optModsFile = modsFile
493 , optCslFile = cslFile
494#endif
495 } = opts
497 if dumpArgs
498 then do
499 hPutStrLn stdout outputFile
500 mapM (\arg -> hPutStrLn stdout arg) args
501 exitWith $ ExitSuccess
502 else return ()
504 let sources = if ignoreArgs then [] else args
506 -- assign reader and writer based on options and filenames
507 let readerName' = if null readerName
508 then defaultReaderName sources
509 else readerName
511 let writerName' = if null writerName
512 then defaultWriterName outputFile
513 else writerName
515 reader <- case (lookup readerName' readers) of
516 Just r -> return r
517 Nothing -> error ("Unknown reader: " ++ readerName')
519 (writer, defaultHeader) <- case (lookup writerName' writers) of
520 Just (w,h) -> return (w, h)
521 Nothing -> error ("Unknown writer: " ++ writerName')
523 environment <- getEnvironment
524 let columns = case lookup "COLUMNS" environment of
525 Just cols -> read cols
526 Nothing -> stateColumns defaultParserState
528 let tabFilter _ [] = ""
529 tabFilter _ ('\n':xs) = '\n':(tabFilter tabStop xs)
530 -- remove DOS line endings
531 tabFilter _ ('\r':'\n':xs) = '\n':(tabFilter tabStop xs)
532 tabFilter _ ('\r':xs) = '\n':(tabFilter tabStop xs)
533 tabFilter spsToNextStop ('\t':xs) =
534 if preserveTabs
535 then '\t':(tabFilter tabStop xs)
536 else replicate spsToNextStop ' ' ++ tabFilter tabStop xs
537 tabFilter 1 (x:xs) =
538 x:(tabFilter tabStop xs)
539 tabFilter spsToNextStop (x:xs) =
540 x:(tabFilter (spsToNextStop - 1) xs)
542 let standalone' = (standalone && not strict) || isNonTextOutput writerName'
544#ifdef _CITEPROC
545 refs <- if null modsFile then return [] else readModsColletionFile modsFile
546#endif
548 let startParserState =
549 defaultParserState { stateParseRaw = parseRaw,
550 stateTabStop = tabStop,
551 stateSanitizeHTML = sanitize,
552 stateStandalone = standalone',
553#ifdef _CITEPROC
554 stateCitations = map citeKey refs,
555#endif
556 stateSmart = smart || writerName' `elem`
557 ["latex", "context"],
558 stateColumns = columns,
559 stateStrict = strict }
560 let csslink = if null css
561 then ""
562 else concatMap
563 (\f -> "<link rel=\"stylesheet\" href=\"" ++
564 f ++ "\" type=\"text/css\" media=\"all\" />\n")
565 css
566 let header = (if (customHeader == "DEFAULT")
567 then defaultHeader
568 else customHeader) ++ csslink ++ includeHeader
569 let writerOptions = WriterOptions { writerStandalone = standalone',
570 writerHeader = header,
571 writerTitlePrefix = titlePrefix,
572 writerTabStop = tabStop,
573 writerTableOfContents = toc &&
574 (not strict) &&
575 writerName' /= "s5",
576 writerHTMLMathMethod = mathMethod,
577 writerS5 = (writerName' == "s5"),
578 writerIgnoreNotes = False,
579 writerIncremental = incremental,
580 writerNumberSections = numberSections,
581 writerIncludeBefore = includeBefore,
582 writerIncludeAfter = includeAfter,
583 writerStrictMarkdown = strict,
584 writerReferenceLinks = referenceLinks,
585 writerWrapText = wrap }
587 if isNonTextOutput writerName' && outputFile == "-"
588 then do hPutStrLn stderr ("Error: Cannot write " ++ writerName ++ " output to stdout.\n" ++
589 "Specify an output file using the -o option.")
590 exitWith $ ExitFailure 5
591 else return ()
593 let sourceDirRelative = if null sources
594 then ""
595 else takeDirectory (head sources)
597 let writeOutput = case writerName' of
598 "odt" -> saveOpenDocumentAsODT outputFile sourceDirRelative
599 _ -> if outputFile == "-"
600 then putStrLn
601 else writeFile outputFile . (++ "\n")
603 fmap (reader startParserState . tabFilter tabStop . intercalate "\n")
604 (readSources sources) >>=
605#ifdef _CITEPROC
606 processBiblio cslFile refs >>=
607#endif
608 writeOutput . writer writerOptions
610 where
611 readSources [] = mapM readSource ["-"]
612 readSources sources = mapM readSource sources
613 readSource "-" = getContents
614 readSource source = readFile source