~ubuntu-branches/ubuntu/natty/lua-gtk/natty

« back to all changes in this revision

Viewing changes to script/gen-html.lua

  • Committer: Bazaar Package Importer
  • Author(s): Enrico Tassi
  • Date: 2009-05-17 18:16:21 UTC
  • mfrom: (1.2.1 upstream) (4.1.1 experimental)
  • Revision ID: james.westby@ubuntu.com-20090517181621-9kmdd82nxg54jsio
* new upstream snapshot comprising many more GNOME libraries:
    Gtk, GDK, GLib, Pango, Atk, Libxml2, Cairo, Clutter, Gtkhtml, 
    GtkSourceView, Gio, Gtkspell and GConf. 
* new upstream release includes a new configure script written in Lua,
  no more bashisms there (Closes: #507205)
* renamed binary packages to liblua5.1-gnome-*
* updated standards-version to 3.8.1, no changes needed
* patch to load .so.* version of libraries and not .so (that was requiring
  -dev packages) (Closes: #522087)
* removed redundant Architecture line from the source stanza of control
  (Closes: #498120)
* updated copyright file, Wolfgang Oertl holds it for 2009 too.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#! /usr/bin/env lua
2
2
-- vim:sw=4:sts=4
3
 
--
4
 
--
5
 
--
6
 
--
7
 
--
8
 
 
9
 
require "luadoc.lp"
 
3
--[[
 
4
 
 
5
 Generate static HTML pages for simple websites.
 
6
 Copyright (C) 2007, 2009 Wolfgang Oertl <wolfgang.oertl@gmail.com>
 
7
 
 
8
 Features:
 
9
 
 
10
  - recursively reads all files, processes the .html files, copies .css,
 
11
    .jpg and .png files.
 
12
 
 
13
  - uses a template file with header and document layout.
 
14
 
 
15
  - parses the input HTML files and processes following patterns:
 
16
    {{item ...}}
 
17
    An item can be:
 
18
        #label          anchor, can be referenced.  No whitespace in label.
 
19
        *               hide this entry (no text output)
 
20
        =label          reference to that anchor.  If not text is given,
 
21
                        use the text of the referenced element.
 
22
        noindex         don't add to the index
 
23
        Any text        text content of this directive; must be the last
 
24
                        item.
 
25
 
 
26
  - Can generate a sorted index of keywords
 
27
 
 
28
  - Generates a short horizontal and detailed vertical menu linking to all
 
29
    the pages using the menu definition in the file "menu.lua" in the
 
30
    input directory.
 
31
 
 
32
  - detects .html files which are not mentioned in the menu, and complains
 
33
    about them.
 
34
 
 
35
  - can use .html.in files in the _output_ directory instead of the
 
36
    equivalent .html file in the input directory.  This enables another
 
37
    program to generate input files which will then get the document
 
38
    structure and appear in the menu.
 
39
 
 
40
 menu_entry structure: [1]=basename, [2]=title, [3]=submenu, [seen]=true
 
41
 
 
42
--]]
 
43
 
10
44
require "lfs"
11
45
 
12
 
html_header = [[
13
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
14
 
    "http://www.w3.org/TR/html4/strict.dtd">
15
 
<html>
16
 
 
17
 
<head>
18
 
 <meta name="description" content="The Lua-Gtk Homepage #TITLE#">
19
 
 <meta name="keywords" content="Lua, Gtk">
20
 
 <title>Lua-Gtk #TITLE#</title>
21
 
 <link rel="stylesheet" href="lua-gtk.css" type="text/css">
22
 
</head>
23
 
 
24
 
<body>
25
 
 
26
 
<div id="header">
27
 
 <img width=128 height=128 border=0 alt="Lua-Gtk Logo" src="img/lua-gtk-logo.png"/>
28
 
 <p>Binding to Gtk 2 for Lua</p>
29
 
 <p>
30
 
  <a href="index.html">Home</a> &middot;
31
 
  <a href="examples1.html">Examples 1</a> &middot;
32
 
  <a href="examples2.html">Examples 2</a> &middot;
33
 
  <a href="reference.html">Reference</a>
34
 
 </p>
35
 
</div>
36
 
 
37
 
<div id="content">
38
 
]]
39
 
 
40
 
html_trailer = [[
41
 
</div>
42
 
</body>
43
 
</html>
44
 
]]
45
 
 
46
 
output_dir = "../build/html/"
47
 
 
 
46
page_template = nil
 
47
input_dir = nil
 
48
output_dir = nil
 
49
config = nil
 
50
main_menu = nil
 
51
extensions = { png=true, gif=true, jpg=true, css=true }
 
52
 
 
53
-- Handling of {{...}} directives
 
54
items = {}                  -- array of items
 
55
items_byname = {}           -- key=anchor name, value=item
 
56
files = {}                  -- array of input HTML files
 
57
curr_file = nil             -- file currently being read, see _process_html
 
58
 
 
59
-- Handling of the index generation
 
60
index_string = nil          -- computed index, used by generate_index()
 
61
 
 
62
-- expand tabs; taken from "Programming in Lua" by Roberto Ierusalimschy
 
63
function expand_tabs(s, tab)
 
64
    local corr = 0
 
65
    tab = tab or 8
 
66
    s = string.gsub(s, "()\t", function(p)
 
67
        local sp = tab - (p - 1 + corr) % tab
 
68
        corr = corr - 1 + sp
 
69
        return string.rep(" ", sp)
 
70
    end)
 
71
    return s
 
72
end
 
73
 
 
74
-- colorize keywords
 
75
lua_keywords = { 
 
76
    "and", "break", "do", "else", "elseif", "end", "false", "for",
 
77
    "function", "if", "in", "local", "nil", "not", "or", "repeat",
 
78
    "return", "then", "true", "until", "while",
 
79
}
 
80
 
 
81
--[[
 
82
lua_library = { 
 
83
    "assert", "collectgarbage", "dofile", "error", "getfenv",
 
84
    "getmetatable", "ipairs", "load", "loadfile", "loadstring", "module",
 
85
    "next", "pairs", "pcall", "print" "rawequal", "rawget", "rawset",
 
86
    "require", "select", "setfenv", "setmetatable", "tonumber", "tostring",
 
87
    "type", "unpack", "xpcall",
 
88
}
 
89
--]]
 
90
 
 
91
lua_gnome = { "gnome", "gtk", "glib", "gdk", "pango", "cairo", "gtkhtml",
 
92
    "gtksourceview" }
 
93
 
 
94
-- globals
 
95
lua_keyindex = nil
 
96
col_res = nil
 
97
function put(s)
 
98
    col_res[#col_res + 1] = s
 
99
end
 
100
word = ""
 
101
delim = nil
 
102
 
 
103
states = {
 
104
 
 
105
    -- looking for start of word
 
106
    [1] = function(c)
 
107
        if c == " " or c == "\n" then
 
108
            put(c)
 
109
            return 1
 
110
        end
 
111
 
 
112
        word = c
 
113
 
 
114
        -- start of string
 
115
        if c == '"' or c == "'" then
 
116
            delim = c
 
117
            return 4
 
118
        end
 
119
 
 
120
        if c >= '0' and c <= '9' then
 
121
            return 5
 
122
        end
 
123
 
 
124
        return 2
 
125
    end,
 
126
 
 
127
    -- collecting a word
 
128
    [2] = function(c)
 
129
        if string.match(c, "^[a-zA-Z_-]$") then
 
130
            word = word .. c
 
131
            -- comment
 
132
            if word == "--" then
 
133
                return 3
 
134
            end
 
135
            return 2
 
136
        end
 
137
 
 
138
        -- number after "-": a negative constant.
 
139
        if c >= "0" and c <= "9" and word == "-" then
 
140
            return states[5](c)
 
141
        end
 
142
 
 
143
        local cl = lua_keyindex[word]
 
144
        if cl then
 
145
            put(string.format("<b class=\"%s\">%s</b>", cl, word))
 
146
        elseif c == '.' then
 
147
            word = word .. c
 
148
            return 2
 
149
        else
 
150
            put(word)
 
151
        end
 
152
        word = ""
 
153
        put(c)
 
154
        return 1
 
155
    end,
 
156
 
 
157
    -- comment
 
158
    [3] = function(c)
 
159
        if c == "\n" then
 
160
            put("<b class=\"co\">" .. word .. "</b>\n")
 
161
            word = ""
 
162
            return 1
 
163
        end
 
164
        word = word .. c
 
165
        return 3
 
166
    end,
 
167
 
 
168
    -- in a string
 
169
    [4] = function(c)
 
170
        word = word .. c
 
171
        if c == delim then
 
172
            put("<b class=\"st\">" .. word .. "</b>")
 
173
            return 1
 
174
        end
 
175
        return 4
 
176
    end,
 
177
 
 
178
    -- in a number
 
179
    [5] = function(c)
 
180
        if c >= '0' and c <= '9' then
 
181
            word = word .. c
 
182
            return 5
 
183
        end
 
184
        put("<b class=\"st\">" .. word .. "</b>")
 
185
        return states[1](c)
 
186
    end,
 
187
 
 
188
}
 
189
 
 
190
 
 
191
---
 
192
-- Given some Lua code in "s" (may be one line or multiple lines), return
 
193
-- HTML code for a colorized (syntax highlighted) representation.
 
194
--
 
195
function colorize(s)
 
196
    if not lua_keyindex then
 
197
        lua_keyindex = {}
 
198
        for _, k in ipairs(lua_keywords) do lua_keyindex[k] = "kw" end
 
199
        for _, k in ipairs(lua_gnome) do lua_keyindex[k] = "gn" end
 
200
        for prefix, ar in pairs { [""]=_G, ["string."]=string,
 
201
            ["math."]=math, ["io."]=io, ["package."]=package,
 
202
            ["os."]=os, ["debug."]=debug, ["table."]=table,
 
203
            ["coroutine."]=coroutine } do
 
204
            for k, v in pairs(ar) do
 
205
                if type(v) == "function" then
 
206
                    lua_keyindex[prefix .. k] = "lb"
 
207
                end
 
208
            end
 
209
        end
 
210
    end
 
211
 
 
212
    local state = 1
 
213
    word = ""
 
214
    col_res = {}
 
215
    for c in string.gmatch(s, ".") do
 
216
        state = states[state](c)
 
217
    end
 
218
    states[state] "\n"
 
219
    while col_res[#col_res] == "\n" do
 
220
        table.remove(col_res)
 
221
    end
 
222
    return table.concat(col_res, "")
 
223
end
 
224
 
 
225
 
 
226
---
 
227
-- The environment available to the functions in the template.  Note that
 
228
-- all global variables (including functions) are available too.  This
 
229
-- should probably change.
 
230
--
48
231
env = {
49
232
 
50
 
   io = io, 
51
 
 
52
 
    -- page header
53
 
    html_header = function(title)
54
 
        -- avoid the second return value (number of substitutions) to be
55
 
        -- returned, too.
56
 
        local s = string.gsub(html_header, "#TITLE#", title)
57
 
        return s
58
 
    end,
59
 
 
60
 
    html_trailer = function()
61
 
        return html_trailer
62
 
    end,
63
 
 
64
233
    -- extract a function from a Lua source file
65
234
    copy_function = function(file, name)
66
235
        local state = 0
67
236
        local res = {}
68
237
 
69
 
        local exists, _ = lfs.attributes("../" .. file)
70
 
        if not exists then return "" end
 
238
        local exists, _ = lfs.attributes(file)
 
239
        if not exists then return "not found: " .. file end
71
240
 
72
 
        for line in io.lines("../" .. file) do
 
241
        for line in io.lines(file) do
73
242
            if state == 0 then
74
243
                if string.match(line, "function " .. name) then
75
244
                    state = 1
86
246
            end
87
247
 
88
248
            if state == 1 then
89
 
                table.insert(res, line)
 
249
                res[#res + 1] = colorize(expand_tabs(line))
90
250
                if string.match(line, "^end") then
91
251
                    break
92
252
                end
96
256
        return table.concat(res, "\n")
97
257
    end,
98
258
 
 
259
    copy_file = function(file)
 
260
        local f = io.open(file, "rb")
 
261
        if not f then return "not found: " .. file end
 
262
        local s = f:read "*a"
 
263
        f:close()
 
264
        return "<div class=\"code\"><code>\n" .. colorize(s)
 
265
            .. "</code></div>\n"
 
266
    end,
 
267
 
 
268
    inline_code = function(s, ...)
 
269
        local sep = select('#', ...) > 0 and "\n" or ""
 
270
        return "<div class=\"code\"><code>\n" .. colorize(s)
 
271
            .. sep .. table.concat({...}, "\n")
 
272
            .. "</code></div>\n"
 
273
    end,
 
274
 
 
275
    generate_index = function()
 
276
        return index_string or ""
 
277
    end,
99
278
}
100
279
 
101
280
 
113
292
    end
114
293
end
115
294
 
 
295
 
116
296
---
117
297
-- Copy a file.  All the directories leading to the destination file are
118
298
-- automatically created.
121
301
-- @param to  Destination
122
302
--
123
303
function _file_copy(from, to)
124
 
    local f_from = io.open(from)
 
304
    local f_from, f_to, buf
 
305
 
 
306
    f_from = lfs.attributes(from)
 
307
    f_to = lfs.attributes(to)
 
308
 
 
309
    -- Skip unchanged files.
 
310
    if f_from and f_to and f_from.size == f_to.size and f_from.modification
 
311
        <= f_to.modification then
 
312
        return
 
313
    end
 
314
 
 
315
    f_from = io.open(from, "rb")
125
316
    assert(f_from)
126
317
    _mkdir(to)
127
 
    local f_to = io.open(to, "w")
 
318
    f_to = io.open(to, "wb")
128
319
    assert(f_to)
129
320
 
130
321
    while true do
131
 
        local buf = f_from:read("*a", 2048)
 
322
        buf = f_from:read("*a", 2048)
132
323
        if not buf or #buf == 0 then break end
133
324
        f_to:write(buf)
134
325
    end
137
328
    f_to:close()
138
329
end
139
330
 
 
331
 
 
332
---
 
333
-- Add some entries to the menu: _parent in each item, further a basename
 
334
-- index in config.menu_index.
 
335
--
 
336
function _prepare_menu(top, parent, ar)
 
337
    ar = ar or top
 
338
    for i, item in ipairs(ar) do
 
339
        config.menu_index[item[1]] = item
 
340
        item._parent = parent
 
341
        if item[3] then
 
342
            _prepare_menu(top, item, item[3])   -- recurse
 
343
        end
 
344
    end
 
345
end
 
346
 
 
347
-- recursively look for the given basename.
 
348
-- ar_in: the part of the menu to look in
 
349
-- ar_out: path to the item if found; [1]=most specific, [2]=parent etc.
 
350
function _find_in_menu(basename, ar_in, ar_out)
 
351
 
 
352
    for i, item in ipairs(ar_in) do
 
353
        if item[1] == basename then
 
354
            ar_out[#ar_out+1] = item
 
355
            return true
 
356
        end
 
357
 
 
358
        if item[3] and _find_in_menu(basename, item[3], ar_out) then
 
359
            ar_out[#ar_out+1] = item
 
360
            return true
 
361
        end
 
362
    end
 
363
 
 
364
end
 
365
 
 
366
 
 
367
---
 
368
-- Build the side menu for the given menu_entry.  It consists of all siblings
 
369
-- and all childs.
 
370
-- @param menu  A menu structure
 
371
-- @param current  The current menu; in order to descend there and display it
 
372
--   differently.
 
373
--
 
374
function make_side_menu(current)
 
375
    local path, m, tbl
 
376
 
 
377
    -- determine the path to the current menu entry
 
378
    path = {}
 
379
    m = current
 
380
    while m do
 
381
        path[m] = 1
 
382
        m = m._parent
 
383
    end
 
384
    path[current] = 2
 
385
 
 
386
    tbl = {}
 
387
    _make_side_menu(tbl, config.menu, path)
 
388
 
 
389
    if #tbl == 0 then return "" end
 
390
    return table.concat(tbl, "\n")
 
391
end
 
392
 
 
393
function _make_side_menu(tbl, menu, path)
 
394
    if #menu == 0 then return end
 
395
 
 
396
    tbl[#tbl + 1] = "<ul>"
 
397
 
 
398
    for i, item in ipairs(menu) do
 
399
        if path[item] == 2 then
 
400
            tbl[#tbl + 1] = string.format("<li><b>%s</b></li>", item[2])
 
401
        else
 
402
            tbl[#tbl + 1] = string.format("<li><a href=\"%s.html\">%s</a></li>",
 
403
                item[1], item[2])
 
404
        end
 
405
        if path[item] and item[3] then
 
406
            tbl[#tbl + 1] = '<li>'
 
407
            _make_side_menu(tbl, item[3], path)
 
408
            tbl[#tbl + 1] = '</li>'
 
409
        end
 
410
    end
 
411
 
 
412
    tbl[#tbl + 1] = "</ul>"
 
413
end
 
414
 
 
415
-- Helper function for _make_side_menu.
 
416
function _add_menu_items(tbl, ar)
 
417
    for i, item in ipairs(ar) do
 
418
        tbl[#tbl + 1] = string.format("<li><a href=\"%s.html\">%s</a></li>",
 
419
            item[1], item[2])
 
420
    end
 
421
end
 
422
 
 
423
 
 
424
---
 
425
-- Fill the template using the current menu entry and the given input file,
 
426
-- and write the resulting HTML file to ofile.
 
427
--
 
428
-- @param basename  Name of the output file without the output base path.
 
429
-- @param ar  Array with variables available to the page for substitution
 
430
--
 
431
function _process_html(ifname, basename, menu_entry, do_index)
 
432
    local ifile, ar, buf
 
433
 
 
434
    ifile = assert(io.open(ifname, "rb"))
 
435
 
 
436
    ar = ar or {}
 
437
    curr_file = {
 
438
        variables = ar,
 
439
        file_name = ifname,
 
440
        basename = basename,
 
441
        menu_entry = menu_entry,
 
442
        index_count = 0,
 
443
    }
 
444
 
 
445
    ar.SIDEMENU = make_side_menu(menu_entry)
 
446
    ar.TITLE = menu_entry[2]
 
447
    ar.MAINMENU = main_menu
 
448
    ar.CONTENTCLASS = (ar.SIDEMENU == "") and "center" or "right"
 
449
 
 
450
    buf = {}
 
451
    for line in ifile:lines() do
 
452
        buf[#buf + 1] = string.gsub(line, "{{(.-)}}", _html_pass1)
 
453
    end
 
454
    ifile:close()
 
455
    ar.CONTENT = table.concat(buf, "\n")
 
456
    -- ar.CONTENT = string.gsub(ifile:read"*a", "{{(.-)}}", _html_pass1)
 
457
 
 
458
    files[#files + 1] = curr_file
 
459
    curr_file = nil
 
460
end
 
461
 
 
462
---
 
463
-- Second pass over HTML files and output.
 
464
--
 
465
function output_html()
 
466
    local ifile, page, old_page, ofile, ofname, skip
 
467
 
 
468
    if not page_template then
 
469
        ifile = assert(io.open(input_dir .. "/template.html", "rb"))
 
470
        page_template = ifile:read "*a"
 
471
        ifile:close()
 
472
    end
 
473
 
 
474
    for _, file in ipairs(files) do
 
475
        _evaluate_html_pass2(file)
 
476
        page = string.gsub(page_template, "#([A-Z]+)#", file.variables)
 
477
 
 
478
        ofname = output_dir .. "/" .. file.basename
 
479
 
 
480
        -- Check for changes.  This avoids a newer date on unchanged
 
481
        -- files.
 
482
        skip = false
 
483
        if lfs.attributes(ofname, "mode") == "file" then
 
484
            ifile = assert(io.open(ofname, "rb"))
 
485
            old_page = ifile:read"*a"
 
486
            ifile:close()
 
487
            if page == old_page then
 
488
                skip = true
 
489
            else
 
490
                print("CHANGES IN", ofname)
 
491
            end
 
492
        end
 
493
 
 
494
        if not skip then
 
495
            ofile = assert(io.open(ofname, "wb"))
 
496
            ofile:write(page)
 
497
            ofile:close()
 
498
        end
 
499
    end
 
500
end
 
501
 
 
502
---
 
503
-- Split a string using a delimiter, which can be a search pattern.  Make sure
 
504
-- that the delimiter doesn't match the empty string.
 
505
--  
 
506
function split(s, delim, is_plain)
 
507
    local ar, pos = {}, 1
 
508
 
 
509
    while true do
 
510
        local first, last = s:find(delim, pos, is_plain)
 
511
        if first then
 
512
            table.insert(ar, s:sub(pos, first-1))
 
513
            pos = last + 1
 
514
        else
 
515
            table.insert(ar, s:sub(pos))
 
516
            break
 
517
        end
 
518
    end
 
519
 
 
520
    return ar
 
521
end
 
522
 
 
523
---
 
524
-- Handler for {{...}} matches during the first pass over the HTML content.
 
525
-- These strings are replaced by {{{%d}}}, the data being stored elsewhere.
 
526
--
 
527
-- Globals: curr_file is the file being read.
 
528
--
 
529
function _html_pass1(str)
 
530
    local c, item
 
531
 
 
532
    -- Split the string into elements and fill "item" with data.
 
533
    item = { file=curr_file }
 
534
    for _, s in ipairs(split(str, " +")) do
 
535
        c = string.sub(s, 1, 1)
 
536
        if c == "#" then
 
537
            item.is_anchor = true
 
538
            item.anchor_name = string.sub(s, 2)
 
539
        elseif c == "*" then
 
540
            item.is_hidden = true
 
541
        elseif c == "=" then
 
542
            item.is_reference = true
 
543
            item.ref_name = string.sub(s, 2)
 
544
        elseif s == "noindex" then
 
545
            item.omit_index = true
 
546
        elseif item.text then
 
547
            item.text = item.text .. " " .. s
 
548
        else
 
549
            item.text = s
 
550
        end
 
551
    end
 
552
 
 
553
    -- if this item has no anchor name, generate the next available
 
554
    if not item.is_anchor then
 
555
        curr_file.index_count = curr_file.index_count + 1
 
556
        item.anchor_name = string.format("idx%d", curr_file.index_count)
 
557
    end
 
558
 
 
559
    if item.is_hidden then
 
560
        item.full_anchor = curr_file.basename
 
561
    else
 
562
        item.full_anchor = string.format("%s#%s", curr_file.basename,
 
563
            item.anchor_name)
 
564
    end
 
565
 
 
566
    -- assign the next number and store.  If an anchor is defined, store
 
567
    -- that too.
 
568
    item.nr = #items + 1
 
569
    items[#items + 1] = item
 
570
    if item.is_anchor then
 
571
        assert(items_byname[item.anchor_name] == nil, "Duplicate anchor")
 
572
        items_byname[item.anchor_name] = item
 
573
    end
 
574
 
 
575
    return string.format("{{{%d}}}", item.nr)
 
576
end
 
577
 
 
578
---
 
579
-- Replace the {{{%d}}} strings with their proper content.
 
580
--
 
581
function _html_pass2(nr)
 
582
    local item, target
 
583
 
 
584
    item = assert(items[tonumber(nr)], "Item " .. tostring(nr) .. " not found")
 
585
 
 
586
    -- nothing is output for hidden items.
 
587
    if item.is_hidden then
 
588
        -- assert(not item.is_anchor)
 
589
        assert(not item.is_reference)
 
590
        return ""
 
591
    end
 
592
 
 
593
    -- a reference is replaced with a link to the referenced anchor
 
594
    if item.is_reference then
 
595
        target = items_byname[item.ref_name]
 
596
        if not target then
 
597
            error(string.format("%s(%d): Missing target %s",
 
598
                item.file.basename,
 
599
                item.line_nr or 0,
 
600
                item.ref_name))
 
601
        end
 
602
 
 
603
        assert(target.is_anchor)
 
604
        assert(item.text or target.text)
 
605
        return string.format('<a href="%s">%s</a>', target.full_anchor,
 
606
            item.text or target.text)
 
607
    end
 
608
 
 
609
    -- named anchors are set
 
610
    if item.is_anchor then
 
611
        return string.format('<a name="%s">%s</a>', item.anchor_name,
 
612
            item.text)
 
613
    end
 
614
 
 
615
    -- unnamed anchor - for the index
 
616
    assert(item.text)
 
617
    return string.format('<a name="%s">%s</a>', item.anchor_name, item.text)
 
618
end
 
619
 
 
620
 
 
621
---
 
622
-- Perform the second pass over the HTML files.  First, {{{%d}}} items left
 
623
-- by the first pass are replaced with their final value, and then inline
 
624
-- Lua code is executed.
 
625
--
 
626
function _evaluate_html_pass2(file)
 
627
    local v = file.variables
 
628
 
 
629
    v.CONTENT = string.gsub(v.CONTENT, "{{{(%d+)}}}", _html_pass2)
 
630
 
 
631
    -- curr_menu = file.menu_entry
 
632
    v.CONTENT = string.gsub(v.CONTENT, "<%%=(.-)%%>", function(fn)
 
633
        local chunk = assert(loadstring("return " .. fn))
 
634
        setfenv(chunk, env)
 
635
        return chunk()
 
636
    end)
 
637
end
 
638
 
140
639
---
141
640
-- Process a file.  If it is a HTML file, run the luadoc template routines on
142
641
-- it, otherwise (if it has a known extension) copy it to the destination.
143
642
--
144
643
function _read_file(path)
145
 
    if string.match(path, "%.html$") then
146
 
        print("Processing " .. path)
147
 
        _mkdir(output_dir .. path)
148
 
        local f = io.open(output_dir .. path, "w")
149
 
        assert(f)
150
 
        io.output(f)
151
 
        luadoc.lp.include(path, env)
152
 
        f:close()
 
644
    local path1, path_in, basename, menu_entry
 
645
 
 
646
    -- basename of the file to process
 
647
    path1 = string.sub(path, #input_dir + 2)
 
648
    _mkdir(output_dir .. "/" .. path1)
 
649
 
 
650
    basename = string.match(path, "([a-z0-9_-]+)%.html$")
 
651
    if basename then
 
652
        if basename == "template" then return end
 
653
        menu_entry = assert(config.menu_index[basename],
 
654
            "Missing menu entry for input file " .. basename)
 
655
        -- if a .in file exists in the output directory, process that instead.
 
656
        -- it might exist if the doc file has been preprocessed.
 
657
        path_in = output_dir .. "/" .. path1 .. ".in"
 
658
        if lfs.attributes(path_in, "mode") ~= "file" then
 
659
            path_in = path
 
660
        end
 
661
        print("Processing " .. path1)
 
662
        menu_entry.seen = true
 
663
        _process_html(path_in, path1, menu_entry)
153
664
        return
154
665
    end
155
666
 
156
 
    if string.match(path, "%.png$") or string.match(path, "%.css$") then
 
667
    local ext = string.match(path, "([^.]+)$")
 
668
 
 
669
    if extensions[ext] then
157
670
        print("Copying " .. path)
158
 
        _file_copy(path, output_dir .. path)
 
671
        _file_copy(path, output_dir .. "/" .. path1)
159
672
        return
160
673
    end
161
674
end
162
675
 
 
676
 
163
677
---
164
678
-- Process a file or directory.  Files are handled by _read_file, while
165
679
-- directories are recursed into.
180
694
    end
181
695
end
182
696
 
183
 
_read_file_dir(".")
 
697
 
 
698
---
 
699
-- Read the configuration file for the documentation, which currently only
 
700
-- defines the menu structure, including the title for each entry.
 
701
--
 
702
function _read_config(ifname)
 
703
    local ifile = assert(io.open(ifname))
 
704
    local s = ifile:read "*a"
 
705
    ifile:close()
 
706
    local closure = assert(loadstring(s))
 
707
    config = {}
 
708
    setfenv(closure, config)
 
709
    closure()
 
710
 
 
711
    -- build the main menu
 
712
    local tbl = {}
 
713
    for _, entry in ipairs(config.menu) do
 
714
        tbl[#tbl + 1] = string.format("<a href=\"%s.html\">%s</a>",
 
715
            entry[1], entry[2])
 
716
    end
 
717
    main_menu = table.concat(tbl, " &middot;\n")
 
718
 
 
719
    config.menu_index = {}
 
720
    _prepare_menu(config.menu)
 
721
 
 
722
end
 
723
 
 
724
 
 
725
---
 
726
-- Walk the menu tree and find entries that no file was generated for.
 
727
-- Either find a ".in" file in the build directory, or complain.
 
728
--
 
729
function _check_menu()
 
730
    local ifname, ofbase
 
731
 
 
732
    for basename, item in pairs(config.menu_index) do
 
733
        if not item.seen then
 
734
            ifname = string.format("%s/%s.html.in", output_dir, basename)
 
735
            ofbase = string.format("%s.html", basename)
 
736
 
 
737
            if lfs.attributes(ifname, "mode") == "file" then
 
738
                print("Processing " .. ifname)
 
739
                item.seen = true
 
740
                _process_html(ifname, ofbase, item)
 
741
            else
 
742
                print("Missing input file for", basename)
 
743
            end
 
744
        end
 
745
    end
 
746
end
 
747
 
 
748
 
 
749
---
 
750
-- Create a HTML snippet with the alphabetically sorted index.  All the
 
751
-- HTML files have already been read.
 
752
--
 
753
function generate_index()
 
754
    local keys, buf, item
 
755
 
 
756
    -- collect all the strings to be placed in the index, sort.
 
757
    keys = {}
 
758
    for _, item in ipairs(items) do
 
759
        if not item.omit_index and item.text then
 
760
            keys[#keys + 1] = { string.upper(item.text), item }
 
761
        end
 
762
    end
 
763
    table.sort(keys, function(a, b) return a[1] < b[1] end)
 
764
 
 
765
    -- combine index entries with the same string
 
766
    for i, item in ipairs(keys) do
 
767
        while keys[i + 1] and keys[i + 1][1] == item[1] do
 
768
            item[#item + 1] = keys[i + 1][2]
 
769
            table.remove(keys, i + 1)
 
770
        end
 
771
    end
 
772
 
 
773
    -- build the index string
 
774
    buf = {}
 
775
    for i, tmp in ipairs(keys) do
 
776
        buf[#buf + 1] = tmp[2].text .. ": "
 
777
        for i = 2, #tmp do
 
778
            buf[#buf + 1] = string.format('%s<a href="%s">%d</a>',
 
779
                i > 2 and ", " or "",
 
780
                tmp[i].full_anchor, i - 1)
 
781
        end
 
782
 
 
783
        buf[#buf + 1] = "<br/>\n"
 
784
    end
 
785
 
 
786
    index_string = table.concat(buf)
 
787
end
 
788
 
 
789
-- MAIN --
 
790
if not arg[2] then
 
791
    print(string.format("Usage: %s [input directory] [output directory]",
 
792
        arg[0]))
 
793
    os.exit(1)
 
794
end
 
795
 
 
796
input_dir = arg[1]
 
797
output_dir = arg[2]
 
798
_read_config(arg[1] .. "/menu.lua")
 
799
_read_file_dir(arg[1])
 
800
_check_menu()
 
801
generate_index()
 
802
output_html()
 
803
 
184
804