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