| rev | 
   line source | 
| 
jbe/bsw@0
 | 
     1 #!/usr/bin/env lua
 | 
| 
jbe/bsw@0
 | 
     2 
 | 
| 
jbe/bsw@0
 | 
     3 local args = {...}
 | 
| 
jbe/bsw@0
 | 
     4 
 | 
| 
jbe/bsw@0
 | 
     5 if not args[1] or string.match(args[1], "^%-") then
 | 
| 
jbe/bsw@0
 | 
     6   print("Usage: autodoc.lua srcdir/ > documentation.html")
 | 
| 
jbe/bsw@0
 | 
     7   os.exit(1)
 | 
| 
jbe/bsw@0
 | 
     8 end
 | 
| 
jbe/bsw@0
 | 
     9 
 | 
| 
jbe/bsw@0
 | 
    10 local entries = {}
 | 
| 
jbe/bsw@0
 | 
    11 
 | 
| 
jbe/bsw@0
 | 
    12 for idx, srcdir in ipairs(args) do
 | 
| 
jbe/bsw@0
 | 
    13   local find_proc = io.popen('find "' .. srcdir .. '" -name \\*.lua', "r")
 | 
| 
jbe/bsw@0
 | 
    14   for filename in find_proc:lines() do
 | 
| 
jbe/bsw@0
 | 
    15     local synopsis, comment, source
 | 
| 
jbe/bsw@0
 | 
    16     local mode
 | 
| 
jbe/bsw@0
 | 
    17     local function reset()
 | 
| 
jbe/bsw@0
 | 
    18       synopsis, comment, source = {}, {}, {}
 | 
| 
jbe/bsw@0
 | 
    19       mode = "idle"
 | 
| 
jbe/bsw@0
 | 
    20     end
 | 
| 
jbe/bsw@0
 | 
    21     reset()
 | 
| 
jbe/bsw@0
 | 
    22     local function strip(tbl)
 | 
| 
jbe/bsw@0
 | 
    23       while true do
 | 
| 
jbe/bsw@0
 | 
    24         local line = tbl[#tbl]
 | 
| 
jbe/bsw@0
 | 
    25         if line and string.find(line, "^%s*$") then
 | 
| 
jbe/bsw@0
 | 
    26           tbl[#tbl] = nil
 | 
| 
jbe/bsw@0
 | 
    27         else
 | 
| 
jbe/bsw@0
 | 
    28           break
 | 
| 
jbe/bsw@0
 | 
    29         end
 | 
| 
jbe/bsw@0
 | 
    30       end
 | 
| 
jbe/bsw@0
 | 
    31       if #tbl > 0 then
 | 
| 
jbe/bsw@0
 | 
    32         local min_indent = math.huge
 | 
| 
jbe/bsw@0
 | 
    33         for idx, line in ipairs(tbl) do
 | 
| 
jbe/bsw@0
 | 
    34           local spaces = string.match(line, "^(%s*)")
 | 
| 
jbe/bsw@0
 | 
    35           if min_indent > #spaces then
 | 
| 
jbe/bsw@0
 | 
    36             min_indent = #spaces
 | 
| 
jbe/bsw@0
 | 
    37           end
 | 
| 
jbe/bsw@0
 | 
    38         end
 | 
| 
jbe/bsw@0
 | 
    39         local pattern_parts = { "^" }
 | 
| 
jbe/bsw@0
 | 
    40         for i = 1, min_indent do
 | 
| 
jbe/bsw@0
 | 
    41           pattern_parts[#pattern_parts+1] = "%s"
 | 
| 
jbe/bsw@0
 | 
    42         end
 | 
| 
jbe/bsw@0
 | 
    43         pattern_parts[#pattern_parts+1] = "(.-)%s*$"
 | 
| 
jbe/bsw@0
 | 
    44         local pattern = table.concat(pattern_parts)
 | 
| 
jbe/bsw@0
 | 
    45         for idx, line in ipairs(tbl) do
 | 
| 
jbe/bsw@0
 | 
    46           tbl[idx] = string.match(line, pattern)
 | 
| 
jbe/bsw@0
 | 
    47         end
 | 
| 
jbe/bsw@0
 | 
    48       end
 | 
| 
jbe/bsw@0
 | 
    49     end
 | 
| 
jbe/bsw@0
 | 
    50     local function entry_done()
 | 
| 
jbe/bsw@0
 | 
    51       if #synopsis > 0 then
 | 
| 
jbe/bsw@0
 | 
    52         strip(synopsis)
 | 
| 
jbe/bsw@0
 | 
    53         strip(comment)
 | 
| 
jbe/bsw@0
 | 
    54         strip(source)
 | 
| 
jbe/bsw@0
 | 
    55         local stripped_synopsis = {}
 | 
| 
jbe/bsw@0
 | 
    56         for idx, line in ipairs(synopsis) do
 | 
| 
jbe/bsw@0
 | 
    57           local stripped_line = string.match(line, "^(.-)%-%-") or line
 | 
| 
jbe/bsw@0
 | 
    58           stripped_line = string.match(stripped_line, "^(.-)%s*$")
 | 
| 
jbe/bsw@0
 | 
    59           stripped_synopsis[#stripped_synopsis+1] = stripped_line
 | 
| 
jbe/bsw@0
 | 
    60         end
 | 
| 
jbe/bsw@0
 | 
    61         local concatted_synopsis = string.gsub(
 | 
| 
jbe/bsw@0
 | 
    62           table.concat(stripped_synopsis, " "), "[%s]+", " "
 | 
| 
jbe/bsw@0
 | 
    63         )
 | 
| 
jbe/bsw@0
 | 
    64         local func_call = string.match(
 | 
| 
jbe/bsw@0
 | 
    65           concatted_synopsis, "^[A-Za-z0-9_, ]+= ?(.-) ?$"
 | 
| 
jbe/bsw@0
 | 
    66         )
 | 
| 
jbe/bsw@0
 | 
    67         if not func_call then
 | 
| 
jbe/bsw@0
 | 
    68           func_call = string.match(
 | 
| 
jbe/bsw@0
 | 
    69             concatted_synopsis,
 | 
| 
jbe/bsw@0
 | 
    70             "^ ?for[A-Za-z0-9_, ]+in (.-) ? do[ %.]+end ?$"
 | 
| 
jbe/bsw@0
 | 
    71           )
 | 
| 
jbe/bsw@0
 | 
    72         end
 | 
| 
jbe/bsw@0
 | 
    73         if not func_call then
 | 
| 
jbe/bsw@0
 | 
    74           func_call = string.match(concatted_synopsis, "^ ?(.-) ?$")
 | 
| 
jbe/bsw@0
 | 
    75         end
 | 
| 
jbe/bsw@0
 | 
    76         func_call = string.gsub(
 | 
| 
jbe/bsw@0
 | 
    77           func_call,
 | 
| 
jbe/bsw@0
 | 
    78           "^([^({]*)[({].*[,;].*[,;].*[,;].*[)}]$",
 | 
| 
jbe/bsw@0
 | 
    79           function(base)
 | 
| 
jbe/bsw@0
 | 
    80             return base .. "{ ... }"
 | 
| 
jbe/bsw@0
 | 
    81           end
 | 
| 
jbe/bsw@0
 | 
    82         )
 | 
| 
jbe/bsw@0
 | 
    83         if entries[func_call] then
 | 
| 
jbe/bsw@0
 | 
    84           error("Multiple occurrences of: " .. func_call)
 | 
| 
jbe/bsw@0
 | 
    85         end
 | 
| 
jbe/bsw@0
 | 
    86         entries[func_call] = {
 | 
| 
jbe/bsw@0
 | 
    87           func_call   = func_call,
 | 
| 
jbe/bsw@0
 | 
    88           synopsis    = synopsis,
 | 
| 
jbe/bsw@0
 | 
    89           comment     = comment,
 | 
| 
jbe/bsw@0
 | 
    90           source      = source
 | 
| 
jbe/bsw@0
 | 
    91         }
 | 
| 
jbe/bsw@0
 | 
    92       end
 | 
| 
jbe/bsw@0
 | 
    93       reset()
 | 
| 
jbe/bsw@0
 | 
    94     end
 | 
| 
jbe/bsw@0
 | 
    95     for line in io.lines(filename, "r") do
 | 
| 
jbe/bsw@0
 | 
    96       local function add_to(tbl)
 | 
| 
jbe/bsw@0
 | 
    97         if #tbl > 0 or not string.match(line, "^%s*$") then
 | 
| 
jbe/bsw@0
 | 
    98           tbl[#tbl+1] = line
 | 
| 
jbe/bsw@0
 | 
    99         end
 | 
| 
jbe/bsw@0
 | 
   100       end
 | 
| 
jbe/bsw@0
 | 
   101       if mode == "idle" then
 | 
| 
jbe/bsw@0
 | 
   102         if string.find(line, "^%s*%-%-%[%[%-%-%s*$") then
 | 
| 
jbe/bsw@0
 | 
   103           mode = "synopsis"
 | 
| 
jbe/bsw@0
 | 
   104         end
 | 
| 
jbe/bsw@0
 | 
   105       elseif mode == "synopsis" then
 | 
| 
jbe/bsw@0
 | 
   106         if string.find(line, "^%s*$") and #synopsis > 0 then
 | 
| 
jbe/bsw@0
 | 
   107           mode = "comment"
 | 
| 
jbe/bsw@0
 | 
   108         elseif string.find(line, "^%s*%-%-]]%-%-%s*$") then
 | 
| 
jbe/bsw@0
 | 
   109           mode = "source"
 | 
| 
jbe/bsw@0
 | 
   110         else
 | 
| 
jbe/bsw@0
 | 
   111           add_to(synopsis)
 | 
| 
jbe/bsw@0
 | 
   112         end
 | 
| 
jbe/bsw@0
 | 
   113       elseif mode == "comment" then
 | 
| 
jbe/bsw@0
 | 
   114         if string.find(line, "^%s*%-%-]]%-%-%s*$") then
 | 
| 
jbe/bsw@0
 | 
   115           mode = "source"
 | 
| 
jbe/bsw@0
 | 
   116         else
 | 
| 
jbe/bsw@0
 | 
   117           add_to(comment)
 | 
| 
jbe/bsw@0
 | 
   118         end
 | 
| 
jbe/bsw@0
 | 
   119       elseif mode == "source" then
 | 
| 
jbe/bsw@0
 | 
   120         if string.find(line, "^%s*%-%-//%-%-%s*$") then
 | 
| 
jbe/bsw@0
 | 
   121           entry_done()
 | 
| 
jbe/bsw@0
 | 
   122         else
 | 
| 
jbe/bsw@0
 | 
   123           add_to(source)
 | 
| 
jbe/bsw@0
 | 
   124         end
 | 
| 
jbe/bsw@0
 | 
   125       end
 | 
| 
jbe/bsw@0
 | 
   126     end
 | 
| 
jbe/bsw@0
 | 
   127     entry_done()
 | 
| 
jbe/bsw@0
 | 
   128   end
 | 
| 
jbe/bsw@0
 | 
   129   find_proc:close()
 | 
| 
jbe/bsw@0
 | 
   130 end
 | 
| 
jbe/bsw@0
 | 
   131 
 | 
| 
jbe/bsw@0
 | 
   132 
 | 
| 
jbe/bsw@0
 | 
   133 function output(...)
 | 
| 
jbe/bsw@0
 | 
   134   return io.stdout:write(...)
 | 
| 
jbe/bsw@0
 | 
   135 end
 | 
| 
jbe/bsw@0
 | 
   136 
 | 
| 
jbe/bsw@0
 | 
   137 function encode(text)
 | 
| 
jbe/bsw@0
 | 
   138   return (
 | 
| 
jbe/bsw@0
 | 
   139     string.gsub(
 | 
| 
jbe/bsw@0
 | 
   140       text, '[<>&"]',
 | 
| 
jbe/bsw@0
 | 
   141       function(char)
 | 
| 
jbe/bsw@0
 | 
   142         if char == '<' then
 | 
| 
jbe/bsw@0
 | 
   143           return "<"
 | 
| 
jbe/bsw@0
 | 
   144         elseif char == '>' then
 | 
| 
jbe/bsw@0
 | 
   145           return ">"
 | 
| 
jbe/bsw@0
 | 
   146         elseif char == '&' then
 | 
| 
jbe/bsw@0
 | 
   147           return "&"
 | 
| 
jbe/bsw@0
 | 
   148         elseif char == '"' then
 | 
| 
jbe/bsw@0
 | 
   149           return """
 | 
| 
jbe/bsw@0
 | 
   150         end
 | 
| 
jbe/bsw@0
 | 
   151       end
 | 
| 
jbe/bsw@0
 | 
   152     )
 | 
| 
jbe/bsw@0
 | 
   153   )
 | 
| 
jbe/bsw@0
 | 
   154 end
 | 
| 
jbe/bsw@0
 | 
   155 
 | 
| 
jbe/bsw@0
 | 
   156 function output_lines(tbl)
 | 
| 
jbe/bsw@0
 | 
   157   for idx, line in ipairs(tbl) do
 | 
| 
jbe/bsw@0
 | 
   158     if idx == 1 then
 | 
| 
jbe/bsw@0
 | 
   159       output('<pre>')
 | 
| 
jbe/bsw@0
 | 
   160     end
 | 
| 
jbe/bsw@0
 | 
   161     local command, comment = string.match(line, "^(.-)(%-%-.*)$")
 | 
| 
jbe/bsw@0
 | 
   162     if command then
 | 
| 
jbe/bsw@0
 | 
   163       output(
 | 
| 
jbe/bsw@0
 | 
   164         encode(command),
 | 
| 
jbe/bsw@0
 | 
   165         '<span class="autodoc_comment_tail">', encode(comment), '</span>'
 | 
| 
jbe/bsw@0
 | 
   166       )
 | 
| 
jbe/bsw@0
 | 
   167     else
 | 
| 
jbe/bsw@0
 | 
   168       output(encode(line))
 | 
| 
jbe/bsw@0
 | 
   169     end
 | 
| 
jbe/bsw@0
 | 
   170     if idx == #tbl then
 | 
| 
jbe/bsw@0
 | 
   171       output('</pre>')
 | 
| 
jbe/bsw@0
 | 
   172     end
 | 
| 
jbe/bsw@0
 | 
   173     output('\n')
 | 
| 
jbe/bsw@0
 | 
   174   end
 | 
| 
jbe/bsw@0
 | 
   175 end
 | 
| 
jbe/bsw@0
 | 
   176 
 | 
| 
jbe/bsw@0
 | 
   177 keys = {}
 | 
| 
jbe/bsw@0
 | 
   178 for key in pairs(entries) do
 | 
| 
jbe/bsw@0
 | 
   179   keys[#keys+1] = key
 | 
| 
jbe/bsw@0
 | 
   180 end
 | 
| 
jbe/bsw@0
 | 
   181 table.sort(keys)
 | 
| 
jbe/bsw@0
 | 
   182 for idx, key in ipairs(keys) do
 | 
| 
jbe/bsw@0
 | 
   183   local entry = entries[key]
 | 
| 
jbe/bsw@0
 | 
   184   output('<li class="autodoc_entry">\n')
 | 
| 
jbe/bsw@0
 | 
   185   output(
 | 
| 
jbe/bsw@0
 | 
   186     '  <div class="short_synopsis"',
 | 
| 
jbe/bsw@0
 | 
   187     ' onclick="document.getElementById(\'autodoc_details_',
 | 
| 
jbe/bsw@0
 | 
   188     idx,
 | 
| 
jbe/bsw@0
 | 
   189     '\').style.display = document.getElementById(\'autodoc_details_',
 | 
| 
jbe/bsw@0
 | 
   190     idx,
 | 
| 
jbe/bsw@0
 | 
   191     '\').style.display ? \'\' : \'none\';">\n'
 | 
| 
jbe/bsw@0
 | 
   192   )
 | 
| 
jbe/bsw@0
 | 
   193   output('    ', encode(entry.func_call), '\n')
 | 
| 
jbe/bsw@0
 | 
   194   output('  </div>\n')
 | 
| 
jbe/bsw@0
 | 
   195   output(
 | 
| 
jbe/bsw@0
 | 
   196     '  <div id="autodoc_details_',
 | 
| 
jbe/bsw@0
 | 
   197     idx,
 | 
| 
jbe/bsw@0
 | 
   198     '" class="autodoc_details" style="display: none;">\n'
 | 
| 
jbe/bsw@0
 | 
   199   )
 | 
| 
jbe/bsw@0
 | 
   200   output('    <div class="autodoc_synopsis">\n')
 | 
| 
jbe/bsw@0
 | 
   201   output_lines(entry.synopsis)
 | 
| 
jbe/bsw@0
 | 
   202   output('    </div>\n')
 | 
| 
jbe/bsw@0
 | 
   203   output('    <div class="autodoc_comment">')
 | 
| 
jbe/bsw@0
 | 
   204   for idx, line in ipairs(entry.comment) do
 | 
| 
jbe/bsw@0
 | 
   205     output(encode(line))
 | 
| 
jbe/bsw@0
 | 
   206     if idx < #entry.comment then
 | 
| 
jbe/bsw@0
 | 
   207       output('<br/>')
 | 
| 
jbe/bsw@0
 | 
   208     end
 | 
| 
jbe/bsw@0
 | 
   209   end
 | 
| 
jbe/bsw@0
 | 
   210   output('    </div>\n')
 | 
| 
jbe/bsw@0
 | 
   211   output('    <div class="autodoc_source">\n')
 | 
| 
jbe/bsw@0
 | 
   212   output_lines(entry.source)
 | 
| 
jbe/bsw@0
 | 
   213   output('    </div>\n')
 | 
| 
jbe/bsw@0
 | 
   214   output('  </div>\n')
 | 
| 
jbe/bsw@0
 | 
   215   output('</li>\n')
 | 
| 
jbe/bsw@0
 | 
   216 end |