git.iko.soy/typeable/pandoc
on 30375bb help sign in
486 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 ,("man" , (writeMan, ""))
76 ,("markdown" , (writeMarkdown, ""))
77 ,("rst" , (writeRST, ""))
78 ,("rtf" , (writeRTF, defaultRTFHeader))
79 ]
81-- | Writer for Pandoc native format.
82writeDoc :: WriterOptions -> Pandoc -> String
83writeDoc options = prettyPandoc
85-- | Data structure for command line options.
86data Opt = Opt
87 { optPreserveTabs :: Bool -- ^ Convert tabs to spaces
88 , optTabStop :: Int -- ^ Number of spaces per tab
89 , optStandalone :: Bool -- ^ Include header, footer
90 , optReader :: String -- ^ Reader format
91 , optWriter :: String -- ^ Writer format
92 , optParseRaw :: Bool -- ^ Parse unconvertable HTML and TeX
93 , optCSS :: String -- ^ CSS file to link to
94 , optTableOfContents :: Bool -- ^ Include table of contents
95 , optIncludeInHeader :: String -- ^ File to include in header
96 , optIncludeBeforeBody :: String -- ^ File to include at top of body
97 , optIncludeAfterBody :: String -- ^ File to include at end of body
98 , optCustomHeader :: String -- ^ Custom header to use, or "DEFAULT"
99 , optTitlePrefix :: String -- ^ Optional prefix for HTML title
100 , optOutputFile :: String -- ^ Name of output file
101 , optNumberSections :: Bool -- ^ Number sections in LaTeX
102 , optIncremental :: Bool -- ^ Use incremental lists in S5
103 , optSmart :: Bool -- ^ Use smart typography
104 , optASCIIMathML :: Bool -- ^ Use ASCIIMathML in HTML
105 , optDumpArgs :: Bool -- ^ Output command-line arguments
106 , optIgnoreArgs :: Bool -- ^ Ignore command-line arguments
107 , optStrict :: Bool -- ^ Use strict markdown syntax
108 , optReferenceLinks :: Bool -- ^ Use reference links in writing markdown, rst
109 }
111-- | Defaults for command-line options.
112defaultOpts :: Opt
113defaultOpts = Opt
114 { optPreserveTabs = False
115 , optTabStop = 4
116 , optStandalone = False
117 , optReader = "" -- null for default reader
118 , optWriter = "" -- null for default writer
119 , optParseRaw = False
120 , optCSS = ""
121 , optTableOfContents = False
122 , optIncludeInHeader = ""
123 , optIncludeBeforeBody = ""
124 , optIncludeAfterBody = ""
125 , optCustomHeader = "DEFAULT"
126 , optTitlePrefix = ""
127 , optOutputFile = "-" -- "-" means stdout
128 , optNumberSections = False
129 , optIncremental = False
130 , optSmart = False
131 , optASCIIMathML = False
132 , optDumpArgs = False
133 , optIgnoreArgs = False
134 , optStrict = False
135 , optReferenceLinks = False
136 }
138-- | A list of functions, each transforming the options data structure
139-- in response to a command-line option.
140options :: [OptDescr (Opt -> IO Opt)]
141options =
142 [ Option "fr" ["from","read"]
143 (ReqArg
144 (\arg opt -> return opt { optReader = map toLower arg })
145 "FORMAT")
146 "" -- ("(" ++ (joinWithSep ", " $ map fst readers) ++ ")")
148 , Option "tw" ["to","write"]
149 (ReqArg
150 (\arg opt -> return opt { optWriter = map toLower arg })
151 "FORMAT")
152 "" -- ("(" ++ (joinWithSep ", " $ map fst writers) ++ ")")
154 , Option "s" ["standalone"]
155 (NoArg
156 (\opt -> return opt { optStandalone = True }))
157 "" -- "Include needed header and footer on output"
159 , Option "o" ["output"]
160 (ReqArg
161 (\arg opt -> return opt { optOutputFile = arg })
162 "FILENAME")
163 "" -- "Name of output file"
165 , Option "p" ["preserve-tabs"]
166 (NoArg
167 (\opt -> return opt { optPreserveTabs = True }))
168 "" -- "Preserve tabs instead of converting to spaces"
170 , Option "" ["tab-stop"]
171 (ReqArg
172 (\arg opt -> return opt { optTabStop = (read arg) } )
173 "TABSTOP")
174 "" -- "Tab stop (default 4)"
176 , Option "" ["strict"]
177 (NoArg
178 (\opt -> return opt { optStrict = True } ))
179 "" -- "Use strict markdown syntax with no extensions"
181 , Option "" ["reference-links"]
182 (NoArg
183 (\opt -> return opt { optReferenceLinks = True } ))
184 "" -- "Use reference links in parsing HTML"
186 , Option "R" ["parse-raw"]
187 (NoArg
188 (\opt -> return opt { optParseRaw = True }))
189 "" -- "Parse untranslatable HTML codes and LaTeX environments as raw"
191 , Option "S" ["smart"]
192 (NoArg
193 (\opt -> return opt { optSmart = True }))
194 "" -- "Use smart quotes, dashes, and ellipses"
196 , Option "m" ["asciimathml"]
197 (NoArg
198 (\opt -> return opt { optASCIIMathML = True,
199 optStandalone = True }))
200 "" -- "Use ASCIIMathML script in html output"
202 , Option "i" ["incremental"]
203 (NoArg
204 (\opt -> return opt { optIncremental = True }))
205 "" -- "Make list items display incrementally in S5"
207 , Option "N" ["number-sections"]
208 (NoArg
209 (\opt -> return opt { optNumberSections = True }))
210 "" -- "Number sections in LaTeX"
212 , Option "" ["toc", "table-of-contents"]
213 (NoArg
214 (\opt -> return opt { optTableOfContents = True }))
215 "" -- "Include table of contents"
217 , Option "c" ["css"]
218 (ReqArg
219 (\arg opt -> return opt { optCSS = arg,
220 optStandalone = True })
221 "CSS")
222 "" -- "Link to CSS style sheet"
224 , Option "H" ["include-in-header"]
225 (ReqArg
226 (\arg opt -> do
227 text <- readFile arg
228 return opt { optIncludeInHeader = text,
229 optStandalone = True })
230 "FILENAME")
231 "" -- "File to include at end of header (implies -s)"
233 , Option "B" ["include-before-body"]
234 (ReqArg
235 (\arg opt -> do
236 text <- readFile arg
237 return opt { optIncludeBeforeBody = text })
238 "FILENAME")
239 "" -- "File to include before document body"
241 , Option "A" ["include-after-body"]
242 (ReqArg
243 (\arg opt -> do
244 text <- readFile arg
245 return opt { optIncludeAfterBody = text })
246 "FILENAME")
247 "" -- "File to include after document body"
249 , Option "C" ["custom-header"]
250 (ReqArg
251 (\arg opt -> do
252 text <- readFile arg
253 return opt { optCustomHeader = text,
254 optStandalone = True })
255 "FILENAME")
256 "" -- "File to use for custom header (implies -s)"
258 , Option "T" ["title-prefix"]
259 (ReqArg
260 (\arg opt -> return opt { optTitlePrefix = arg,
261 optStandalone = True })
262 "STRING")
263 "" -- "String to prefix to HTML window title"
265 , Option "D" ["print-default-header"]
266 (ReqArg
267 (\arg opt -> do
268 let header = case (lookup arg writers) of
269 Just (writer, head) -> head
270 Nothing -> error ("Unknown reader: " ++ arg)
271 hPutStr stdout header
272 exitWith ExitSuccess)
273 "FORMAT")
274 "" -- "Print default header for FORMAT"
276 , Option "" ["dump-args"]
277 (NoArg
278 (\opt -> return opt { optDumpArgs = True }))
279 "" -- "Print output filename and arguments to stdout."
281 , Option "" ["ignore-args"]
282 (NoArg
283 (\opt -> return opt { optIgnoreArgs = True }))
284 "" -- "Ignore command-line arguments."
286 , Option "v" ["version"]
287 (NoArg
288 (\_ -> do
289 prg <- getProgName
290 hPutStrLn stderr (prg ++ " " ++ version ++
291 copyrightMessage)
292 exitWith $ ExitFailure 4))
293 "" -- "Print version"
295 , Option "h" ["help"]
296 (NoArg
297 (\_ -> do
298 prg <- getProgName
299 hPutStr stderr (usageMessage prg options)
300 exitWith $ ExitFailure 2))
301 "" -- "Show help"
302 ]
304-- Returns usage message
305usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
306usageMessage programName options = usageInfo
307 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
308 (joinWithSep ", " $ map fst readers) ++ "\nOutput formats: " ++
309 (joinWithSep ", " $ map fst writers) ++ "\nOptions:")
310 options
312-- Determine default reader based on source file extensions
313defaultReaderName :: [String] -> String
314defaultReaderName [] = "markdown"
315defaultReaderName (x:xs) =
316 let x' = map toLower x in
317 case (matchRegex (mkRegex ".*\\.(.*)") x') of
318 Nothing -> defaultReaderName xs -- no extension
319 Just ["xhtml"] -> "html"
320 Just ["html"] -> "html"
321 Just ["htm"] -> "html"
322 Just ["tex"] -> "latex"
323 Just ["latex"] -> "latex"
324 Just ["ltx"] -> "latex"
325 Just ["rst"] -> "rst"
326 Just ["native"] -> "native"
327 Just _ -> "markdown"
329-- Determine default writer based on output file extension
330defaultWriterName :: String -> String
331defaultWriterName "-" = "html" -- no output file
332defaultWriterName x =
333 let x' = map toLower x in
334 case (matchRegex (mkRegex ".*\\.(.*)") x') of
335 Nothing -> "markdown" -- no extension
336 Just [""] -> "markdown" -- empty extension
337 Just ["tex"] -> "latex"
338 Just ["latex"] -> "latex"
339 Just ["ltx"] -> "latex"
340 Just ["rtf"] -> "rtf"
341 Just ["rst"] -> "rst"
342 Just ["s5"] -> "s5"
343 Just ["native"] -> "native"
344 Just ["txt"] -> "markdown"
345 Just ["text"] -> "markdown"
346 Just ["md"] -> "markdown"
347 Just ["markdown"] -> "markdown"
348 Just ["db"] -> "docbook"
349 Just ["xml"] -> "docbook"
350 Just ["sgml"] -> "docbook"
351 Just [[x]] | x `elem` ['1'..'9'] -> "man"
352 Just _ -> "html"
354main = do
356 rawArgs <- getArgs
357 prg <- getProgName
358 let compatMode = (prg == "hsmarkdown")
360 let (actions, args, errors) = if compatMode
361 then ([], rawArgs, [])
362 else getOpt Permute options rawArgs
364 if (not (null errors))
365 then do
366 name <- getProgName
367 mapM (\e -> hPutStrLn stderr e) errors
368 hPutStr stderr (usageMessage name options)
369 exitWith $ ExitFailure 2
370 else
371 return ()
373 let defaultOpts' = if compatMode
374 then defaultOpts { optReader = "markdown"
375 , optWriter = "html"
376 , optStrict = True }
377 else defaultOpts
379 -- thread option data structure through all supplied option actions
380 opts <- foldl (>>=) (return defaultOpts') actions
382 let Opt { optPreserveTabs = preserveTabs
383 , optTabStop = tabStop
384 , optStandalone = standalone
385 , optReader = readerName
386 , optWriter = writerName
387 , optParseRaw = parseRaw
388 , optCSS = css
389 , optTableOfContents = toc
390 , optIncludeInHeader = includeHeader
391 , optIncludeBeforeBody = includeBefore
392 , optIncludeAfterBody = includeAfter
393 , optCustomHeader = customHeader
394 , optTitlePrefix = titlePrefix
395 , optOutputFile = outputFile
396 , optNumberSections = numberSections
397 , optIncremental = incremental
398 , optSmart = smart
399 , optASCIIMathML = asciiMathML
400 , optDumpArgs = dumpArgs
401 , optIgnoreArgs = ignoreArgs
402 , optStrict = strict
403 , optReferenceLinks = referenceLinks
404 } = opts
406 if dumpArgs
407 then do
408 hPutStrLn stdout outputFile
409 mapM (\arg -> hPutStrLn stdout arg) args
410 exitWith $ ExitSuccess
411 else return ()
413 let sources = if ignoreArgs then [] else args
415 -- assign reader and writer based on options and filenames
416 let readerName' = if null readerName
417 then defaultReaderName sources
418 else readerName
420 let writerName' = if null writerName
421 then defaultWriterName outputFile
422 else writerName
424 reader <- case (lookup readerName' readers) of
425 Just r -> return r
426 Nothing -> error ("Unknown reader: " ++ readerName')
428 (writer, defaultHeader) <- case (lookup writerName' writers) of
429 Just (w,h) -> return (w, h)
430 Nothing -> error ("Unknown writer: " ++ writerName')
432 output <- if (outputFile == "-")
433 then return stdout
434 else openFile outputFile WriteMode
436 environment <- getEnvironment
437 let columns = case lookup "COLUMNS" environment of
438 Just cols -> read cols
439 Nothing -> stateColumns defaultParserState
441 let tabFilter = if preserveTabs then id else (tabsToSpaces tabStop)
442 let removeCRs str = filter (/= '\r') str -- remove DOS-style line endings
443 let startParserState =
444 defaultParserState { stateParseRaw = parseRaw,
445 stateTabStop = tabStop,
446 stateStandalone = standalone && (not strict),
447 stateSmart = smart || writerName' == "latex",
448 stateColumns = columns,
449 stateStrict = strict }
450 let csslink = if (css == "")
451 then ""
452 else "<link rel=\"stylesheet\" href=\"" ++ css ++
453 "\" type=\"text/css\" media=\"all\" />\n"
454 let asciiMathMLLink = if asciiMathML then asciiMathMLScript else ""
455 let header = (if (customHeader == "DEFAULT")
456 then defaultHeader
457 else customHeader) ++
458 csslink ++ asciiMathMLLink ++ includeHeader
459 let writerOptions = WriterOptions { writerStandalone = standalone &&
460 (not strict),
461 writerHeader = header,
462 writerTitlePrefix = titlePrefix,
463 writerTabStop = tabStop,
464 writerTableOfContents = toc &&
465 (not strict) &&
466 writerName/="s5",
467 writerS5 = (writerName=="s5"),
468 writerIgnoreNotes = False,
469 writerIncremental = incremental,
470 writerNumberSections = numberSections,
471 writerIncludeBefore = includeBefore,
472 writerIncludeAfter = includeAfter,
473 writerStrictMarkdown = strict,
474 writerReferenceLinks = referenceLinks }
476 (readSources sources) >>= (hPutStr output . toUTF8 .
477 (writer writerOptions) .
478 (reader startParserState) . tabFilter .
479 removeCRs . fromUTF8 . (joinWithSep "\n")) >>
480 hClose output
482 where
483 readSources [] = mapM readSource ["-"]
484 readSources sources = mapM readSource sources
485 readSource "-" = getContents
486 readSource source = readFile source