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