summaryrefslogtreecommitdiff
path: root/src/tools/GFDoc.hs
blob: fcd5ae4051daab055f8003379579c66a45e3e93b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
----------------------------------------------------------------------
-- |
-- Maintainer  : AR
-- Stability   : (stable)
-- Portability : (portable)
--
-- > CVS $Date: 2005/04/16 05:40:50 $ 
-- > CVS $Author: peb $
-- > CVS $Revision: 1.7 $
--
-- produce a HTML document from a list of GF grammar files. AR 6\/10\/2002
--
-- Added @--!@ (NewPage) and @--*@ (Item) 21\/11\/2003
-----------------------------------------------------------------------------

module Main (main) where


import Data.Char
import Data.List
import System.Cmd
import System.Directory
import System.Environment
import System.Locale
import System.Time

-- to read files and write a file

main :: IO ()
main = do
  xx <- getArgs
  let 
   (typ,format,names) = case xx of
    "-latex"  : xs -> (0,doc2latex,xs)
    "-htmls"  : xs -> (2,doc2html,xs)
    "-txt"    : xs -> (3,doc2txt,xs)
    "-txt2"   : xs -> (3,doc2txt2,xs)
    "-txthtml": xs -> (4,doc2txt,xs)
    xs            -> (1,doc2html,xs)
  if null xx
     then do
       putStrLn welcome
       putStrLn help
     else flip mapM_ names (\name -> do  
       ss <- readFile name
       time <- modTime name
       let outfile = fileFormat typ name
       writeFile outfile $ format $ pDoc time ss)
  case typ of
     2 -> 
       mapM_ (\name -> system $ "htmls " ++ (fileFormat typ name)) names
     4 ->
       mapM_ (\name -> 
               system $ "txt2tags -thtml --toc " ++ (fileFormat typ name)) names
     _ -> return ()
  return ()

modTime :: FilePath -> IO ModTime
modTime name = 
    do
    t <- getModificationTime name
    ct <- toCalendarTime t
    let timeFmt = "%Y-%m-%d %H:%M:%S %Z"
    return $ formatCalendarTime defaultTimeLocale timeFmt ct

welcome = unlines [
  "",
  "gfdoc - a rudimentary GF document generator.",
  "(c) Aarne Ranta (aarne@cs.chalmers.se) 2002 under GNU GPL."
  ]

help = unlines $ [
  "",
  "Usage: gfdoc (-latex|-htmls|-txt|-txthtml) <file>+",
  "",
  "The program operates with lines in GF code, treating them into LaTeX",
  "(flag -latex), to a set of HTML documents (flag -htmls), to a txt2tags file",
  "(flag -txt), to HTML via txt (flag -txthtml), or to one",
  "HTML file (by default). The output is written in a file",
  "whose name is formed from the input file name by replacing its suffix",
  "with html or tex; in case of set of HTML files, the names are prefixed",
  "by 01-, 02-, etc, and each file has navigation links.",
  "",
  "The translation is line by line",
  "depending as follows on how the line begins",
  "",
  " --[Int]    heading of level Int",  
  " --         new paragraph",
  " --!        new page (in HTML, recognized by the htmls program)",
  " --.        end of document",
  " --*[Text]  Text paragraph starting with a bullet",
  " --[Text]   Text belongs to text paragraph",
  " [Text]     Text belongs to code paragraph",
  " --%        (in the end of a line): ignore this line",
  "",
  "Within a text paragraph, text enclosed between certain characters",
  "is treated specially:",
  "",
  " *[Text]*   emphasized (boldface)",
  " \"[Text]\"   example string (italics)",
  " $[Text]$   example code (courier)",
  "",
  "For other formatting and links, we recommend the txt2tags format."
  ]

fileFormat typ x = body ++ suff where
  body = reverse $ dropWhile (/='.') $ reverse x
  suff = case typ of
    0 -> "tex"
    _ | typ < 3 -> "html"
    _ -> "txt"

-- the document datatype

data Doc = Doc Title ModTime [Paragraph]

type ModTime = String

type Title = [TextItem]

data Paragraph = 
   Text [TextItem]         -- text line starting with --
 | List [[TextItem]]       -- 
 | Code String             -- other text line
 | Item [TextItem]         -- bulleted item: line prefixed by --*
 | New                     -- new paragraph: line consisting of --
 | NewPage                 -- new parage: line consisting of --!
 | Heading Int [TextItem]  -- text line starting with --n where n = 1,2,3,4

data TextItem =
   Str String
 | Emp String  -- emphasized,     *...*
 | Lit String  -- string literal, "..."
 | Inl String  -- inlined code,   '...'


-- parse document

pDoc :: ModTime -> String -> Doc
pDoc time s = case dropWhile emptyOrPragma (lines s) of
  ('-':'-':'1':title) : paras -> Doc (pItems title) time (map pPara (grp paras))
  paras -> Doc [] time (map pPara (grp paras))
 where
   grp ss = case ss of
     s : rest | ignore s      -> grp rest
              | isEnd s       -> []
              | begComment s  -> let (s1,s2) = getComment (drop 2 s : rest) 
                                 in map ("-- " ++) s1 ++ grp s2 
              | isComment s   -> s : grp rest
              | all isSpace s -> grp rest
     [] -> []
     _ -> unlines code : grp rest where (code,rest) = span (not . isComment) ss 
   pPara s = case s of
     '-':'-':d:text | isDigit d -> Heading (read [d]) (pItems text)
     '-':'-':'!':[]             -> NewPage
     '-':'-':[]                 -> New
     '-':'-':'*':text           -> Item (pItems (dropWhile isSpace text))
     '-':'-':text               -> Text (pItems (dropWhile isSpace text))
     _                          -> Code s
   pItems s = case s of
     '*'  : cs -> get 1 Emp (=='*')  cs
     '"'  : cs -> get 1 Lit (=='"')  cs
     '$'  : cs -> get 1 Inl (=='$') cs
     []        -> []
     _         -> get 0 Str (flip elem "*\"$") s

   get _ _   _     [] = []
   get k con isEnd cs = con beg : pItems (drop k rest) 
                    where (beg,rest) =  span (not . isEnd) cs
 
   isEnd s = case s of
     '-':'-':'.':_ -> True
     _ -> False

   emptyOrPragma s = all isSpace s || "--#" `isPrefixOf` s

ignore s = case reverse s of
     '%':'-':'-':_ -> True
     _ -> False

-- render in html

doc2html :: Doc -> String
doc2html (Doc title time paras) = unlines $
  tagXML "html" $
    tagXML "body" $
      unwords (tagXML "i" ["Produced by " ++ welcome]) :
      mkTagXML "p" :
      concat (tagXML "h1" [concat (map item2html title)]) :
      empty :
      map para2html paras

para2html :: Paragraph -> String
para2html p = case p of
  Text its      -> concat (map item2html its)
  Item its      -> mkTagXML "li" ++ concat (map item2html its)
  Code s        -> unlines $ tagXML "pre" $ map (indent 2) $ filter (not . ignore) $ 
                                              remEmptyLines $ lines $ spec s
  New           -> mkTagXML "p"
  NewPage       -> mkTagXML "p" ++ "\n" ++ mkTagXML "!-- NEW --"
  Heading i its -> concat $ tagXML ('h':show i) [concat (map item2html its)]

item2html :: TextItem -> String
item2html i = case i of
  Str s -> spec s
  Emp s -> concat $ tagXML "b" [spec s]
  Lit s -> concat $ tagXML "i" [spec s]
  Inl s -> concat $ tagXML "tt" [spec s]

mkTagXML t = '<':t ++ ">"
mkEndTagXML t = mkTagXML ('/':t)
tagXML t ss = mkTagXML t : ss ++ [mkEndTagXML t]

spec = elimLt

elimLt s = case s of
  '<':cs -> "&lt;" ++ elimLt cs
  c  :cs -> c : elimLt cs
  _      -> s


-- render in latex

doc2latex :: Doc -> String
doc2latex (Doc title time paras) = unlines $
  preludeLatex :
  funLatex "title"  [concat (map item2latex title)] :
  funLatex "author" [fontLatex "footnotesize" (welcome)] :
  envLatex "document" (
    funLatex "maketitle" [] :
    map para2latex paras)   

para2latex :: Paragraph -> String
para2latex p = case p of
  Text its      -> concat (map item2latex its)
  Item its      -> "\n\n$\\bullet$" ++ concat (map item2latex its) ++ "\n\n"
  Code s        -> unlines $ envLatex "verbatim" $ map (indent 2) $ 
                                                   remEmptyLines $ lines $ s
  New           -> "\n"
  NewPage       -> "\\newpage"
  Heading i its -> headingLatex i (concat (map item2latex its))

item2latex :: TextItem -> String
item2latex i = case i of
  Str s -> specl s
  Emp s -> fontLatex "bf" (specl s)
  Lit s -> fontLatex "it" (specl s)
  Inl s -> fontLatex "tt" (specl s)

funLatex :: String -> [String] -> String
funLatex f xs = "\\" ++ f ++ concat ["{" ++ x ++ "}" | x <- xs]

envLatex :: String -> [String] -> [String]
envLatex e ss = 
  funLatex "begin" [e] :
  ss ++
  [funLatex "end" [e]]

headingLatex :: Int -> String -> String
-- for slides
-- headingLatex _ s = funLatex "newone" [] ++ "\n" ++ funLatex "heading" [s] 
headingLatex i s = funLatex t [s] where 
  t = case i of
    2 -> "section"
    3 -> "subsection"
    _ -> "subsubsection"

fontLatex :: String -> String -> String
fontLatex f s = "{\\" ++ f ++ " " ++ s ++ "}"

specl = eliml

eliml s = case s of
  '|':cs -> mmath "mid" ++ elimLt cs
  '{':cs -> mmath "\\{" ++ elimLt cs
  '}':cs -> mmath "\\}" ++ elimLt cs
  _      -> s

mmath s = funLatex "mbox" ["$" ++ s ++ "$"]

preludeLatex = unlines $ [
  "\\documentclass[12pt]{article}",
  "\\usepackage{isolatin1}",
  "\\setlength{\\oddsidemargin}{0mm}",
  "\\setlength{\\evensidemargin}{-2mm}",
  "\\setlength{\\topmargin}{-16mm}",
  "\\setlength{\\textheight}{240mm}",
  "\\setlength{\\textwidth}{158mm}",
  "\\setlength{\\parskip}{2mm}",
  "\\setlength{\\parindent}{0mm}"
 ]

-- render in txt2tags
-- as main document (welcome, top-level subtitles)
-- as chapter (no welcome, subtitle level + i)

doc2txt :: Doc -> String
doc2txt (Doc title time paras) = unlines $
  let tit = concat (map item2txt title) in
      tit:
      ("Last update: " ++ time):
      "":
      "% NOTE: this is a txt2tags file.":
      "% Create an html file from this file using:":
      ("% txt2tags " ++ tit):
      "\n":
      concat (["Produced by " ++ welcome]) :
      "\n" :
      empty :
      map (para2txt 0) paras

doc2txt2 :: Doc -> String
doc2txt2 (Doc title time paras) = unlines $
  let tit = concat (map item2txt title) in
      tit:
      "":
      concat (tagTxt (replicate 2 '=') [tit]):
      "\n":
      empty :
      map (para2txt 2) paras

para2txt :: Int -> Paragraph -> String
para2txt j p = case p of
  Text its      -> concat (map item2txt its)
  Item its      -> "- " ++ concat (map item2txt its)
  Code s        -> unlines $ tagTxt "```" $ map (indent 2) $ 
                                              remEmptyLines $ lines s
  New           -> "\n"
  NewPage       -> "\n" ++ "!-- NEW --"
  Heading i its -> 
    concat $ tagTxt (replicate (i + j) '=') [concat (map item2txt its)]

item2txt :: TextItem -> String
item2txt i = case i of
  Str s -> s
  Emp s -> concat $ tagTxt "**" [spec s]
  Lit s -> concat $ tagTxt "//" [spec s]
  Inl s -> concat $ tagTxt "``" [spec s]

tagTxt t ss = t : ss ++ [t]



-- auxiliaries

empty = ""

isComment = (== "--") . take 2

begComment =  (== "{-") . take 2

getComment ss = case ss of
  "-}":ls -> ([],ls)
  l:ls -> (l : s1, s2) where (s1,s2) = getComment ls
  _    -> ([],[])

indent n = (replicate n ' ' ++)

remEmptyLines = rem False where
  rem prevGood ls = case span empty ls of
    (_ :_, ss@(_ : _)) -> (if prevGood then ("":) else id) $ rem False ss
    (_,    [])         -> []
    (_,    s:ss)       -> s : rem True ss 
  empty = all isSpace