git.iko.soy/typeable/pandoc
on a6da87f help sign in
494 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.Shared ( joinWithSep, tabsToSpaces )
35import Text.Regex ( mkRegex, matchRegex )
36import System.Environment ( getArgs, getProgName, getEnvironment )
37import System.Exit ( exitWith, ExitCode (..) )
38import System.Console.GetOpt
39import System.IO
40import Data.Maybe ( fromMaybe )
41import Data.List ( isPrefixOf )
42import Data.Char ( toLower )
43import Control.Monad ( (>>=) )
45version :: String
46version = "0.42"
48copyrightMessage :: String
49copyrightMessage = "\nCopyright (C) 2006-7 John MacFarlane\n\
50 \Web: http://sophos.berkeley.edu/macfarlane/pandoc\n\
51 \This is free software; see the source for copying conditions. There is no\n\
52 \warranty, not even for merchantability or fitness for a particular purpose."
54-- | Association list of formats and readers.
55readers :: [(String, ParserState -> String -> Pandoc)]
56readers = [("native" , readPandoc)
57 ,("markdown" , readMarkdown)
58 ,("rst" , readRST)
59 ,("html" , readHtml)
60 ,("latex" , readLaTeX)
61 ]
63-- | Reader for native Pandoc format.
64readPandoc :: ParserState -> String -> Pandoc
65readPandoc state input = read input
67-- | Association list of formats and pairs of writers and default headers.
68writers :: [ ( String, ( WriterOptions -> Pandoc -> String, String ) ) ]
69writers = [("native" , (writeDoc, ""))
70 ,("html" , (writeHtmlString, ""))
71 ,("s5" , (writeS5String, defaultS5Header))
72 ,("docbook" , (writeDocbook, defaultDocbookHeader))
73 ,("latex" , (writeLaTeX, defaultLaTeXHeader))
74 ,("context" , (writeConTeXt, defaultConTeXtHeader))
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 , optUseASCIIMathML :: Bool -- ^ Use ASCIIMathML
105 , optASCIIMathMLURL :: Maybe String -- ^ URL to ASCIIMathML.js
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 , optUseASCIIMathML = False
133 , optASCIIMathMLURL = Nothing
134 , optDumpArgs = False
135 , optIgnoreArgs = False
136 , optStrict = False
137 , optReferenceLinks = False
138 }
140-- | A list of functions, each transforming the options data structure
141-- in response to a command-line option.
142options :: [OptDescr (Opt -> IO Opt)]
143options =
144 [ Option "fr" ["from","read"]
145 (ReqArg
146 (\arg opt -> return opt { optReader = map toLower arg })
147 "FORMAT")
148 "" -- ("(" ++ (joinWithSep ", " $ map fst readers) ++ ")")
150 , Option "tw" ["to","write"]
151 (ReqArg
152 (\arg opt -> return opt { optWriter = map toLower arg })
153 "FORMAT")
154 "" -- ("(" ++ (joinWithSep ", " $ map fst writers) ++ ")")
156 , Option "s" ["standalone"]
157 (NoArg
158 (\opt -> return opt { optStandalone = True }))
159 "" -- "Include needed header and footer on output"
161 , Option "o" ["output"]
162 (ReqArg
163 (\arg opt -> return opt { optOutputFile = arg })
164 "FILENAME")
165 "" -- "Name of output file"
167 , Option "p" ["preserve-tabs"]
168 (NoArg
169 (\opt -> return opt { optPreserveTabs = True }))
170 "" -- "Preserve tabs instead of converting to spaces"
172 , Option "" ["tab-stop"]
173 (ReqArg
174 (\arg opt -> return opt { optTabStop = (read arg) } )
175 "TABSTOP")
176 "" -- "Tab stop (default 4)"
178 , Option "" ["strict"]
179 (NoArg
180 (\opt -> return opt { optStrict = True } ))
181 "" -- "Disable markdown syntax extensions"
183 , Option "" ["reference-links"]
184 (NoArg
185 (\opt -> return opt { optReferenceLinks = True } ))
186 "" -- "Use reference links in parsing HTML"
188 , Option "R" ["parse-raw"]
189 (NoArg
190 (\opt -> return opt { optParseRaw = True }))
191 "" -- "Parse untranslatable HTML codes and LaTeX environments as raw"
193 , Option "S" ["smart"]
194 (NoArg
195 (\opt -> return opt { optSmart = True }))
196 "" -- "Use smart quotes, dashes, and ellipses"
198 , Option "m" ["asciimathml"]
199 (OptArg
200 (\arg opt -> return opt { optUseASCIIMathML = True,
201 optASCIIMathMLURL = arg,
202 optStandalone = True })
203 "URL")
204 "" -- "Use ASCIIMathML script in html output"
206 , Option "i" ["incremental"]
207 (NoArg
208 (\opt -> return opt { optIncremental = True }))
209 "" -- "Make list items display incrementally in S5"
211 , Option "N" ["number-sections"]
212 (NoArg
213 (\opt -> return opt { optNumberSections = True }))
214 "" -- "Number sections in LaTeX"
216 , Option "" ["toc", "table-of-contents"]
217 (NoArg
218 (\opt -> return opt { optTableOfContents = True }))
219 "" -- "Include table of contents"
221 , Option "c" ["css"]
222 (ReqArg
223 (\arg opt -> return opt { optCSS = arg,
224 optStandalone = True })
225 "CSS")
226 "" -- "Link to CSS style sheet"
228 , Option "H" ["include-in-header"]
229 (ReqArg
230 (\arg opt -> do
231 text <- readFile arg
232 return opt { optIncludeInHeader = fromUTF8 text,
233 optStandalone = True })
234 "FILENAME")
235 "" -- "File to include at end of header (implies -s)"
237 , Option "B" ["include-before-body"]
238 (ReqArg
239 (\arg opt -> do
240 text <- readFile arg
241 return opt { optIncludeBeforeBody = fromUTF8 text })
242 "FILENAME")
243 "" -- "File to include before document body"
245 , Option "A" ["include-after-body"]
246 (ReqArg
247 (\arg opt -> do
248 text <- readFile arg
249 return opt { optIncludeAfterBody = fromUTF8 text })
250 "FILENAME")
251 "" -- "File to include after document body"
253 , Option "C" ["custom-header"]
254 (ReqArg
255 (\arg opt -> do
256 text <- readFile arg
257 return opt { optCustomHeader = fromUTF8 text,
258 optStandalone = True })
259 "FILENAME")
260 "" -- "File to use for custom header (implies -s)"
262 , Option "T" ["title-prefix"]
263 (ReqArg
264 (\arg opt -> return opt { optTitlePrefix = arg,
265 optStandalone = True })
266 "STRING")
267 "" -- "String to prefix to HTML window title"
269 , Option "D" ["print-default-header"]
270 (ReqArg
271 (\arg opt -> do
272 let header = case (lookup arg writers) of
273 Just (writer, head) -> head
274 Nothing -> error ("Unknown reader: " ++ arg)
275 hPutStr stdout header
276 exitWith ExitSuccess)
277 "FORMAT")
278 "" -- "Print default header for FORMAT"
280 , Option "" ["dump-args"]
281 (NoArg
282 (\opt -> return opt { optDumpArgs = True }))
283 "" -- "Print output filename and arguments to stdout."
285 , Option "" ["ignore-args"]
286 (NoArg
287 (\opt -> return opt { optIgnoreArgs = True }))
288 "" -- "Ignore command-line arguments."
290 , Option "v" ["version"]
291 (NoArg
292 (\_ -> do
293 prg <- getProgName
294 hPutStrLn stderr (prg ++ " " ++ version ++
295 copyrightMessage)
296 exitWith $ ExitFailure 4))
297 "" -- "Print version"
299 , Option "h" ["help"]
300 (NoArg
301 (\_ -> do
302 prg <- getProgName
303 hPutStr stderr (usageMessage prg options)
304 exitWith $ ExitFailure 2))
305 "" -- "Show help"
306 ]
308-- Returns usage message
309usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
310usageMessage programName options = usageInfo
311 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
312 (joinWithSep ", " $ map fst readers) ++ "\nOutput formats: " ++
313 (joinWithSep ", " $ map fst writers) ++ "\nOptions:")
314 options
316-- Determine default reader based on source file extensions
317defaultReaderName :: [String] -> String
318defaultReaderName [] = "markdown"
319defaultReaderName (x:xs) =
320 let x' = map toLower x in
321 case (matchRegex (mkRegex ".*\\.(.*)") x') of
322 Nothing -> defaultReaderName xs -- no extension
323 Just ["xhtml"] -> "html"
324 Just ["html"] -> "html"
325 Just ["htm"] -> "html"
326 Just ["tex"] -> "latex"
327 Just ["latex"] -> "latex"
328 Just ["ltx"] -> "latex"
329 Just ["rst"] -> "rst"
330 Just ["native"] -> "native"
331 Just _ -> "markdown"
333-- Determine default writer based on output file extension
334defaultWriterName :: String -> String
335defaultWriterName "-" = "html" -- no output file
336defaultWriterName x =
337 let x' = map toLower x in
338 case (matchRegex (mkRegex ".*\\.(.*)") x') of
339 Nothing -> "markdown" -- no extension
340 Just [""] -> "markdown" -- empty extension
341 Just ["tex"] -> "latex"
342 Just ["latex"] -> "latex"
343 Just ["ltx"] -> "latex"
344 Just ["context"] -> "context"
345 Just ["ctx"] -> "context"
346 Just ["rtf"] -> "rtf"
347 Just ["rst"] -> "rst"
348 Just ["s5"] -> "s5"
349 Just ["native"] -> "native"
350 Just ["txt"] -> "markdown"
351 Just ["text"] -> "markdown"
352 Just ["md"] -> "markdown"
353 Just ["markdown"] -> "markdown"
354 Just ["db"] -> "docbook"
355 Just ["xml"] -> "docbook"
356 Just ["sgml"] -> "docbook"
357 Just [[x]] | x `elem` ['1'..'9'] -> "man"
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 , optTableOfContents = toc
396 , optIncludeInHeader = includeHeader
397 , optIncludeBeforeBody = includeBefore
398 , optIncludeAfterBody = includeAfter
399 , optCustomHeader = customHeader
400 , optTitlePrefix = titlePrefix
401 , optOutputFile = outputFile
402 , optNumberSections = numberSections
403 , optIncremental = incremental
404 , optSmart = smart
405 , optUseASCIIMathML = useAsciiMathML
406 , optASCIIMathMLURL = asciiMathMLURL
407 , optDumpArgs = dumpArgs
408 , optIgnoreArgs = ignoreArgs
409 , optStrict = strict
410 , optReferenceLinks = referenceLinks
411 } = opts
413 if dumpArgs
414 then do
415 hPutStrLn stdout outputFile
416 mapM (\arg -> hPutStrLn stdout arg) args
417 exitWith $ ExitSuccess
418 else return ()
420 let sources = if ignoreArgs then [] else args
422 -- assign reader and writer based on options and filenames
423 let readerName' = if null readerName
424 then defaultReaderName sources
425 else readerName
427 let writerName' = if null writerName
428 then defaultWriterName outputFile
429 else writerName
431 reader <- case (lookup readerName' readers) of
432 Just r -> return r
433 Nothing -> error ("Unknown reader: " ++ readerName')
435 (writer, defaultHeader) <- case (lookup writerName' writers) of
436 Just (w,h) -> return (w, h)
437 Nothing -> error ("Unknown writer: " ++ writerName')
439 output <- if (outputFile == "-")
440 then return stdout
441 else openFile outputFile WriteMode
443 environment <- getEnvironment
444 let columns = case lookup "COLUMNS" environment of
445 Just cols -> read cols
446 Nothing -> stateColumns defaultParserState
448 let tabFilter = if preserveTabs then id else (tabsToSpaces tabStop)
449 let removeCRs str = filter (/= '\r') str -- remove DOS-style line endings
450 let startParserState =
451 defaultParserState { stateParseRaw = parseRaw,
452 stateTabStop = tabStop,
453 stateStandalone = standalone && (not strict),
454 stateSmart = smart || writerName' `elem`
455 ["latex", "context"],
456 stateColumns = columns,
457 stateStrict = strict }
458 let csslink = if (css == "")
459 then ""
460 else "<link rel=\"stylesheet\" href=\"" ++ css ++
461 "\" type=\"text/css\" media=\"all\" />\n"
462 let header = (if (customHeader == "DEFAULT")
463 then defaultHeader
464 else customHeader) ++ csslink ++ includeHeader
465 let writerOptions = WriterOptions { writerStandalone = standalone &&
466 (not strict),
467 writerHeader = header,
468 writerTitlePrefix = titlePrefix,
469 writerTabStop = tabStop,
470 writerTableOfContents = toc &&
471 (not strict) &&
472 writerName/="s5",
473 writerUseASCIIMathML = useAsciiMathML,
474 writerASCIIMathMLURL = asciiMathMLURL,
475 writerS5 = (writerName=="s5"),
476 writerIgnoreNotes = False,
477 writerIncremental = incremental,
478 writerNumberSections = numberSections,
479 writerIncludeBefore = includeBefore,
480 writerIncludeAfter = includeAfter,
481 writerStrictMarkdown = strict,
482 writerReferenceLinks = referenceLinks }
484 (readSources sources) >>= (hPutStrLn output . toUTF8 .
485 (writer writerOptions) .
486 (reader startParserState) . tabFilter .
487 removeCRs . fromUTF8 . (joinWithSep "\n")) >>
488 hClose output
490 where
491 readSources [] = mapM readSource ["-"]
492 readSources sources = mapM readSource sources
493 readSource "-" = getContents
494 readSource source = readFile source