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