1-------------------------------------------------------------------------------
2-- Name:        genidocs.lua
3-- Purpose:     This script generates docs from the wxLua interface files
4-- Author:      John Labenski
5-- Created:     19/05/2006
6-- Copyright:   John Labenski
7-- Licence:     wxWidgets licence
8-------------------------------------------------------------------------------
9
10completeClassRefTable = nil -- a table of names that is a complete list of classes
11                            -- from a library the wrapper are for
12                            -- For wxWidgets this is taken from the alphabetical
13                            -- list of classes in the wxWidgets reference manual
14                            -- This is used to print if a class is wrapped or not.
15
16typedefTable          = {} -- filled from the data cache files
17dataTypeTable         = {}
18preprocConditionTable = {}
19
20colours = {}
21
22colours.class    = "DD0000" -- red
23colours.member   = "CC6600" -- orange
24colours.rename   = "990099" -- dark pink
25colours.override = "BB0055" -- reddish pink
26colours.operator = "663300" -- brown
27
28colours.enum     = "0066CC" -- blue
29colours.define   = "006666" -- turquoise
30colours.event    = "660033" -- purple
31colours.func     = "AA0000" -- dark red
32
33colours.comment    = "009900" -- green
34colours.blkcomment = "888888" -- grey
35
36colours.in_manual     = "AAFFAA" -- for table showing classes
37colours.in_wxlua      = "AAFFAA"
38colours.not_in_manual = "FFAAAA"
39colours.not_in_wxlua  = "FFAAAA"
40
41-- ----------------------------------------------------------------------------
42-- Dummy function that genwxbind.lua has and the XXX_rules.lua might use
43-- ----------------------------------------------------------------------------
44
45function AllocDataType() end
46
47-- ----------------------------------------------------------------------------
48-- For testing and choosing pleasing colors
49-- ----------------------------------------------------------------------------
50
51function GenerateTestColours(fileTable)
52    table.insert(fileTable, "<h2>Colours used to denote types</h2>")
53
54    table.insert(fileTable, MakeColour("Comments - //", colours.comment).."<br>")
55    table.insert(fileTable, MakeColour("Block Comments - /* ... */", colours.blkcomment).."<br>")
56
57    table.insert(fileTable, MakeColour("Enums - enum", colours.enum).."<br>")
58    table.insert(fileTable, MakeColour("Defines - #define [_string] [_object] [_pointer]", colours.define).."<br>")
59    table.insert(fileTable, MakeColour("Events - %define_event", colours.event).."<br>")
60    table.insert(fileTable, MakeColour("Functions - %function", colours.func).."<br>")
61
62    table.insert(fileTable, MakeColour("Classes - class", colours.class).."<br>")
63    table.insert(fileTable, MakeColour("Class Members - %member", colours.member).."<br>")
64    table.insert(fileTable, MakeColour("Renamed Functions - %rename", colours.rename).."<br>")
65    table.insert(fileTable, MakeColour("Overridden Functions - %override", colours.override).."<br>")
66    table.insert(fileTable, MakeColour("Operator Functions - operator", colours.operator).."<br><br>")
67end
68
69
70-- ----------------------------------------------------------------------------
71-- Make simple HTML tag items
72-- ----------------------------------------------------------------------------
73
74-- color is "RRGGBB" in hex
75function MakeColour(str, color, size)
76    if size then
77        return "<font size=+"..size.." color=#"..color..">"..str.."</font>"
78    end
79
80    return "<font color=#"..color..">"..str.."</font>"
81end
82function MakeBold(str)
83    return "<b>"..str.."</b>"
84end
85function MakeItalic(str)
86    return "<i>"..str.."</i>"
87end
88function MakeLink(link_name, str)
89    --<a href="#papers">papers</a>
90    return "<a href=\"#"..link_name.."\">"..(str or link_name).."</a>"
91end
92function MakeTag(link_name, str)
93    --<a name="papers">Papers</a>
94    return "<a name=\""..link_name.."\">"..(str or link_name).."</a>"
95end
96
97-- convert invalid chars to something valid for use in <a name=...
98function MakeTagName(name)
99    local s = string.lower(name)
100    s = string.gsub(s, "%/", "_")
101    s = string.gsub(s, "% ", "_")
102    s = string.gsub(s, "%(", "_")
103    s = string.gsub(s, "%)", "_")
104    return s
105end
106
107-- replace any chars as necessary before adding our own code
108function MakeHTML(str)
109    local s = string.gsub(str, "&", "&amp;")
110    s = string.gsub(s, ">", "&gt;")
111    s = string.gsub(s, "<", "&lt;")
112    return s
113end
114
115-- ----------------------------------------------------------------------------
116-- Make the HTML footer
117-- ----------------------------------------------------------------------------
118
119function GenerateFooter(fileTable)
120    table.insert(fileTable, "</body>")
121    table.insert(fileTable, "</html>")
122
123    return fileTable
124end
125
126-- ----------------------------------------------------------------------------
127-- Make the Class reference HTML code
128-- ----------------------------------------------------------------------------
129
130function GenerateClassReference(fileTable)
131    local names = {}
132
133    table.insert(fileTable, "<h2>Classes</h2>")
134
135    local allClasses = {}
136
137    if completeClassRefTable then
138        for k, v in pairs(completeClassRefTable) do
139            allClasses[k] = false -- for example ALL wxWidgets classes
140        end
141    end
142    for k, v in pairs(dataTypeTable) do
143        -- hack for special classes
144        if (v.ValueType == "class") or (v.ValueType == "struct") or (v.ValueType == "wx2lua") then
145            allClasses[k]      = true -- the ones we wrap
146        end
147    end
148
149    for k, v in pairs(allClasses) do
150        table.insert(names, k)
151    end
152    table.sort(names)
153
154    --[[
155    <table border="1">
156        <tr>  <td>row 1, cell 1</td>  <td>row 1, cell 2</td> </tr>
157        <tr>  <td>row 2, cell 1</td>  <td>row 2, cell 2</td> </tr>
158    </table>
159    ]]
160
161    if completeClassRefTable then
162
163        table.insert(fileTable, "<table border=\"1\" summary=\"Table showing what wxWidgets C++ classes are wrapped by wxLua\">")
164        table.insert(fileTable, "  <tr><th>Class Name</th> <th>"..completeClassRefColLabel.."</th> <th>Wrapped by wxLua</th> <th>Notes</th></tr>")
165
166        for n = 1, #names do
167            local cname = names[n]
168
169            table.insert(fileTable, "<tr>")
170
171            -- link to class in html file
172            if allClasses[cname] then
173                table.insert(fileTable, "<td>"..MakeLink(cname)) -- optional </td>
174            else
175                table.insert(fileTable, "<td>"..cname)
176            end
177
178            -- in "manual" or complete list of classes
179            if completeClassRefTable and completeClassRefTable[cname] then
180                table.insert(fileTable, "<td align=\"center\" bgcolor=#"..colours.in_manual..">X")
181            else
182                table.insert(fileTable, "<td bgcolor=#"..colours.not_in_manual..">&nbsp;")
183            end
184
185            -- wrapped by wxLua
186            if allClasses[cname] then
187                table.insert(fileTable, "<td align=\"center\" bgcolor=#"..colours.in_wxlua..">X")
188            else
189                table.insert(fileTable, "<td bgcolor=#"..colours.not_in_wxlua..">&nbsp;")
190            end
191
192            -- note about the class
193            if msgForClassInIndex and msgForClassInIndex[cname] then
194                table.insert(fileTable, "<td>"..msgForClassInIndex[cname])
195            else
196                table.insert(fileTable, "<td>&nbsp;")
197            end
198
199            -- table.insert(fileTable, "</tr>") -- optional </tr>
200        end
201
202        table.insert(fileTable, "</table><br>")
203    else
204        for n = 1, #names do
205            table.insert(fileTable, MakeLink(names[n]).."<br>")
206        end
207    end
208
209    table.insert(fileTable, "<br>")
210
211    return fileTable
212end
213
214-- ----------------------------------------------------------------------------
215-- Make the Enum reference HTML code
216-- ----------------------------------------------------------------------------
217
218function GenerateEnumReference(fileTable)
219    local names = {}
220
221    table.insert(fileTable, "<h2>Enums</h2>")
222
223    for k, v in pairs(dataTypeTable) do
224        if v.ValueType == "enum" then
225            table.insert(names, k)
226        end
227    end
228    table.sort(names)
229    for n = 1, #names do
230        table.insert(fileTable, MakeLink(names[n]).."<br>")
231    end
232
233    table.insert(fileTable, "<br>")
234
235    return fileTable
236end
237
238-- ----------------------------------------------------------------------------
239-- Helper functions
240-- ----------------------------------------------------------------------------
241
242local nameChars = {} -- valid chars for C variables for function names
243for n = string.byte("a"), string.byte("z") do nameChars[n] = true end
244for n = string.byte("A"), string.byte("Z") do nameChars[n] = true end
245for n = string.byte("0"), string.byte("9") do nameChars[n] = true end
246nameChars[string.byte("_")] = true
247nameChars[string.byte(":")] = true
248
249function GetPreviousWord(str, pos)
250    local start_pos = 0
251    local end_pos = 0
252    for n = pos, 0, -1 do
253        if not nameChars[string.byte(str, n)] then
254            if end_pos ~= 0 then
255                start_pos = n+1
256                break
257            end
258        elseif end_pos == 0 then
259            end_pos = n
260        end
261    end
262    return string.sub(str, start_pos, end_pos), start_pos
263end
264
265-- if the tag in the txt is before the ifbefore_pos then return true
266function TagIsBefore(txt, tag, ifbefore_pos)
267    local pos = string.find(txt, tag, 1, 1)
268    if pos and ((ifbefore_pos == nil) or (pos < ifbefore_pos)) then
269        return true
270    end
271    return false
272end
273
274
275function GetAllComments(str)
276    local function FindAllStrings(str, find_txt, tbl)
277        local s, e = string.find(str, find_txt, 1, 1)
278        while s do
279            table.insert(tbl, { ["s"] = s, ["e"] = e, ["txt"] = find_txt })
280            s, e = string.find(str, find_txt, e+1, 1)
281        end
282    end
283
284    local t = {}
285    FindAllStrings(str, "//", t)
286    FindAllStrings(str, "/*", t)
287    FindAllStrings(str, "*/", t)
288
289    table.sort(t, function(t1, t2) return t1.s < t2.s end)
290
291    return t
292end
293
294-- ----------------------------------------------------------------------------
295-- Read the .i files and convert them to HTML
296-- ----------------------------------------------------------------------------
297
298function ReadInterfaceFiles(fileTable)
299
300    table.insert(fileTable, "<h2>Interface files</h2>")
301
302    for i = 1, #interface_fileTable do
303        for j = 1, #interface_fileTable[i].files do
304            local s = interface_fileTable[i].file_path..interface_fileTable[i].files[j]
305            table.insert(fileTable, MakeLink(MakeTagName(s), s).."<br>")
306        end
307    end
308
309    local strSp = string.byte(" ")
310
311    for i = 1, #interface_fileTable do
312    for j = 1, #interface_fileTable[i].files do
313
314        table.insert(fileTable, "<br><HR>\n")
315        local filename = interface_fileTable[i].file_path..interface_fileTable[i].files[j]
316        table.insert(fileTable, "<h2>"..MakeTag(MakeTagName(filename), filename).." - Lua table = '"..interface_fileTable[i].namespace.."'</h2>")
317        table.insert(fileTable, "<HR>\n")
318
319        local in_comment  = false
320        local in_class    = false
321        local in_enum     = false
322        local brace_count = 0
323        local in_block    = false
324
325        local line_n = 0
326
327        for line in io.lines(filename) do
328            line_n = line_n + 1
329            local cname = ""
330            local out_line = MakeHTML(line)
331
332            local comment_pos = string.find(line, "//", 1, 1) or 1E6
333
334            -- handle all comments in the order they appear
335            local t = GetAllComments(out_line)
336            for n = 1, #t do
337                if t[n].txt == "//" then
338                    out_line = string.sub(out_line, 1, t[n].s-1)..MakeColour(string.sub(out_line, t[n].s), colours.comment)
339                    break
340                elseif t[n].txt == "/*" then
341                    if in_comment then print("ERROR mismatched /* */ in :", filename, line_n, line) end
342
343                    in_comment = true
344                    out_line = string.sub(out_line, 1, t[n].s-1).."<font color=#"..colours.blkcomment..">"..string.sub(out_line, t[n].s)
345                    t = GetAllComments(out_line)
346                elseif t[n].txt == "*/" then
347                    if not in_comment then print("ERROR mismatched /* */ in :", filename, line_n, line) end
348                    in_comment = false
349                    out_line = string.sub(out_line, 1, t[n].s+1).."</font>"..string.sub(out_line, t[n].s+2)
350                    t = GetAllComments(out_line)
351                end
352            end
353
354            local class_pos, class_pos2 = string.find(line, "class ", 1, 1)
355            local enum_pos,  enum_pos2  = string.find(line, "enum ", 1, 1)
356
357            if not class_pos then
358                class_pos, class_pos2 = string.find(line, "struct ", 1, 1)
359            end
360
361            local brace_open_pos  = string.find(line, "{", 1, 1)
362            local brace_close_pos = string.find(line, "}", 1, 1)
363
364            if (brace_open_pos and (brace_open_pos < comment_pos)) then
365                brace_count = brace_count + 1
366            end
367            if (brace_close_pos and (brace_close_pos < comment_pos)) then
368                brace_count = brace_count - 1
369            end
370
371            if (brace_count < 0) then
372                print("ERROR - brace mismatch ", filename, line_n, "'"..line.."'")
373            end
374
375            if (class_pos and (class_pos < comment_pos)) or
376               (enum_pos  and (enum_pos  < comment_pos)) then
377
378                in_class = (class_pos ~= nil)
379                in_enum  = (enum_pos  ~= nil)
380
381                -- find this class not the base class
382                local colon = string.find(line, ":", 1, 1)
383                local start_pos = 0
384                if class_pos and colon then
385                    cname, start_pos = GetPreviousWord(line, colon-1)
386                elseif comment_pos < 1E6 then
387                    cname, start_pos = GetPreviousWord(line, comment_pos-1)
388                else
389                    cname, start_pos = GetPreviousWord(line, string.len(line))
390                end
391
392                if cname == "enum" then
393                    out_line = string.sub(out_line, 1, start_pos-1)..cname..string.sub(out_line, start_pos+string.len(cname))
394                else
395                    out_line = string.sub(out_line, 1, start_pos-1)..MakeTag(cname)..string.sub(out_line, start_pos+string.len(cname))
396                end
397
398                if class_pos then
399                    out_line = MakeColour(out_line, colours.class, 1)
400                end
401                if enum_pos then
402                    out_line = MakeColour(out_line, colours.enum, 1)
403                end
404
405                out_line = MakeBold(out_line)
406            else
407                -- priortize the colouring so we don't have to check for every single case
408
409                if TagIsBefore(line, "}", comment_pos) and (brace_count == 0) then
410                    --out_line = MakeColour(out_line, colours.class)
411                    --end_block = true
412                    --class_pos = string.find(line, "}", 1, 1)
413                elseif TagIsBefore(line, "%member", comment_pos) then
414                    out_line = MakeColour(out_line, colours.member)
415                elseif TagIsBefore(line, "%rename", comment_pos) then
416                    out_line = MakeColour(out_line, colours.rename)
417                elseif TagIsBefore(line, "%override", 1E6) then
418                    out_line = MakeColour(out_line, colours.override)
419                elseif TagIsBefore(line, "%event", comment_pos) then
420                    out_line = MakeColour(out_line, colours.event)
421                elseif TagIsBefore(line, "#define", comment_pos) then
422                    out_line = MakeColour(out_line, colours.define)
423                elseif TagIsBefore(line, "%function", comment_pos) then
424                    out_line = MakeColour(out_line, colours.func)
425                end
426            end
427
428            local used = {}
429            used[cname] = true
430
431            for w in string.gmatch(line, "([%w_]+)") do
432                if ((string.len(cname) == 0) or (not string.find(w, cname, 1, 1))) and
433                    (not used[w]) and
434                    dataTypeTable[w] and (dataTypeTable[w].ValueType ~= "number") and
435                    (dataTypeTable[w].ValueType ~= "wxtypedef") and (dataTypeTable[w].ValueType ~= "special") then
436
437                    used[w] = true
438
439                    -- replace the classname with a link, but not if it's part of a name
440                    --out_line = string.gsub(out_line, w, MakeLink(w))
441                    local pat = "[ %&%*%(%)%{%}%[%]%+%-%=%<%>%.%-%+%|%/%,]"
442                    -- need extra ending space to find words at end of line
443                    local s, e = string.find(out_line.." ", w..pat, 1)
444                    while s do
445                        local link = MakeLink(w)
446                        out_line = string.sub(out_line, 1, s-1)..link..string.sub(out_line, e)
447                        s, e = string.find(out_line.." ", w..pat, s+string.len(link))
448                    end
449                end
450            end
451
452            -- italicize the %keywords
453            out_line = string.gsub(out_line, "(%%[%w_]+)", function(s) return "<i>"..s.."</i>" end)
454
455--[[
456            -- alternate to blockquote, just force the spaces
457            local start_spaces = 0
458
459            for n = 1, string.len(out_line) do
460                if string.byte(out_line, n) == strSp then
461                    start_spaces = start_spaces + 1
462                else
463                    break
464                end
465            end
466            if start_spaces > 0 then
467                out_line = string.rep("&nbsp;", start_spaces)..string.sub(out_line, start_spaces)
468            end
469]]
470
471            local tail = "<br>"
472
473            local start_block = false
474            local end_block   = false
475
476            if (in_class or in_enum) and (not in_block) and (brace_count > 0) then
477                start_block = true
478            elseif (in_class or in_enum) and in_block and (brace_count == 0) then
479                end_block = true
480            end
481
482            if start_block then
483                tail = "" -- don't add extra space since blockquote already gives a linebreak
484
485                in_block = true
486
487                if in_comment then
488                    out_line = out_line.."</font>"
489                end
490
491                out_line = out_line.."\n<blockquote>"
492
493                -- need to restart font color after blockquote for "tidy"
494                if enum_pos then
495                    out_line = out_line.."<font color=#"..colours.enum..">"
496                end
497                -- restart the block comment after blockquote, overrides enum colour
498                if in_comment then
499                    out_line = out_line.."<font color=#"..colours.blkcomment..">"
500                end
501            elseif end_block then
502                -- need to restart font color after blockquote for "tidy"
503
504                in_block = false
505
506                if in_class then
507                    in_class = false
508                    out_line = "</blockquote>"..out_line -- MakeColour(out_line, colours.class)
509                end
510                if in_enum then
511                    in_enum = false
512                    out_line = "</font>\n</blockquote>"..out_line --MakeColour(out_line, colours.enum)
513                end
514                -- restart the block comment after blockquote
515                if in_comment then
516                    out_line = "</font>"..out_line.."<font color=#"..colours.blkcomment..">"
517                end
518
519            end
520
521            table.insert(fileTable, out_line..tail)
522        end
523    end
524    end
525end
526
527-- ----------------------------------------------------------------------------
528-- Load a file of the classes listed in the wxWidgets manual
529-- ----------------------------------------------------------------------------
530
531function LoadCompleteClassRef(filePath)
532    for line in io.lines(filePath) do
533        -- only create this if necessary
534        if not completeClassRefTable then completeClassRefTable = {} end
535
536        for w in string.gmatch(line, "([%w_]+)") do -- strip spaces if any
537            completeClassRefTable[w] = true
538        end
539    end
540end
541
542-- ---------------------------------------------------------------------------
543-- Do the contents of the file match the strings in the fileData table?
544--   the table may contain any number of \n per index
545--   returns true for a match or false if not
546-- ---------------------------------------------------------------------------
547function FileDataIsTableData(filename, fileData)
548    local file_handle = io.open(filename)
549    if not file_handle then return false end -- ok if it doesn't exist
550
551    local f = file_handle:read("*a")
552    local is_same = (f == table.concat(fileData, "\n"))
553    io.close(file_handle)
554    return is_same
555end
556
557-- ---------------------------------------------------------------------------
558-- Write the contents of the table fileData (indexes 1.. are line numbers)
559--  to the filename, but only write to the file if FileDataIsTableData returns
560--  false. If overwrite_always is true then always overwrite the file.
561--  returns true if the file was overwritten
562-- ---------------------------------------------------------------------------
563function WriteTableToFile(filename, fileData, overwrite_always)
564    assert(filename and fileData, "Invalid filename or fileData in WriteTableToFile")
565
566    if (not overwrite_always) and FileDataIsTableData(filename, fileData) then
567        print("No changes to file : '"..filename.."'")
568        return false
569    end
570
571    print("Updating file : '"..filename.."'")
572
573    local outfile = io.open(filename, "w+")
574    if not outfile then
575        print("Unable to open file for writing '"..filename.."'.")
576        return
577    end
578
579    outfile:write(table.concat(fileData, "\n"))
580
581    outfile:flush()
582    outfile:close()
583    return true
584end
585
586-- ----------------------------------------------------------------------------
587-- main()
588-- ----------------------------------------------------------------------------
589
590function main()
591      -- load rules file
592    if not rulesFilename then
593        print("Warning: No rules filename set!")
594        rulesFilename = ""
595    end
596
597    local rules = loadfile("./"..rulesFilename)
598    if rules then
599        rules()
600        print("Loaded rules file: "..rulesFilename)
601    else
602        print("ERROR : unable to load rules file: "..rulesFilename)
603        print("This could mean that either the file cannot be found or there is an error in it.")
604        print("The rules file should be valid lua code, try running it with lua directly.")
605    end
606
607    for n = 1, #interface_fileTable do
608        local datatypes_filename = interface_fileTable[n].file_path..interface_fileTable[n].datatypes_filename
609        local datatypes_file = loadfile(datatypes_filename)
610        if datatypes_file then
611            datatypes_file()
612            print("Loaded data types file: "..datatypes_filename)
613        else
614            print("WARNING: unable to load data types file: "..datatypes_filename)
615        end
616    end
617
618    dataTypeTable["wxString"].ValueType = "class" -- FIXME hack for wxString DefType as "special"
619
620    if completeClassRefFileTable then
621        for n = 1, #completeClassRefFileTable do
622            LoadCompleteClassRef(completeClassRefFileTable[n])
623            print("Loaded complete class reference : "..completeClassRefFileTable[n])
624        end
625    end
626
627    fileTable = { htmlHeader }
628    GenerateClassReference(fileTable)
629    table.insert(fileTable, "<HR>")
630    GenerateEnumReference(fileTable)
631    table.insert(fileTable, "<HR>")
632    GenerateTestColours(fileTable)
633    table.insert(fileTable, "<HR>")
634    ReadInterfaceFiles(fileTable)
635    GenerateFooter(fileTable)
636
637
638    WriteTableToFile(output_filename , fileTable)
639    --for n = 1, #fileTable do print(fileTable[n]) end
640
641end
642
643main()
644