git.iko.soy/typeable/pandoc
on 035f0ff help sign in
624 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-8 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" ++ wrapWords 78 languages
74-- | Converts a list of strings into a single string with the items printed as
75-- comma separated words in lines with a maximum line length.
76wrapWords :: Int -> [String] -> String
77wrapWords c = wrap' c c where
78 wrap' _ _ [] = ""
79 wrap' cols remaining (x:xs) = if remaining == cols
80 then x ++ wrap' cols (remaining - length x) xs
81 else if (length x + 1) > remaining
82 then ",\n" ++ x ++ wrap' cols (cols - length x) xs
83 else ", " ++ x ++ wrap' cols (remaining - (length x + 2)) xs
85-- | Association list of formats and readers.
86readers :: [(String, ParserState -> String -> Pandoc)]
87readers = [("native" , readPandoc)
88 ,("markdown" , readMarkdown)
89 ,("rst" , readRST)
90 ,("html" , readHtml)
91 ,("latex" , readLaTeX)
92 ]
94-- | Reader for native Pandoc format.
95readPandoc :: ParserState -> String -> Pandoc
96readPandoc _ input = read input
98-- | Association list of formats and pairs of writers and default headers.
99writers :: [ ( String, ( WriterOptions -> Pandoc -> String, String ) ) ]
100writers = [("native" , (writeDoc, ""))
101 ,("html" , (writeHtmlString, ""))
102 ,("s5" , (writeS5String, defaultS5Header))
103 ,("docbook" , (writeDocbook, defaultDocbookHeader))
104 ,("opendocument" , (writeOpenDocument, defaultOpenDocumentHeader))
105 ,("odt" , (writeOpenDocument, defaultOpenDocumentHeader))
106 ,("latex" , (writeLaTeX, defaultLaTeXHeader))
107 ,("context" , (writeConTeXt, defaultConTeXtHeader))
108 ,("texinfo" , (writeTexinfo, ""))
109 ,("man" , (writeMan, ""))
110 ,("markdown" , (writeMarkdown, ""))
111 ,("rst" , (writeRST, ""))
112 ,("mediawiki" , (writeMediaWiki, ""))
113 ,("rtf" , (writeRTF, defaultRTFHeader))
114 ]
116isNonTextOutput :: String -> Bool
117isNonTextOutput = (`elem` ["odt"])
119-- | Writer for Pandoc native format.
120writeDoc :: WriterOptions -> Pandoc -> String
121writeDoc _ = prettyPandoc
123-- | Data structure for command line options.
124data Opt = Opt
125 { optPreserveTabs :: Bool -- ^ Convert tabs to spaces
126 , optTabStop :: Int -- ^ Number of spaces per tab
127 , optStandalone :: Bool -- ^ Include header, footer
128 , optReader :: String -- ^ Reader format
129 , optWriter :: String -- ^ Writer format
130 , optParseRaw :: Bool -- ^ Parse unconvertable HTML and TeX
131 , optCSS :: [String] -- ^ CSS file to link to
132 , optTableOfContents :: Bool -- ^ Include table of contents
133 , optIncludeInHeader :: String -- ^ File to include in header
134 , optIncludeBeforeBody :: String -- ^ File to include at top of body
135 , optIncludeAfterBody :: String -- ^ File to include at end of body
136 , optCustomHeader :: String -- ^ Custom header to use, or "DEFAULT"
137 , optTitlePrefix :: String -- ^ Optional prefix for HTML title
138 , optOutputFile :: String -- ^ Name of output file
139 , optNumberSections :: Bool -- ^ Number sections in LaTeX
140 , optIncremental :: Bool -- ^ Use incremental lists in S5
141 , optSmart :: Bool -- ^ Use smart typography
142 , optHTMLMathMethod :: HTMLMathMethod -- ^ Method to print HTML math
143 , optDumpArgs :: Bool -- ^ Output command-line arguments
144 , optIgnoreArgs :: Bool -- ^ Ignore command-line arguments
145 , optStrict :: Bool -- ^ Use strict markdown syntax
146 , optReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
147 , optWrapText :: Bool -- ^ Wrap text
148 , optSanitizeHTML :: Bool -- ^ Sanitize HTML
149#ifdef _CITEPROC
150 , optModsFile :: String
151 , optCslFile :: String
152#endif
153 }
155-- | Defaults for command-line options.
156defaultOpts :: Opt
157defaultOpts = Opt
158 { optPreserveTabs = False
159 , optTabStop = 4
160 , optStandalone = False
161 , optReader = "" -- null for default reader
162 , optWriter = "" -- null for default writer
163 , optParseRaw = False
164 , optCSS = []
165 , optTableOfContents = False
166 , optIncludeInHeader = ""
167 , optIncludeBeforeBody = ""
168 , optIncludeAfterBody = ""
169 , optCustomHeader = "DEFAULT"
170 , optTitlePrefix = ""
171 , optOutputFile = "-" -- "-" means stdout
172 , optNumberSections = False
173 , optIncremental = False
174 , optSmart = False
175 , optHTMLMathMethod = PlainMath
176 , optDumpArgs = False
177 , optIgnoreArgs = False
178 , optStrict = False
179 , optReferenceLinks = False
180 , optWrapText = True
181 , optSanitizeHTML = False
182#ifdef _CITEPROC
183 , optModsFile = []
184 , optCslFile = []
185#endif
186 }
188-- | A list of functions, each transforming the options data structure
189-- in response to a command-line option.
190options :: [OptDescr (Opt -> IO Opt)]
191options =
192 [ Option "fr" ["from","read"]
193 (ReqArg
194 (\arg opt -> return opt { optReader = map toLower arg })
195 "FORMAT")
196 "" -- ("(" ++ (intercalate ", " $ map fst readers) ++ ")")
198 , Option "tw" ["to","write"]
199 (ReqArg
200 (\arg opt -> return opt { optWriter = map toLower arg })
201 "FORMAT")
202 "" -- ("(" ++ (intercalate ", " $ map fst writers) ++ ")")
204 , Option "s" ["standalone"]
205 (NoArg
206 (\opt -> return opt { optStandalone = True }))
207 "" -- "Include needed header and footer on output"
209 , Option "o" ["output"]
210 (ReqArg
211 (\arg opt -> return opt { optOutputFile = arg })
212 "FILENAME")
213 "" -- "Name of output file"
215 , Option "p" ["preserve-tabs"]
216 (NoArg
217 (\opt -> return opt { optPreserveTabs = True }))
218 "" -- "Preserve tabs instead of converting to spaces"
220 , Option "" ["tab-stop"]
221 (ReqArg
222 (\arg opt -> return opt { optTabStop = (read arg) } )
223 "TABSTOP")
224 "" -- "Tab stop (default 4)"
226 , Option "" ["strict"]
227 (NoArg
228 (\opt -> return opt { optStrict = True } ))
229 "" -- "Disable markdown syntax extensions"
231 , Option "" ["reference-links"]
232 (NoArg
233 (\opt -> return opt { optReferenceLinks = True } ))
234 "" -- "Use reference links in parsing HTML"
236 , Option "R" ["parse-raw"]
237 (NoArg
238 (\opt -> return opt { optParseRaw = True }))
239 "" -- "Parse untranslatable HTML codes and LaTeX environments as raw"
241 , Option "S" ["smart"]
242 (NoArg
243 (\opt -> return opt { optSmart = True }))
244 "" -- "Use smart quotes, dashes, and ellipses"
246 , Option "m" ["latexmathml", "asciimathml"]
247 (OptArg
248 (\arg opt -> return opt { optHTMLMathMethod =
249 LaTeXMathML arg })
250 "URL")
251 "" -- "Use LaTeXMathML script in html output"
253 , Option "" ["mimetex"]
254 (OptArg
255 (\arg opt -> return opt { optHTMLMathMethod = MimeTeX
256 (fromMaybe "/cgi-bin/mimetex.cgi" arg)})
257 "URL")
258 "" -- "Use mimetex for HTML math"
260 , Option "" ["jsmath"]
261 (OptArg
262 (\arg opt -> return opt { optHTMLMathMethod = JsMath arg})
263 "URL")
264 "" -- "Use jsMath for HTML math"
266 , Option "" ["gladtex"]
267 (NoArg
268 (\opt -> return opt { optHTMLMathMethod = GladTeX }))
269 "" -- "Use gladtex for HTML math"
271 , Option "i" ["incremental"]
272 (NoArg
273 (\opt -> return opt { optIncremental = True }))
274 "" -- "Make list items display incrementally in S5"
276 , Option "N" ["number-sections"]
277 (NoArg
278 (\opt -> return opt { optNumberSections = True }))
279 "" -- "Number sections in LaTeX"
281 , Option "" ["no-wrap"]
282 (NoArg
283 (\opt -> return opt { optWrapText = False }))
284 "" -- "Do not wrap text in output"
286 , Option "" ["sanitize-html"]
287 (NoArg
288 (\opt -> return opt { optSanitizeHTML = True }))
289 "" -- "Sanitize HTML"
291 , Option "" ["toc", "table-of-contents"]
292 (NoArg
293 (\opt -> return opt { optTableOfContents = True }))
294 "" -- "Include table of contents"
296 , Option "c" ["css"]
297 (ReqArg
298 (\arg opt -> do
299 let old = optCSS opt
300 return opt { optCSS = old ++ [arg],
301 optStandalone = True })
302 "CSS")
303 "" -- "Link to CSS style sheet"
305 , Option "H" ["include-in-header"]
306 (ReqArg
307 (\arg opt -> do
308 let old = optIncludeInHeader opt
309 text <- readFile arg
310 return opt { optIncludeInHeader = old ++ text,
311 optStandalone = True })
312 "FILENAME")
313 "" -- "File to include at end of header (implies -s)"
315 , Option "B" ["include-before-body"]
316 (ReqArg
317 (\arg opt -> do
318 let old = optIncludeBeforeBody opt
319 text <- readFile arg
320 return opt { optIncludeBeforeBody = old ++ text })
321 "FILENAME")
322 "" -- "File to include before document body"
324 , Option "A" ["include-after-body"]
325 (ReqArg
326 (\arg opt -> do
327 let old = optIncludeAfterBody opt
328 text <- readFile arg
329 return opt { optIncludeAfterBody = old ++ text })
330 "FILENAME")
331 "" -- "File to include after document body"
333 , Option "C" ["custom-header"]
334 (ReqArg
335 (\arg opt -> do
336 text <- readFile arg
337 return opt { optCustomHeader = text,
338 optStandalone = True })
339 "FILENAME")
340 "" -- "File to use for custom header (implies -s)"
342 , Option "T" ["title-prefix"]
343 (ReqArg
344 (\arg opt -> return opt { optTitlePrefix = arg,
345 optStandalone = True })
346 "STRING")
347 "" -- "String to prefix to HTML window title"
349 , Option "D" ["print-default-header"]
350 (ReqArg
351 (\arg _ -> do
352 let header = case (lookup arg writers) of
353 Just (_, h) -> h
354 Nothing -> error ("Unknown reader: " ++ arg)
355 hPutStr stdout header
356 exitWith ExitSuccess)
357 "FORMAT")
358 "" -- "Print default header for FORMAT"
359#ifdef _CITEPROC
360 , Option "" ["mods"]
361 (ReqArg
362 (\arg opt -> return opt { optModsFile = arg} )
363 "FILENAME")
364 ""
365 , Option "" ["csl"]
366 (ReqArg
367 (\arg opt -> return opt { optCslFile = arg} )
368 "FILENAME")
369 ""
370#endif
371 , Option "" ["dump-args"]
372 (NoArg
373 (\opt -> return opt { optDumpArgs = True }))
374 "" -- "Print output filename and arguments to stdout."
376 , Option "" ["ignore-args"]
377 (NoArg
378 (\opt -> return opt { optIgnoreArgs = True }))
379 "" -- "Ignore command-line arguments."
381 , Option "v" ["version"]
382 (NoArg
383 (\_ -> do
384 prg <- getProgName
385 hPutStrLn stderr (prg ++ " " ++ pandocVersion ++ compileInfo ++
386 copyrightMessage)
387 exitWith $ ExitFailure 4))
388 "" -- "Print version"
390 , Option "h" ["help"]
391 (NoArg
392 (\_ -> do
393 prg <- getProgName
394 hPutStr stderr (usageMessage prg options)
395 exitWith $ ExitFailure 2))
396 "" -- "Show help"
397 ]
399-- Returns usage message
400usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
401usageMessage programName opts = usageInfo
402 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
403 (intercalate ", " $ map fst readers) ++ "\nOutput formats: " ++
404 (intercalate ", " $ map fst writers) ++ "\nOptions:")
405 opts
407-- Determine default reader based on source file extensions
408defaultReaderName :: [FilePath] -> String
409defaultReaderName [] = "markdown"
410defaultReaderName (x:xs) =
411 case takeExtension (map toLower x) of
412 ".xhtml" -> "html"
413 ".html" -> "html"
414 ".htm" -> "html"
415 ".tex" -> "latex"
416 ".latex" -> "latex"
417 ".ltx" -> "latex"
418 ".rst" -> "rst"
419 ".native" -> "native"
420 _ -> defaultReaderName xs
422-- Determine default writer based on output file extension
423defaultWriterName :: FilePath -> String
424defaultWriterName "-" = "html" -- no output file
425defaultWriterName x =
426 case takeExtension (map toLower x) of
427 "" -> "markdown" -- empty extension
428 ".tex" -> "latex"
429 ".latex" -> "latex"
430 ".ltx" -> "latex"
431 ".context" -> "context"
432 ".ctx" -> "context"
433 ".rtf" -> "rtf"
434 ".rst" -> "rst"
435 ".s5" -> "s5"
436 ".native" -> "native"
437 ".txt" -> "markdown"
438 ".text" -> "markdown"
439 ".md" -> "markdown"
440 ".markdown" -> "markdown"
441 ".texi" -> "texinfo"
442 ".texinfo" -> "texinfo"
443 ".db" -> "docbook"
444 ".odt" -> "odt"
445 ['.',y] | y `elem` ['1'..'9'] -> "man"
446 _ -> "html"
448main :: IO ()
449main = do
451 rawArgs <- getArgs
452 prg <- getProgName
453 let compatMode = (prg == "hsmarkdown")
455 let (actions, args, errors) = if compatMode
456 then ([], rawArgs, [])
457 else getOpt Permute options rawArgs
459 if (not (null errors))
460 then do
461 name <- getProgName
462 mapM (\e -> hPutStrLn stderr e) errors
463 hPutStr stderr (usageMessage name options)
464 exitWith $ ExitFailure 2
465 else
466 return ()
468 let defaultOpts' = if compatMode
469 then defaultOpts { optReader = "markdown"
470 , optWriter = "html"
471 , optStrict = True }
472 else defaultOpts
474 -- thread option data structure through all supplied option actions
475 opts <- foldl (>>=) (return defaultOpts') actions
477 let Opt { optPreserveTabs = preserveTabs
478 , optTabStop = tabStop
479 , optStandalone = standalone
480 , optReader = readerName
481 , optWriter = writerName
482 , optParseRaw = parseRaw
483 , optCSS = css
484 , optTableOfContents = toc
485 , optIncludeInHeader = includeHeader
486 , optIncludeBeforeBody = includeBefore
487 , optIncludeAfterBody = includeAfter
488 , optCustomHeader = customHeader
489 , optTitlePrefix = titlePrefix
490 , optOutputFile = outputFile
491 , optNumberSections = numberSections
492 , optIncremental = incremental
493 , optSmart = smart
494 , optHTMLMathMethod = mathMethod
495 , optDumpArgs = dumpArgs
496 , optIgnoreArgs = ignoreArgs
497 , optStrict = strict
498 , optReferenceLinks = referenceLinks
499 , optWrapText = wrap
500 , optSanitizeHTML = sanitize
501#ifdef _CITEPROC
502 , optModsFile = modsFile
503 , optCslFile = cslFile
504#endif
505 } = opts
507 if dumpArgs
508 then do
509 hPutStrLn stdout outputFile
510 mapM (\arg -> hPutStrLn stdout arg) args
511 exitWith $ ExitSuccess
512 else return ()
514 let sources = if ignoreArgs then [] else args
516 -- assign reader and writer based on options and filenames
517 let readerName' = if null readerName
518 then defaultReaderName sources
519 else readerName
521 let writerName' = if null writerName
522 then defaultWriterName outputFile
523 else writerName
525 reader <- case (lookup readerName' readers) of
526 Just r -> return r
527 Nothing -> error ("Unknown reader: " ++ readerName')
529 (writer, defaultHeader) <- case (lookup writerName' writers) of
530 Just (w,h) -> return (w, h)
531 Nothing -> error ("Unknown writer: " ++ writerName')
533 environment <- getEnvironment
534 let columns = case lookup "COLUMNS" environment of
535 Just cols -> read cols
536 Nothing -> stateColumns defaultParserState
538 let tabFilter _ [] = ""
539 tabFilter _ ('\n':xs) = '\n':(tabFilter tabStop xs)
540 -- remove DOS line endings
541 tabFilter _ ('\r':'\n':xs) = '\n':(tabFilter tabStop xs)
542 tabFilter _ ('\r':xs) = '\n':(tabFilter tabStop xs)
543 tabFilter spsToNextStop ('\t':xs) =
544 if preserveTabs
545 then '\t':(tabFilter tabStop xs)
546 else replicate spsToNextStop ' ' ++ tabFilter tabStop xs
547 tabFilter 1 (x:xs) =
548 x:(tabFilter tabStop xs)
549 tabFilter spsToNextStop (x:xs) =
550 x:(tabFilter (spsToNextStop - 1) xs)
552 let standalone' = (standalone && not strict) || isNonTextOutput writerName'
554#ifdef _CITEPROC
555 refs <- if null modsFile then return [] else readModsColletionFile modsFile
556#endif
558 let startParserState =
559 defaultParserState { stateParseRaw = parseRaw,
560 stateTabStop = tabStop,
561 stateSanitizeHTML = sanitize,
562 stateStandalone = standalone',
563#ifdef _CITEPROC
564 stateCitations = map citeKey refs,
565#endif
566 stateSmart = smart || writerName' `elem`
567 ["latex", "context"],
568 stateColumns = columns,
569 stateStrict = strict }
570 let csslink = if null css
571 then ""
572 else concatMap
573 (\f -> "<link rel=\"stylesheet\" href=\"" ++
574 f ++ "\" type=\"text/css\" media=\"all\" />\n")
575 css
576 let header = (if (customHeader == "DEFAULT")
577 then defaultHeader
578 else customHeader) ++ csslink ++ includeHeader
579 let writerOptions = WriterOptions { writerStandalone = standalone',
580 writerHeader = header,
581 writerTitlePrefix = titlePrefix,
582 writerTabStop = tabStop,
583 writerTableOfContents = toc &&
584 (not strict) &&
585 writerName' /= "s5",
586 writerHTMLMathMethod = mathMethod,
587 writerS5 = (writerName' == "s5"),
588 writerIgnoreNotes = False,
589 writerIncremental = incremental,
590 writerNumberSections = numberSections,
591 writerIncludeBefore = includeBefore,
592 writerIncludeAfter = includeAfter,
593 writerStrictMarkdown = strict,
594 writerReferenceLinks = referenceLinks,
595 writerWrapText = wrap }
597 if isNonTextOutput writerName' && outputFile == "-"
598 then do hPutStrLn stderr ("Error: Cannot write " ++ writerName ++ " output to stdout.\n" ++
599 "Specify an output file using the -o option.")
600 exitWith $ ExitFailure 5
601 else return ()
603 let sourceDirRelative = if null sources
604 then ""
605 else takeDirectory (head sources)
607 let writeOutput = case writerName' of
608 "odt" -> saveOpenDocumentAsODT outputFile sourceDirRelative
609 _ -> if outputFile == "-"
610 then putStrLn
611 else writeFile outputFile . (++ "\n")
613 fmap (reader startParserState . tabFilter tabStop . intercalate "\n")
614 (readSources sources) >>=
615#ifdef _CITEPROC
616 processBiblio cslFile refs >>=
617#endif
618 writeOutput . writer writerOptions
620 where
621 readSources [] = mapM readSource ["-"]
622 readSources sources = mapM readSource sources
623 readSource "-" = getContents
624 readSource source = readFile source