git.iko.soy/typeable/pandoc
on ca6cb23 help sign in
476 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\nWeb: http://sophos.berkeley.edu/macfarlane/pandoc\nThis is free software; see the source for copying conditions. There is no\nwarranty, not even for merchantability or fitness for a particular purpose."
66-- | Association list of formats and readers.
67readers :: [(String, ParserState -> String -> Pandoc)]
68readers = [("native" , readPandoc)
69 ,("markdown" , readMarkdown)
70 ,("rst" , readRST)
71 ,("html" , readHtml)
72 ,("latex" , readLaTeX)
73 ]
75-- | Reader for native Pandoc format.
76readPandoc :: ParserState -> String -> Pandoc
77readPandoc state input = read input
79-- | Association list of formats and pairs of writers and default headers.
80writers :: [ ( String, ( WriterOptions -> Pandoc -> String, String ) ) ]
81writers = [("native" , (writeDoc, ""))
82 ,("html" , (writeHtml, defaultHtmlHeader))
83 ,("s5" , (writeS5, defaultS5Header))
84 ,("docbook" , (writeDocbook, defaultDocbookHeader))
85 ,("latex" , (writeLaTeX, defaultLaTeXHeader))
86 ,("markdown" , (writeMarkdown, ""))
87 ,("rst" , (writeRST, ""))
88 ,("rtf" , (writeRTF, defaultRTFHeader))
89 ]
91-- | Writer for Pandoc native format.
92writeDoc :: WriterOptions -> Pandoc -> String
93writeDoc options = prettyPandoc
95-- | Data structure for command line options.
96data Opt = Opt
97 { optPreserveTabs :: Bool -- ^ Convert tabs to spaces
98 , optTabStop :: Int -- ^ Number of spaces per tab
99 , optStandalone :: Bool -- ^ Include header, footer
100 , optReader :: String -- ^ Reader format
101 , optWriter :: String -- ^ Writer format
102 , optParseRaw :: Bool -- ^ Parse unconvertable HTML and TeX
103 , optCSS :: String -- ^ CSS file to link to
104 , optIncludeInHeader :: String -- ^ File to include in header
105 , optIncludeBeforeBody :: String -- ^ File to include at top of body
106 , optIncludeAfterBody :: String -- ^ File to include at end of body
107 , optCustomHeader :: String -- ^ Custom header to use, or "DEFAULT"
108 , optTitlePrefix :: String -- ^ Optional prefix for HTML title
109 , optOutputFile :: String -- ^ Name of output file
110 , optNumberSections :: Bool -- ^ Number sections in LaTeX
111 , optIncremental :: Bool -- ^ Use incremental lists in S5
112 , optSmart :: Bool -- ^ Use smart typography
113 , optASCIIMathML :: Bool -- ^ Use ASCIIMathML in HTML
114 , optDumpArgs :: Bool -- ^ Output command-line arguments
115 , optIgnoreArgs :: Bool -- ^ Ignore command-line arguments
116 , optStrict :: Bool -- ^ Use strict markdown syntax
117 }
119-- | Defaults for command-line options.
120defaultOpts :: Opt
121defaultOpts = Opt
122 { optPreserveTabs = False
123 , optTabStop = 4
124 , optStandalone = False
125 , optReader = "" -- null for default reader
126 , optWriter = "" -- null for default writer
127 , optParseRaw = False
128 , optCSS = ""
129 , optIncludeInHeader = ""
130 , optIncludeBeforeBody = ""
131 , optIncludeAfterBody = ""
132 , optCustomHeader = "DEFAULT"
133 , optTitlePrefix = ""
134 , optOutputFile = "-" -- "-" means stdout
135 , optNumberSections = False
136 , optIncremental = False
137 , optSmart = False
138 , optASCIIMathML = False
139 , optDumpArgs = False
140 , optIgnoreArgs = False
141 , optStrict = False
142 }
144-- | A list of functions, each transforming the options data structure
145-- in response to a command-line option.
146options :: [OptDescr (Opt -> IO Opt)]
147options =
148 [ Option "fr" ["from","read"]
149 (ReqArg
150 (\arg opt -> return opt { optReader = map toLower arg })
151 "FORMAT")
152 "" -- ("(" ++ (joinWithSep ", " (map fst readers)) ++ ")")
154 , Option "tw" ["to","write"]
155 (ReqArg
156 (\arg opt -> return opt { optWriter = map toLower arg })
157 "FORMAT")
158 "" -- ("(" ++ (joinWithSep ", " (map fst writers)) ++ ")")
160 , Option "s" ["standalone"]
161 (NoArg
162 (\opt -> return opt { optStandalone = True }))
163 "" -- "Include needed header and footer on output"
165 , Option "o" ["output"]
166 (ReqArg
167 (\arg opt -> return opt { optOutputFile = arg })
168 "FILENAME")
169 "" -- "Name of output file"
171 , Option "p" ["preserve-tabs"]
172 (NoArg
173 (\opt -> return opt { optPreserveTabs = True }))
174 "" -- "Preserve tabs instead of converting to spaces"
176 , Option "" ["tab-stop"]
177 (ReqArg
178 (\arg opt -> return opt { optTabStop = (read arg) } )
179 "TABSTOP")
180 "" -- "Tab stop (default 4)"
182 , Option "" ["strict"]
183 (NoArg
184 (\opt -> return opt { optStrict = True } ))
185 "" -- "Use strict markdown syntax with no extensions"
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 "c" ["css"]
214 (ReqArg
215 (\arg opt -> return opt { optCSS = arg,
216 optStandalone = True })
217 "CSS")
218 "" -- "Link to CSS style sheet"
220 , Option "H" ["include-in-header"]
221 (ReqArg
222 (\arg opt -> do
223 text <- readFile arg
224 return opt { optIncludeInHeader = text,
225 optStandalone = True })
226 "FILENAME")
227 "" -- "File to include at end of header (implies -s)"
229 , Option "B" ["include-before-body"]
230 (ReqArg
231 (\arg opt -> do
232 text <- readFile arg
233 return opt { optIncludeBeforeBody = text })
234 "FILENAME")
235 "" -- "File to include before document body"
237 , Option "A" ["include-after-body"]
238 (ReqArg
239 (\arg opt -> do
240 text <- readFile arg
241 return opt { optIncludeAfterBody = text })
242 "FILENAME")
243 "" -- "File to include after document body"
245 , Option "C" ["custom-header"]
246 (ReqArg
247 (\arg opt -> do
248 text <- readFile arg
249 return opt { optCustomHeader = text,
250 optStandalone = True })
251 "FILENAME")
252 "" -- "File to use for custom header (implies -s)"
254 , Option "T" ["title-prefix"]
255 (ReqArg
256 (\arg opt -> return opt { optTitlePrefix = arg,
257 optStandalone = True })
258 "STRING")
259 "" -- "String to prefix to HTML window title"
261 , Option "D" ["print-default-header"]
262 (ReqArg
263 (\arg opt -> do
264 let header = case (lookup arg writers) of
265 Just (writer, head) -> head
266 Nothing -> error ("Unknown reader: " ++ arg)
267 hPutStr stdout header
268 exitWith ExitSuccess)
269 "FORMAT")
270 "" -- "Print default header for FORMAT"
272 , Option "" ["dump-args"]
273 (NoArg
274 (\opt -> return opt { optDumpArgs = True }))
275 "" -- "Print output filename and arguments to stdout."
277 , Option "" ["ignore-args"]
278 (NoArg
279 (\opt -> return opt { optIgnoreArgs = True }))
280 "" -- "Ignore command-line arguments."
282 , Option "v" ["version"]
283 (NoArg
284 (\_ -> do
285 prg <- getProgName
286 hPutStrLn stderr (prg ++ " " ++ version ++
287 copyrightMessage)
288 exitWith $ ExitFailure 4))
289 "" -- "Print version"
291 , Option "h" ["help"]
292 (NoArg
293 (\_ -> do
294 prg <- getProgName
295 hPutStr stderr (usageMessage prg options)
296 exitWith $ ExitFailure 2))
297 "" -- "Show help"
298 ]
300-- Returns usage message
301usageMessage :: String -> [OptDescr (Opt -> IO Opt)] -> String
302usageMessage programName options = usageInfo
303 (programName ++ " [OPTIONS] [FILES]" ++ "\nInput formats: " ++
304 joinWithSep ", " (map fst readers) ++ "\nOutput formats: " ++
305 joinWithSep ", " (map fst writers) ++ "\nOptions:")
306 options
308-- Determine default reader based on source file extensions
309defaultReaderName :: [String] -> String
310defaultReaderName [] = "markdown"
311defaultReaderName (x:xs) =
312 let x' = map toLower x in
313 case (matchRegex (mkRegex ".*\\.(.*)") x') of
314 Nothing -> defaultReaderName xs -- no extension
315 Just ["xhtml"] -> "html"
316 Just ["html"] -> "html"
317 Just ["htm"] -> "html"
318 Just ["tex"] -> "latex"
319 Just ["latex"] -> "latex"
320 Just ["ltx"] -> "latex"
321 Just ["rst"] -> "rst"
322 Just ["native"] -> "native"
323 Just _ -> "markdown"
325-- Determine default writer based on output file extension
326defaultWriterName :: String -> String
327defaultWriterName "-" = "html" -- no output file
328defaultWriterName x =
329 let x' = map toLower x in
330 case (matchRegex (mkRegex ".*\\.(.*)") x') of
331 Nothing -> "markdown" -- no extension
332 Just [""] -> "markdown" -- empty extension
333 Just ["tex"] -> "latex"
334 Just ["latex"] -> "latex"
335 Just ["ltx"] -> "latex"
336 Just ["rtf"] -> "rtf"
337 Just ["rst"] -> "rst"
338 Just ["s5"] -> "s5"
339 Just ["native"] -> "native"
340 Just ["txt"] -> "markdown"
341 Just ["text"] -> "markdown"
342 Just ["md"] -> "markdown"
343 Just ["markdown"] -> "markdown"
344 Just ["db"] -> "docbook"
345 Just ["xml"] -> "docbook"
346 Just ["sgml"] -> "docbook"
347 Just _ -> "html"
349main = do
351 rawArgs <- getArgs
352 prg <- getProgName
353 let compatMode = (prg == "hsmarkdown")
355 let (actions, args, errors) = if compatMode
356 then ([], rawArgs, [])
357 else getOpt Permute options rawArgs
359 if (not (null errors))
360 then do
361 name <- getProgName
362 mapM (\e -> hPutStrLn stderr e) errors
363 hPutStr stderr (usageMessage name options)
364 exitWith $ ExitFailure 2
365 else
366 return ()
368 let defaultOpts' = if compatMode
369 then defaultOpts { optReader = "markdown"
370 , optWriter = "html"
371 , optStrict = True }
372 else defaultOpts
374 -- thread option data structure through all supplied option actions
375 opts <- foldl (>>=) (return defaultOpts') actions
377 let Opt { optPreserveTabs = preserveTabs
378 , optTabStop = tabStop
379 , optStandalone = standalone
380 , optReader = readerName
381 , optWriter = writerName
382 , optParseRaw = parseRaw
383 , optCSS = css
384 , optIncludeInHeader = includeHeader
385 , optIncludeBeforeBody = includeBefore
386 , optIncludeAfterBody = includeAfter
387 , optCustomHeader = customHeader
388 , optTitlePrefix = titlePrefix
389 , optOutputFile = outputFile
390 , optNumberSections = numberSections
391 , optIncremental = incremental
392 , optSmart = smart
393 , optASCIIMathML = asciiMathML
394 , optDumpArgs = dumpArgs
395 , optIgnoreArgs = ignoreArgs
396 , optStrict = strict
397 } = opts
399 if dumpArgs
400 then do
401 hPutStrLn stdout outputFile
402 mapM (\arg -> hPutStrLn stdout arg) args
403 exitWith $ ExitSuccess
404 else return ()
406 let sources = if ignoreArgs then [] else args
408 -- assign reader and writer based on options and filenames
409 let readerName' = if null readerName
410 then defaultReaderName sources
411 else readerName
413 let writerName' = if null writerName
414 then defaultWriterName outputFile
415 else writerName
417 reader <- case (lookup readerName' readers) of
418 Just r -> return r
419 Nothing -> error ("Unknown reader: " ++ readerName')
421 (writer, defaultHeader) <- case (lookup writerName' writers) of
422 Just (w,h) -> return (w, h)
423 Nothing -> error ("Unknown writer: " ++ writerName')
425 output <- if (outputFile == "-")
426 then return stdout
427 else openFile outputFile WriteMode
429 environment <- getEnvironment
430 let columns = case lookup "COLUMNS" environment of
431 Just cols -> read cols
432 Nothing -> stateColumns defaultParserState
434 let tabFilter = if preserveTabs then id else (tabsToSpaces tabStop)
435 let addBlank str = str ++ "\n\n"
436 let removeCRs str = filter (/= '\r') str -- remove DOS-style line endings
437 let filter = tabFilter . addBlank . removeCRs
438 let startParserState =
439 defaultParserState { stateParseRaw = parseRaw,
440 stateTabStop = tabStop,
441 stateStandalone = standalone && (not strict),
442 stateSmart = smart || writerName' == "latex",
443 stateColumns = columns,
444 stateStrict = strict }
445 let csslink = if (css == "")
446 then ""
447 else "<link rel=\"stylesheet\" href=\"" ++ css ++
448 "\" type=\"text/css\" media=\"all\" />\n"
449 let asciiMathMLLink = if asciiMathML then asciiMathMLScript else ""
450 let header = (if (customHeader == "DEFAULT")
451 then defaultHeader
452 else customHeader) ++
453 csslink ++ asciiMathMLLink ++ includeHeader
454 let writerOptions = WriterOptions { writerStandalone = standalone &&
455 (not strict),
456 writerHeader = header,
457 writerTitlePrefix = titlePrefix,
458 writerTabStop = tabStop,
459 writerNotes = [],
460 writerS5 = (writerName=="s5"),
461 writerIncremental = incremental,
462 writerNumberSections = numberSections,
463 writerIncludeBefore = includeBefore,
464 writerIncludeAfter = includeAfter,
465 writerStrictMarkdown = strict }
467 (readSources sources) >>= (hPutStr output . encodeUTF8 .
468 (writer writerOptions) .
469 (reader startParserState) . filter .
470 decodeUTF8 . (joinWithSep "\n")) >> hClose output
472 where
473 readSources [] = mapM readSource ["-"]
474 readSources sources = mapM readSource sources
475 readSource "-" = getContents
476 readSource source = readFile source