summaryrefslogtreecommitdiff
path: root/src/genhelp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/genhelp.lua')
-rw-r--r--src/genhelp.lua300
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. */")