git.iko.soy/typeable/pandoc
on 2deea09 help sign in
425 lines 17 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.LaTeX ( writeLaTeX )
41import Text.Pandoc.Readers.LaTeX ( readLaTeX )
42import Text.Pandoc.Writers.RTF ( writeRTF )
43import Text.Pandoc.Writers.Markdown ( writeMarkdown )
44import Text.Pandoc.Writers.DefaultHeaders ( defaultHtmlHeader,
45 defaultRTFHeader, defaultS5Header, defaultLaTeXHeader )
46import Text.Pandoc.Definition
47import Text.Pandoc.Shared
48import Text.Regex ( mkRegex, splitRegex )
49import System ( exitWith, getArgs, getProgName )
50import System.Exit
51import System.Console.GetOpt
52import System.IO
53import Data.Maybe ( fromMaybe )
54import Data.List ( isPrefixOf )
55import Char ( toLower )
56import Control.Monad ( (>>=) )
58version :: String
59version = "0.3"
61copyrightMessage :: String
62copyrightMessage = "\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."
64-- | Association list of formats and readers.
65readers :: [(String, ParserState -> String -> Pandoc)]
66readers = [("native" , readPandoc)
67 ,("markdown" , readMarkdown)
68 ,("rst" , readRST)
69 ,("html" , readHtml)
70 ,("latex" , readLaTeX)
71 ]
73-- | Reader for native Pandoc format.
74readPandoc :: ParserState -> String -> Pandoc
75readPandoc state input = read input
77-- | Association list of formats and pairs of writers and default headers.
78writers :: [ ( String, ( WriterOptions -> Pandoc -> String, String ) ) ]
79writers = [("native" , (writeDoc, ""))
80 ,("html" , (writeHtml, defaultHtmlHeader))
81 ,("s5" , (writeS5, defaultS5Header))
82 ,("latex" , (writeLaTeX, defaultLaTeXHeader))
83 ,("markdown" , (writeMarkdown, ""))
84 ,("rst" , (writeRST, ""))
85 ,("rtf" , (writeRTF, defaultRTFHeader))
86 ]
88-- | Writer for Pandoc native format.
89writeDoc :: WriterOptions -> Pandoc -> String
90writeDoc options = prettyPandoc
92-- | Data structure for command line options.
93data Opt = Opt
94 { optPreserveTabs :: Bool -- ^ If @False@, convert tabs to spaces
95 , optTabStop :: Int -- ^ Number of spaces per tab
96 , optStandalone :: Bool -- ^ If @True@, include header, footer
97 , optReader :: ParserState -> String -> Pandoc -- ^ Read format
98 , optWriter :: WriterOptions -> Pandoc -> String -- ^ Write fmt
99 , optParseRaw :: Bool -- ^ If @True@, parse unconvertable
100 -- HTML and TeX
101 , optCSS :: String -- ^ CSS file to link to
102 , optIncludeInHeader :: String -- ^ File to include in header
103 , optIncludeBeforeBody :: String -- ^ File to include at top of body
104 , optIncludeAfterBody :: String -- ^ File to include at end of body
105 , optCustomHeader :: String -- ^ Custom header to use, or "DEFAULT"
106 , optDefaultHeader :: String -- ^ Default header
107 , optTitlePrefix :: String -- ^ Optional prefix for HTML title
108 , optOutputFile :: String -- ^ Name of output file
109 , optNumberSections :: Bool -- ^ If @True@, number sections in LaTeX
110 , optIncremental :: Bool -- ^ If @True@, incremental lists in S5
111 , optSmart :: Bool -- ^ If @True@, use smart typography
112 , optASCIIMathML :: Bool -- ^ If @True@, use ASCIIMathML in HTML
113 , optShowUsage :: Bool -- ^ If @True@, show usage message
114 , optDebug :: Bool -- ^ If @True@, output debug messages
115 }
117-- | Defaults for command-line options.
118startOpt :: Opt
119startOpt = Opt
120 { optPreserveTabs = False
121 , optTabStop = 4
122 , optStandalone = False
123 , optReader = readMarkdown
124 , optWriter = writeHtml
125 , optParseRaw = False
126 , optCSS = ""
127 , optIncludeInHeader = ""
128 , optIncludeBeforeBody = ""
129 , optIncludeAfterBody = ""
130 , optCustomHeader = "DEFAULT"
131 , optDefaultHeader = defaultHtmlHeader
132 , optTitlePrefix = ""
133 , optOutputFile = "" -- null for stdout
134 , optNumberSections = False
135 , optIncremental = False
136 , optSmart = False
137 , optASCIIMathML = False
138 , optShowUsage = False
139 , optDebug = False
140 }
142-- | A list of functions, each transforming the options data structure in response
143-- to a command-line option.
144allOptions :: [OptDescr (Opt -> IO Opt)]
145allOptions =
146 [ Option "fr" ["from","read"]
147 (ReqArg
148 (\arg opt -> case (lookup (map toLower arg) readers) of
149 Just reader -> return opt { optReader = reader }
150 Nothing -> error ("Unknown reader: " ++ arg) )
151 "FORMAT")
152 ("Source format (" ++
153 (concatMap (\(name, fn) -> " " ++ name) readers) ++ " )")
155 , Option "tw" ["to","write"]
156 (ReqArg
157 (\arg opt -> case (lookup (map toLower arg) writers) of
158 Just (writer, defaultHeader) ->
159 return opt { optWriter = writer,
160 optDefaultHeader = defaultHeader }
161 Nothing -> error ("Unknown writer: " ++ arg) )
162 "FORMAT")
163 ("Output format (" ++ (concatMap (\(name, fn) -> " " ++ name) writers) ++ " )")
165 , Option "s" ["standalone"]
166 (NoArg
167 (\opt -> return opt { optStandalone = True }))
168 "Include needed header and footer on output"
170 , Option "o" ["output"]
171 (ReqArg
172 (\arg opt -> do
173 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 "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 in HTML output"
198 , Option "m" ["asciimathml"]
199 (NoArg
200 (\opt -> return opt { optASCIIMathML = True,
201 optStandalone = True }))
202 "Use ASCIIMathML script in html output"
204 , Option "i" ["incremental"]
205 (NoArg
206 (\opt -> return opt { optIncremental = True }))
207 "Make list items display incrementally in S5"
209 , Option "N" ["number-sections"]
210 (NoArg
211 (\opt -> return opt { optNumberSections = True }))
212 "Number sections in LaTeX"
214 , Option "c" ["css"]
215 (ReqArg
216 (\arg opt -> return opt { optCSS = arg,
217 optStandalone = True })
218 "CSS")
219 "Link to CSS style sheet"
221 , Option "H" ["include-in-header"]
222 (ReqArg
223 (\arg opt -> do
224 text <- readFile arg
225 return opt { optIncludeInHeader = text,
226 optStandalone = True })
227 "FILENAME")
228 "File to include at end of header (implies -s)"
230 , Option "B" ["include-before-body"]
231 (ReqArg
232 (\arg opt -> do
233 text <- readFile arg
234 return opt { optIncludeBeforeBody = text })
235 "FILENAME")
236 "File to include before document body"
238 , Option "A" ["include-after-body"]
239 (ReqArg
240 (\arg opt -> do
241 text <- readFile arg
242 return opt { optIncludeAfterBody = text })
243 "FILENAME")
244 "File to include after document body"
246 , Option "C" ["custom-header"]
247 (ReqArg
248 (\arg opt -> do
249 text <- readFile arg
250 return opt { optCustomHeader = text,
251 optStandalone = True })
252 "FILENAME")
253 "File to use for custom header (implies -s)"
255 , Option "T" ["title-prefix"]
256 (ReqArg
257 (\arg opt -> return opt { optTitlePrefix = arg,
258 optStandalone = True })
259 "STRING")
260 "String to prefix to HTML window title"
262 , Option "D" ["print-default-header"]
263 (ReqArg
264 (\arg opt -> do
265 let header = case (lookup arg writers) of
266 Just (writer, head) -> head
267 Nothing -> error ("Unknown reader: " ++ arg)
268 hPutStr stdout header
269 exitWith ExitSuccess)
270 "FORMAT")
271 "Print default header for FORMAT"
273 , Option "d" ["debug"]
274 (NoArg
275 (\opt -> return opt { optDebug = True }))
276 "Print debug messages to stderr, output to stdout"
278 , Option "v" ["version"]
279 (NoArg
280 (\_ -> do
281 prg <- getProgName
282 hPutStrLn stderr (prg ++ " " ++ version ++
283 copyrightMessage)
284 exitWith $ ExitFailure 2))
285 "Print version"
287 , Option "h" ["help"]
288 (NoArg
289 (\opt -> return opt { optShowUsage = True }))
290 "Show help"
291 ]
293-- parse name of calling program and return default reader and writer descriptions
294parseProgName name =
295 case (splitRegex (mkRegex "2") (map toLower name)) of
296 [from, to] -> (from, to)
297 _ -> ("markdown", "html")
299-- set default options based on reader and writer descriptions; start is starting options
300setDefaultOpts from to start =
301 case ((lookup from readers), (lookup to writers)) of
302 (Just reader, Just (writer, header)) -> start {optReader = reader,
303 optWriter = writer,
304 optDefaultHeader = header}
305 _ -> start
307-- True if single-letter option is in option list
308inOptList :: [Char] -> OptDescr (Opt -> IO Opt) -> Bool
309inOptList list desc =
310 let (Option letters _ _ _) = desc in
311 any (\x -> x `elem` list) letters
313-- Reformat usage message so it doesn't wrap illegibly
314reformatUsageInfo = gsub " *--" " --" .
315 gsub "(-[A-Za-z0-9]) *--" "\\1, --" .
316 gsub " *([^- ])" "\n\t\\1"
318main = do
320 name <- getProgName
321 let (from, to) = parseProgName name
323 let irrelevantOptions = if not ('2' `elem` name)
324 then ""
325 else "frtwD" ++
326 (if (to /= "html" && to /= "s5") then "SmcT" else "") ++
327 (if (to /= "latex") then "N" else "") ++
328 (if (to /= "s5") then "i" else "") ++
329 (if (from /= "html" && from /= "latex") then "R" else "")
331 let options = filter (not . inOptList irrelevantOptions) allOptions
333 let defaultOpts = setDefaultOpts from to startOpt
335 args <- getArgs
336 let (actions, sources, errors) = getOpt Permute options args
338 if (not (null errors))
339 then do
340 mapM (\e -> hPutStrLn stderr e) errors
341 hPutStrLn stderr (reformatUsageInfo $
342 usageInfo (name ++ " [OPTIONS] [FILES]") options)
343 exitWith $ ExitFailure 2
344 else
345 return ()
347 -- thread option data structure through all supplied option actions
348 opts <- foldl (>>=) (return defaultOpts) actions
350 let Opt { optPreserveTabs = preserveTabs
351 , optTabStop = tabStop
352 , optStandalone = standalone
353 , optReader = reader
354 , optWriter = writer
355 , optParseRaw = parseRaw
356 , optCSS = css
357 , optIncludeInHeader = includeHeader
358 , optIncludeBeforeBody = includeBefore
359 , optIncludeAfterBody = includeAfter
360 , optCustomHeader = customHeader
361 , optDefaultHeader = defaultHeader
362 , optTitlePrefix = titlePrefix
363 , optOutputFile = outputFile
364 , optNumberSections = numberSections
365 , optIncremental = incremental
366 , optSmart = smart
367 , optASCIIMathML = asciiMathML
368 , optShowUsage = showUsage
369 , optDebug = debug
370 } = opts
372 if showUsage
373 then do
374 hPutStr stderr (reformatUsageInfo $ usageInfo (name ++ " [OPTIONS] [FILES]") options)
375 exitWith $ ExitFailure 2
376 else return ()
378 output <- if ((null outputFile) || debug)
379 then return stdout
380 else openFile outputFile WriteMode
382 if debug
383 then do
384 hPutStrLn stderr ("OUTPUT=" ++ outputFile)
385 hPutStr stderr $ concatMap (\s -> "INPUT=" ++ s ++ "\n") sources
386 else return ()
388 let writingS5 = (defaultHeader == defaultS5Header)
389 let tabFilter = if preserveTabs then id else (tabsToSpaces tabStop)
390 let addBlank str = str ++ "\n\n"
391 let removeCRs str = filter (/= '\r') str -- remove DOS-style line endings
392 let filter = tabFilter . addBlank . removeCRs
393 let startParserState = defaultParserState { stateParseRaw = parseRaw,
394 stateTabStop = tabStop,
395 stateStandalone = standalone }
396 let csslink = if (css == "")
397 then ""
398 else "<link rel=\"stylesheet\" href=\"" ++ css ++
399 "\" type=\"text/css\" media=\"all\" />\n"
400 let asciiMathMLLink = if asciiMathML then asciiMathMLScript else ""
401 let header = (if (customHeader == "DEFAULT")
402 then defaultHeader
403 else customHeader) ++
404 csslink ++ asciiMathMLLink ++ includeHeader
405 let writerOptions = WriterOptions { writerStandalone = standalone,
406 writerHeader = header,
407 writerTitlePrefix = titlePrefix,
408 writerSmart = smart,
409 writerTabStop = tabStop,
410 writerS5 = writingS5,
411 writerIncremental = incremental,
412 writerNumberSections = numberSections,
413 writerIncludeBefore = includeBefore,
414 writerIncludeAfter = includeAfter }
416 (readSources sources) >>= (hPutStr output . encodeUTF8 .
417 (writer writerOptions) .
418 (reader startParserState) . filter .
419 decodeUTF8 . (joinWithSep "\n")) >> hClose output
421 where
422 readSources [] = mapM readSource ["-"]
423 readSources sources = mapM readSource sources
424 readSource "-" = getContents
425 readSource source = readFile source