diff options
Diffstat (limited to 'src/genhelp.lua')
-rw-r--r-- | src/genhelp.lua | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/genhelp.lua b/src/genhelp.lua new file mode 100644 index 0000000..06a3590 --- /dev/null +++ b/src/genhelp.lua @@ -0,0 +1,300 @@ +#!/usr/bin/lua5.3 + +--[[ +Utility to convert SCDOC manpages to apk-tools help messages + +General: + - Wrangle *apk-applet*(SECTION) links + - Uppercase _underlined_ things as they are "keywords" + - Other format specs like ** to be removed + - For options text, the first sentence (within the first line) is taken as the help text + +Main page: apk.8.scd + - SYNOPSIS + - COMMANDS has ## header with a table for commands list + - GLOBAL OPTIONS and COMMIT OPTIONS for option group help + - NOTES + +Applet pages: apk-*.8.scd + - Take usage from SYNOPSIS, can have multiple lines like apk-version(8) + - Take DESCRIPTION, take first paragraph, rewrap, and put as section in applet specific help + - From OPTIONS take each option and it's first sentence (within the first line) +--]] + +local function splittokens(s) + local res = {} + for w in s:gmatch("%S+") do + res[#res+1] = w + end + return res +end + +local function textwrap(text, linewidth) + local spaceleft = linewidth + local res = {} + local line = {} + + for _, word in ipairs(splittokens(text)) do + if #word + 1 > spaceleft then + table.insert(res, table.concat(line, ' ')) + line = { word } + spaceleft = linewidth - #word + else + table.insert(line, word) + spaceleft = spaceleft - (#word + 1) + end + end + table.insert(res, table.concat(line, ' ')) + return res +end + +local function upperfirst(s) + return s:sub(1,1):upper() .. s:sub(2):lower() +end + +scdoc = { + usage_prefix = "usage: ", +} +scdoc.__index = scdoc + +function scdoc:nop(ln) + --print(self.section, ln) +end + +function scdoc:SYNOPSIS_text(ln) + table.insert(self.usage, self.usage_prefix .. ln) + self.usage_prefix = " or: " +end + +function scdoc:COMMANDS_text(ln) + local ch = ln:sub(1,1) + local a, b = ln:match("^([[|:<]*)%s+(.+)") + if ch == '|' then + self.cur_cmd = { b, "" } + table.insert(self.commands, self.cur_cmd) + elseif ch == ':' and self.cur_cmd then + self.cur_cmd[2] = b + self.cur_cmd = nil + end +end + +function scdoc:COMMANDS_subsection(n) + n = n:sub(1,1) .. n:sub(2):lower() + table.insert(self.commands, n) +end + +function scdoc:DESCRIPTION_text(ln) + table.insert(self.description, ln) +end + +function scdoc:DESCRIPTION_paragraph() + if #self.description > 0 then + self.section_text = self.nop + end +end + +function scdoc:OPTIONS_text(ln) + local ch = ln:sub(1,1) + if ch == '-' then + self.cur_opt = { ln, {} } + table.insert(self.options, self.cur_opt) + elseif ch == '\t' then + table.insert(self.cur_opt[2], ln:sub(2)) + end +end + +function scdoc:NOTES_text(ln) + table.insert(self.notes, ln) +end + +function scdoc:parse_default(ln) + if #ln == 0 then + return (self[self.section .. "_paragraph"] or self.nop)(self) + end + + s, n = ln:match("^(#*) (.*)") + if s and n then + if #s == 1 then + local optgroup, opts = n:match("^(%S*) ?(OPTIONS)$") + if opts then + if #optgroup == 0 then optgroup = self.applet end + self.options = { name = optgroup } + table.insert(self.optgroup, self.options) + n = opts + end + + self.section = n + self.section_text = self[n .. "_text"] or self.nop + self.subsection = nil + else + self.subsection = n + local f = self[self.section.."_subsection"] + if f then f(self, n) end + end + return + end + + -- Handle formatting + ln = ln:gsub("apk%-(%S+)%(%d%)", "%1") + ln = ln:gsub("([^\\])%*(.-[^\\])%*", "%1%2") + ln = ln:gsub("^%*(.-[^\\])%*", "%1") + ln = ln:gsub("([^\\])_(.-[^\\])_", function(a,s) return a..s:upper() end) + ln = ln:gsub("^_(.-[^\\])_", function(a,s) return a..s:upper() end) + ln = ln:gsub("\\", "") + + self:section_text(ln) +end + +function scdoc:parse_header(ln) + self.manpage, self.mansection = ln:match("^(%S*)%((%d*)%)") + if self.manpage:find("^apk%-") then + self.applet = self.manpage:sub(5):lower() + else + self.applet = self.manpage:upper() + end + self.parser = self.parse_default + self.section_text = self.nop +end + +function scdoc:parse(fn) + self.parser = self.parse_header + for l in io.lines(fn) do + self:parser(l) + end +end + +function scdoc:render_options(out, options) + local width = self.width + local nindent = 24 + + table.insert(out, ("%s options:\n"):format(upperfirst(options.name))) + for _, opt in ipairs(options) do + local indent = (" "):rep(nindent) + k, v = opt[1], opt[2] + if #k > nindent - 4 then + table.insert(out, (" %s\n"):format(k, "", v)) + table.insert(out, indent) + else + local fmt = (" %%-%ds "):format(nindent - 4) + table.insert(out, fmt:format(k, v)) + end + + v = table.concat(v, " ") + local i = v:find("%.%s") + if not i then i = v:find("%.$") end + if i then v = v:sub(1, i-1) end + v = textwrap(v, width - nindent - 1) + + table.insert(out, v[1]) + table.insert(out, "\n") + for i = 2, #v do + table.insert(out, indent) + table.insert(out, v[i]) + table.insert(out, "\n") + end + end +end + +function scdoc:render_optgroups(out) + for _, options in ipairs(self.optgroup) do + if #options > 0 then + table.insert(out, options.name .. "\x00") + self:render_options(out, options) + if options.name == self.applet then + self:render_footer(out) + end + table.insert(out, "\x00") + end + end +end + +function scdoc:render_footer(out) + table.insert(out, ("\nFor more information: man %s %s\n"):format(self.mansection, self.manpage)) +end + +function scdoc:render(out) + local width = self.width + + if not self.applet then return end + table.insert(out, self.applet .. "\x00") + table.insert(out, table.concat(self.usage, "\n")) + table.insert(out, "\n") + if #self.commands > 0 then + for _, cmd in ipairs(self.commands) do + if type(cmd) == "string" then + table.insert(out, "\n" .. cmd .. ":\n") + else + table.insert(out, (" %-10s %s\n"):format(cmd[1], cmd[2])) + end + end + elseif #self.description > 0 then + table.insert(out, "\nDescription:\n") + for _, ln in ipairs(textwrap(table.concat(self.description, ' '), width - 2)) do + table.insert(out, (" %s\n"):format(ln)) + end + end + if #self.notes > 0 then + table.insert(out, "\n") + table.insert(out, table.concat(self.notes, "\n")) + if self.manpage == "apk" then self:render_footer(out) + else table.insert(out, "\n") end + end + table.insert(out, "\x00") +end + +local function compress(data) + local zlib = require 'zlib' + local level = 9 + if type(zlib.version()) == "string" then + -- lua-lzlib interface + return zlib.compress(data, level) + else + -- lua-zlib interface + return zlib.deflate(level)(data, "finish") + end +end + +local function dump_compressed_vars(name, data, header) + local width = 16 + local cout = compress(data) + if header then print(header) end + print(("static const unsigned int uncompressed_%s_size = %d;"):format(name, #data)) + print(("static const unsigned char compressed_%s[] = { /* %d bytes */"):format(name, #cout)) + for i = 1, #cout do + if i % width == 1 then + io.write("\t") + end + --print(cout:byte(i)) + io.write(("0x%02x,"):format(cout:byte(i))) + if i % width == 0 or i == #cout then + io.write("\n") + end + end + print("};") +end + +local f = {} +for _, fn in ipairs(arg) do + doc = setmetatable({ + width = 78, + section = "HEADER", + usage = {}, + description = {}, + commands = {}, + notes = {}, + optgroup = {}, + }, scdoc) + doc:parse(fn) + table.insert(f, doc) +end +table.sort(f, function(a, b) return a.applet < b.applet end) + +local out = {} +for _, doc in ipairs(f) do doc:render(out) end +for _, doc in ipairs(f) do doc:render_optgroups(out) end + +table.insert(out, "\x00") + +local help = table.concat(out) +--io.stderr:write(help) +dump_compressed_vars("help", help, "/* Automatically generated by genhelp.lua. Do not modify. */") |