git.iko.soy/typeable/pandoc
on 229b310 help sign in
489 lines 20 kB history raw
1{-
2Copyright (C) 2006-7 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-7 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.UTF8
34import Text.Pandoc.ASCIIMathML
35import Text.Pandoc.Shared ( joinWithSep, tabsToSpaces )
36import Text.Regex ( mkRegex, matchRegex )
37import System.Environment ( getArgs, getProgName, getEnvironment )
38import System.Exit ( exitWith, ExitCode (..) )
39import System.Console.GetOpt
40import System.IO
41import Data.Maybe ( fromMaybe )
42import Data.List ( isPrefixOf )
43import Data.Char ( toLower )
44import Control.Monad ( (>>=) )
46version :: String
47version = "0.4"
49copyrightMessage :: String
50copyrightMessage = "\nCopyright (C) 2006-7 John MacFarlane\n\
51 \Web: http://sophos.berkeley.edu/macfarlane/pandoc\n\
52 \This is free software; see the source for copying conditions. There is no\n\
53 \warranty, not even for merchantability or fitness for a particular purpose."
55-- | Association list of formats and readers.
56readers :: [(String, ParserState -> String -> Pandoc)]
57readers = [("native" , readPandoc)
58 ,("markdown" , readMarkdown)
59 ,("rst" , readRST)
60 ,("html" , readHtml)
61 ,("latex" , readLaTeX)
62 ]
64-- | Reader for native Pandoc format.
65readPandoc :: ParserState -> String -> Pandoc
66readPandoc state input = read input
68-- | Association list of formats and pairs of writers and default headers.
69writers :: [ ( String, ( WriterOptions -> Pandoc -> String, String ) ) ]
70writers = [("native" , (writeDoc, ""))
71 ,("html" , (writeHtmlString, ""))
72 ,("s5" , (writeS5String, defaultS5Header))
73 ,("docbook" , (writeDocbook, defaultDocbookHeader))
74 ,("latex" , (writeLaTeX, defaultLaTeXHeader))
75 ,("context" , (writeConTeXt, defaultConTeXtHeader))
76 ,("man" , (writeMan, ""))
77 ,("markdown" , (writeMarkdown, ""))
78 ,("rst" , (writeRST, ""))
79 ,("rtf" , (writeRTF, defaultRTFHeader))
80 ]
82-- | Writer for Pandoc native format.
83writeDoc :: WriterOptions -> Pandoc -> String
84writeDoc options = prettyPandoc
86-- | Data structure for command line options.
87data Opt = Opt
88 { optPreserveTabs :: Bool -- ^ Convert tabs to spaces
89 , optTabStop :: Int -- ^ Number of spaces per tab
90 , optStandalone :: Bool -- ^ Include header, footer
91 , optReader :: String -- ^ Reader format
92 , optWriter :: String -- ^ Writer format
93 , optParseRaw :: Bool -- ^ Parse unconvertable HTML and TeX
94 , optCSS :: String -- ^ CSS file to link to
95 , optTableOfContents :: Bool -- ^ Include table of contents
96 , optIncludeInHeader :: String -- ^ File to include in header
97 , optIncludeBeforeBody :: String -- ^ File to include at top of body
98 , optIncludeAfterBody :: String -- ^ File to include at end of body
99 , optCustomHeader :: String -- ^ Custom header to use, or "DEFAULT"
100 , optTitlePrefix :: String -- ^ Optional prefix for HTML title
101 , optOutputFile :: String -- ^ Name of output file
102 , optNumberSections :: Bool -- ^ Number sections in LaTeX
103 , optIncremental :: Bool -- ^ Use incremental lists in S5
104 , optSmart :: Bool -- ^ Use smart typography
105 , optASCIIMathML :: Bool -- ^ Use ASCIIMathML in HTML
106 , optDumpArgs :: Bool -- ^ Output command-line arguments
107 , optIgnoreArgs :: Bool -- ^ Ignore command-line arguments
108 , optStrict :: Bool -- ^ Use strict markdown syntax
109 , optReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
110 }
112-- | Defaults for command-line options.
113defaultOpts :: Opt
114defaultOpts = Opt
115 { optPreserveTabs = False
116 , optTabStop = 4
117 , optStandalone = False
118 , optReader = "" -- null for default reader
119 , optWriter = "" -- null for default writer
120 , optParseRaw = False
121 , optCSS = ""
122 , optTableOfContents = False
123 , optIncludeInHeader = ""
124 , optIncludeBeforeBody = ""
125 , optIncludeAfterBody = ""
126 , optCustomHeader = "DEFAULT"
127 , optTitlePrefix = ""
128 , optOutputFile = "-" -- "-" means stdout
129 , optNumberSections = False
130 , optIncremental = False
131 , optSmart = False
132 , optASCIIMathML = False
133 , optDumpArgs = False
134 , optIgnoreArgs = False
135 , optStrict = False
136 , optReferenceLinks = False
137 }
139-- | A list of functions, each transforming the options data structure
140-- in response to a command-line option.
141options :: [OptDescr (Opt -> IO Opt)]
142options =
143 [ Option "fr" ["from","read"]
144 (ReqArg
145 (\arg opt -> return opt { optReader = map toLower arg })
146 "FORMAT")
147 "" -- ("(" ++ (joinWithSep ", " $ map fst readers) ++ ")")
149 , Option "tw" ["to","write"]
150 (ReqArg
151 (\arg opt -> return opt { optWriter = map toLower arg })
152 "FORMAT")
153 "" -- ("(" ++ (joinWithSep ", " $ map fst writers) ++ ")")
155 , Option "s" ["standalone"]
156 (NoArg
157 (\opt -> return opt { optStandalone = True }))
158 "" -- "Include needed header and footer on output"
160 , Option "o" ["output"]
161 (ReqArg
162 (\arg opt -> return opt { optOutputFile = arg })
163 "FILENAME")
164 "" -- "Name of output file"
166 , Option "p" ["preserve-tabs"]
167 (NoArg
168 (\opt -> return opt { optPreserveTabs = True }))
169 "" -- "Preserve tabs instead of converting to spaces"
171 , Option "" ["tab-stop"]
172 (ReqArg
173 (\arg opt -> return opt { optTabStop = (read arg) } )
174 "TABSTOP")
175 "" -- "Tab stop (default 4)"
177 , Option "" ["strict"]
178 (NoArg
179 (\opt -> return opt { optStrict = True } ))
180 "" -- "Use strict markdown syntax with no extensions"
182 , Option "" ["reference-links"]
183 (NoArg
184 (\opt -> return opt { optReferenceLinks = True } ))
185 "" -- "Use reference links in parsing HTML"
187 , Option "R" ["parse-raw"]
188 (NoArg
189 (\opt -> return opt { optParseRaw = True }))
190 "" -- "Parse untranslatable HTML codes and LaTeX environments as raw"
192 , Option "S" ["smart"]
193 (NoArg
194 (\opt -> return opt { optSmart = True }))
195 "" -- "Use smart quotes, dashes, and ellipses"
197 , Option "m" ["asciimathml"]
198 (NoArg
199 (\opt -> return opt { optASCIIMathML = True,
200 optStandalone = True }))
201 "" -- "Use ASCIIMathML script in html output"
203 , Option "i" ["incremental"]
204 (NoArg
205 (\opt -> return opt { optIncremental = True }))
206 "" -- "Make list items display incrementally in S5"
208 , Option "N" ["number-sections"]
209 (NoArg
210 (\opt -> return opt { optNumberSections = True }))
211 "" -- "Number sections in LaTeX"
213 , Option "" ["toc", "table-of-contents"]
214 (NoArg
215 (\opt -> return opt { optTableOfContents = True }))
216 "" -- "Include table of contents"
218 , Option "c" ["css"]
219 (ReqArg
220 (\arg opt -> return opt { optCSS = arg,
221 optStandalone = True })
222 "CSS")
223 "" -- "Link to CSS style sheet"
225 , Option "H" ["include-in-header"]
226 (ReqArg
227 (\arg opt -> do
228 text <- readFile arg
229 return opt { optIncludeInHeader = text,
230 optStandalone = True })
231 "FILENAME")
232 "" -- "File to include at end of header (implies -s)"
234 , Option "B" ["include-before-body"]
235 (ReqArg
236 (\arg opt -> do
237 text <- readFile arg
238 return opt { optIncludeBeforeBody = text })
239 "FILENAME")
240 "" -- "File to include before document body"
242 , Option "A" ["include-after-body"]
243 (ReqArg
244 (\arg opt -> do
245 text <- readFile arg
246 return opt { optIncludeAfterBody = text })
247 "FILENAME")
248 "" -- "File to include after document body"
250 , Option "C" ["custom-header"]
251 (ReqArg
252 (\arg opt -> do
253 text <- readFile arg
254 return opt { optCustomHeader = text,
255 optStandalone = True })
256 "FILENAME")
257 "" -- "File to use for custom header (implies -s)"
259 , Option "T" ["title-prefix"]
260 (ReqArg
261 (\arg opt -> return opt { optTitlePrefix = arg,
262 optStandalone = True })
263 "STRING")
264 "" -- "String to prefix to HTML window title"
266 , Option "D" ["print-default-header"]
267 (ReqArg
268 (\arg opt -> do
269 let header = case (lookup arg writers) of
270 Just (writer, head) -> head
271 Nothing -> error ("Unknown reader: " ++ arg)
272 hPutStr stdout header
273 exitWith ExitSuccess)
274 "FORMAT")
275 "" -- "Print default header for FORMAT"
277 , Option "" ["dump-args"]
278 (NoArg
279 (\opt -> return opt { optDumpArgs = True }))
280 "" -- "Print output filename and arguments to stdout."
282 , Option "" ["ignore-args"]
283 (NoArg
284 (\opt -> return opt { optIgnoreArgs = True }))
285 "" -- "Ignore command-line arguments."
287 , Option "v" ["version"]
288 (NoArg
289 (\_ -> do
290 prg <- getProgName
291 hPutStrLn stderr (prg ++ " " ++ version ++
292 copyrightMessage)
293 exitWith $ ExitFailure 4))
294 "" -- "Print version"
296 , Option "h" ["help"]
297 (NoArg
298 (\_ -> do
299 prg <- getProgName
300 hPutStr stderr (usageMessage prg options)
301 exitWith $ ExitFailure 2))
302 "" -- "Show help"
303 ]
305-- Returns usage message
306usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
307usageMessage programName options = usageInfo
308 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
309 (joinWithSep ", " $ map fst readers) ++ "\nOutput formats: " ++
310 (joinWithSep ", " $ map fst writers) ++ "\nOptions:")
311 options
313-- Determine default reader based on source file extensions
314defaultReaderName :: [String] -> String
315defaultReaderName [] = "markdown"
316defaultReaderName (x:xs) =
317 let x' = map toLower x in
318 case (matchRegex (mkRegex ".*\\.(.*)") x') of
319 Nothing -> defaultReaderName xs -- no extension
320 Just ["xhtml"] -> "html"
321 Just ["html"] -> "html"
322 Just ["htm"] -> "html"
323 Just ["tex"] -> "latex"
324 Just ["latex"] -> "latex"
325 Just ["ltx"] -> "latex"
326 Just ["rst"] -> "rst"
327 Just ["native"] -> "native"
328 Just _ -> "markdown"
330-- Determine default writer based on output file extension
331defaultWriterName :: String -> String
332defaultWriterName "-" = "html" -- no output file
333defaultWriterName x =
334 let x' = map toLower x in
335 case (matchRegex (mkRegex ".*\\.(.*)") x') of
336 Nothing -> "markdown" -- no extension
337 Just [""] -> "markdown" -- empty extension
338 Just ["tex"] -> "latex"
339 Just ["latex"] -> "latex"
340 Just ["ltx"] -> "latex"
341 Just ["context"] -> "context"
342 Just ["ctx"] -> "context"
343 Just ["rtf"] -> "rtf"
344 Just ["rst"] -> "rst"
345 Just ["s5"] -> "s5"
346 Just ["native"] -> "native"
347 Just ["txt"] -> "markdown"
348 Just ["text"] -> "markdown"
349 Just ["md"] -> "markdown"
350 Just ["markdown"] -> "markdown"
351 Just ["db"] -> "docbook"
352 Just ["xml"] -> "docbook"
353 Just ["sgml"] -> "docbook"
354 Just [[x]] | x `elem` ['1'..'9'] -> "man"
355 Just _ -> "html"
357main = do
359 rawArgs <- getArgs
360 prg <- getProgName
361 let compatMode = (prg == "hsmarkdown")
363 let (actions, args, errors) = if compatMode
364 then ([], rawArgs, [])
365 else getOpt Permute options rawArgs
367 if (not (null errors))
368 then do
369 name <- getProgName
370 mapM (\e -> hPutStrLn stderr e) errors
371 hPutStr stderr (usageMessage name options)
372 exitWith $ ExitFailure 2
373 else
374 return ()
376 let defaultOpts' = if compatMode
377 then defaultOpts { optReader = "markdown"
378 , optWriter = "html"
379 , optStrict = True }
380 else defaultOpts
382 -- thread option data structure through all supplied option actions
383 opts <- foldl (>>=) (return defaultOpts') actions
385 let Opt { optPreserveTabs = preserveTabs
386 , optTabStop = tabStop
387 , optStandalone = standalone
388 , optReader = readerName
389 , optWriter = writerName
390 , optParseRaw = parseRaw
391 , optCSS = css
392 , optTableOfContents = toc
393 , optIncludeInHeader = includeHeader
394 , optIncludeBeforeBody = includeBefore
395 , optIncludeAfterBody = includeAfter
396 , optCustomHeader = customHeader
397 , optTitlePrefix = titlePrefix
398 , optOutputFile = outputFile
399 , optNumberSections = numberSections
400 , optIncremental = incremental
401 , optSmart = smart
402 , optASCIIMathML = asciiMathML
403 , optDumpArgs = dumpArgs
404 , optIgnoreArgs = ignoreArgs
405 , optStrict = strict
406 , optReferenceLinks = referenceLinks
407 } = opts
409 if dumpArgs
410 then do
411 hPutStrLn stdout outputFile
412 mapM (\arg -> hPutStrLn stdout arg) args
413 exitWith $ ExitSuccess
414 else return ()
416 let sources = if ignoreArgs then [] else args
418 -- assign reader and writer based on options and filenames
419 let readerName' = if null readerName
420 then defaultReaderName sources
421 else readerName
423 let writerName' = if null writerName
424 then defaultWriterName outputFile
425 else writerName
427 reader <- case (lookup readerName' readers) of
428 Just r -> return r
429 Nothing -> error ("Unknown reader: " ++ readerName')
431 (writer, defaultHeader) <- case (lookup writerName' writers) of
432 Just (w,h) -> return (w, h)
433 Nothing -> error ("Unknown writer: " ++ writerName')
435 output <- if (outputFile == "-")
436 then return stdout
437 else openFile outputFile WriteMode
439 environment <- getEnvironment
440 let columns = case lookup "COLUMNS" environment of
441 Just cols -> read cols
442 Nothing -> stateColumns defaultParserState
444 let tabFilter = if preserveTabs then id else (tabsToSpaces tabStop)
445 let removeCRs str = filter (/= '\r') str -- remove DOS-style line endings
446 let startParserState =
447 defaultParserState { stateParseRaw = parseRaw,
448 stateTabStop = tabStop,
449 stateStandalone = standalone && (not strict),
450 stateSmart = smart || writerName' == "latex",
451 stateColumns = columns,
452 stateStrict = strict }
453 let csslink = if (css == "")
454 then ""
455 else "<link rel=\"stylesheet\" href=\"" ++ css ++
456 "\" type=\"text/css\" media=\"all\" />\n"
457 let asciiMathMLLink = if asciiMathML then asciiMathMLScript else ""
458 let header = (if (customHeader == "DEFAULT")
459 then defaultHeader
460 else customHeader) ++
461 csslink ++ asciiMathMLLink ++ includeHeader
462 let writerOptions = WriterOptions { writerStandalone = standalone &&
463 (not strict),
464 writerHeader = header,
465 writerTitlePrefix = titlePrefix,
466 writerTabStop = tabStop,
467 writerTableOfContents = toc &&
468 (not strict) &&
469 writerName/="s5",
470 writerS5 = (writerName=="s5"),
471 writerIgnoreNotes = False,
472 writerIncremental = incremental,
473 writerNumberSections = numberSections,
474 writerIncludeBefore = includeBefore,
475 writerIncludeAfter = includeAfter,
476 writerStrictMarkdown = strict,
477 writerReferenceLinks = referenceLinks }
479 (readSources sources) >>= (hPutStr output . toUTF8 .
480 (writer writerOptions) .
481 (reader startParserState) . tabFilter .
482 removeCRs . fromUTF8 . (joinWithSep "\n")) >>
483 hClose output
485 where
486 readSources [] = mapM readSource ["-"]
487 readSources sources = mapM readSource sources
488 readSource "-" = getContents
489 readSource source = readFile source