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