add script to autogenerate help from man pages

This creates main help like:
--
usage: apk [<OPTIONS>...] COMMAND [<ARGUMENTS>...]

Package installation and removal:
  add        Add packages to WORLD and commit changes
  del        Remove packages from WORLD and commit changes

System maintenance:
  fix        Check WORLD against the system and ensure consistency
  update     Update repository indexes
  upgrade    Install upgrades available from repositories
  cache      Commands related to the management of an offline package cache

Querying package information:
  info       Give detailed information about packages or repositories
  list       List packages matching a pattern or other criteria
  dot        Generate graphviz graphs
  policy     Show repository policy for packages

Repository maintenance:
  index      Create repository index file from packages
  fetch      Download packages from global repositories to a local directory
  manifest   Show checksums of package contents
  verify     Verify package integrity and signature

Miscellaneous:
  audit      Audit directories for changes
  stats      Show statistics about repositories and installations
  version    Compare package versions or perform tests on version strings

This apk has coffee making abilities.
--

And applet specific help like:
--
usage: apk add [<OPTIONS>...] PACKAGES...

Description:
  apk add adds the requested packages to WORLD and installs (or upgrades)
  them if not already present, ensuring all dependencies are met.

Options:
  --initdb         Initialize a new package database
  -l, --latest     Disables normal heuristics for choosing which repository to install a
  -u, --upgrade    When adding packages which are already installed, upgrade them rather
  -t, --virtual NAME
                   Instead of adding the specified packages to WORLD, create a new
  --no-chown       Do not change file owner or group
--
cute-signatures
Timo Teräs 2020-04-24 11:49:14 +03:00
parent d61c009f7a
commit 5258b484bf
24 changed files with 385 additions and 111 deletions

View File

@ -6,17 +6,18 @@ test:alpine:
stage: test
script:
- apk update
- apk add make gcc git musl-dev openssl-dev linux-headers zlib-dev lua5.3-dev
- apk add make gcc git musl-dev openssl-dev linux-headers zlib-dev lua5.3-dev lua5.3-lzlib
- make -j$(nproc) check
tags:
- docker-alpine
- x86_64
test:debian:
image: debian
stage: test
script:
- apt-get update
- apt-get install -y make gcc git libssl-dev zlib1g-dev lua5.3-dev sudo
- apt-get install -y make gcc git libssl-dev zlib1g-dev lua5.3-dev lua5.2 lua-zlib-dev sudo
- unlink /bin/sh
- ln -s /bin/bash /bin/sh
- make -j$(nproc) check

View File

@ -5,7 +5,7 @@ CFLAGS_common.o += -DCA_CERT_FILE=\"$(CONFDIR)/ca.pem\" -DCA_CRL_FILE=\"$(CONFD
CFLAGS_common.o += -DCLIENT_CERT_FILE=\"$(CONFDIR)/cert.pem\" -DCLIENT_KEY_FILE=\"$(CONFDIR)/cert.key\"
quiet_cmd_generr = GENERR $@
cmd_generr = $(obj)/errlist.sh $(basename $(<F))_errlist $(shell echo $(basename $(<F)) | tr a-z A-Z) $< > $@
cmd_generr = $(src)/errlist.sh $(basename $(<F))_errlist $(shell echo $(basename $(<F)) | tr a-z A-Z) $< > $@
$(obj)/%err.h: $(obj)/%.errors
@$(call echo-cmd,generr) $(cmd_generr); $(cmd_generr)
$(obj)/%err.h: $(src)/%.errors
@$(call echo-cmd,generr) $(cmd_generr)

View File

@ -1,6 +1,8 @@
PKG_CONFIG ?= pkg-config
LUAAPK ?= yes
LUA ?= $(firstword $(wildcard /usr/bin/lua5.3 /usr/bin/lua5.2))
OPENSSL_CFLAGS := $(shell $(PKG_CONFIG) --cflags openssl)
OPENSSL_LIBS := $(shell $(PKG_CONFIG) --libs openssl)
@ -52,7 +54,7 @@ endif
# Apk utility
progs-y += apk
apk-objs := apk.o \
apk-objs := apk.o help.o \
app_add.o app_del.o app_fix.o app_update.o app_upgrade.o \
app_info.o app_list.o app_search.o app_manifest.o \
app_policy.o app_stats.o \
@ -82,6 +84,17 @@ LIBS := -Wl,--as-needed \
$(OPENSSL_LIBS) $(ZLIB_LIBS) \
-Wl,--no-as-needed
# Help generation
quiet_cmd_genhelp = GENHELP $@
cmd_genhelp = $(LUA) $(src)/genhelp.lua $(filter %.scd, $^) > $@
$(obj)/help.h: $(src)/genhelp.lua $(wildcard doc/apk*.8.scd)
@$(call echo-cmd,genhelp) $(cmd_genhelp)
CFLAGS_help.o := -I$(obj)
generate-y += help.h
# Test build
ifeq ($(TEST),y)
progs-y += apk-test

View File

@ -192,8 +192,8 @@ static int option_parse_global(void *ctx, struct apk_db_options *dbopts, int opt
static const struct apk_option options_global[] = {
{ 'h', "help" },
{ 'p', "root", required_argument, "DIR" },
{ 'X', "repository", required_argument, "REPO" },
{ 'p', "root", required_argument },
{ 'X', "repository", required_argument },
{ 'q', "quiet" },
{ 'v', "verbose" },
{ 'i', "interactive" },
@ -207,23 +207,23 @@ static const struct apk_option options_global[] = {
{ 0x123, "force-refresh" },
{ 'U', "update-cache" },
{ 0x101, "progress" },
{ 0x10f, "progress-fd", required_argument, "FD" },
{ 0x10f, "progress-fd", required_argument },
{ 0x110, "no-progress" },
{ 0x106, "purge" },
{ 0x103, "allow-untrusted" },
{ 0x105, "wait", required_argument, "TIME" },
{ 0x107, "keys-dir", required_argument, "KEYSDIR" },
{ 0x108, "repositories-file", required_argument, "REPOFILE" },
{ 0x105, "wait", required_argument },
{ 0x107, "keys-dir", required_argument },
{ 0x108, "repositories-file", required_argument },
{ 0x109, "no-network" },
{ 0x115, "no-cache" },
{ 0x116, "cache-dir", required_argument, "CACHEDIR" },
{ 0x119, "cache-max-age", required_argument, "AGE" },
{ 0x112, "arch", required_argument, "ARCH" },
{ 0x116, "cache-dir", required_argument },
{ 0x119, "cache-max-age", required_argument },
{ 0x112, "arch", required_argument },
{ 0x114, "print-arch" },
#ifdef TEST_MODE
{ 0x200, "test-repo", required_argument, "REPO" },
{ 0x201, "test-instdb", required_argument, "INSTALLED" },
{ 0x202, "test-world", required_argument, "WORLD DEPS" },
{ 0x200, "test-repo", required_argument },
{ 0x201, "test-instdb", required_argument },
{ 0x202, "test-world", required_argument },
#endif
};
@ -280,59 +280,10 @@ const struct apk_option_group optgroup_commit = {
.parse = option_parse_commit,
};
static int format_option(char *buf, size_t len, const struct apk_option *o,
const char *separator)
{
int i = 0;
if (o->val <= 0xff && isalnum(o->val)) {
i += snprintf(&buf[i], len - i, "-%c", o->val);
if (o->name != NULL)
i += snprintf(&buf[i], len - i, "%s", separator);
}
if (o->name != NULL)
i += snprintf(&buf[i], len - i, "--%s", o->name);
if (o->arg_name != NULL)
i += snprintf(&buf[i], len - i, " %s", o->arg_name);
return i;
}
static void print_usage(const char *cmd, const char *args, const struct apk_option_group **optgroups)
{
struct apk_indent indent = { .indent = 11 };
const struct apk_option *opts;
char word[128];
int g, i, j;
indent.x = printf("\nusage: apk %s", cmd) - 1;
for (g = 0; optgroups[g]; g++) {
opts = optgroups[g]->options;
for (i = 0; i < optgroups[g]->num_options; i++) {
if (!opts[i].name) continue;
j = 0;
word[j++] = '[';
j += format_option(&word[j], sizeof(word) - j, &opts[i], "|");
word[j++] = ']';
apk_print_indented(&indent, APK_BLOB_PTR_LEN(word, j));
}
}
if (args != NULL)
apk_print_indented(&indent, APK_BLOB_STR(args));
printf("\n");
}
static int usage(struct apk_applet *applet)
{
version();
if (applet == NULL) {
print_usage("<COMMAND>", "[<args>...]", default_optgroups);
} else {
print_usage(applet->name, applet->arguments, &applet->optgroups[1]);
}
printf("\nThis apk has coffee making abilities.\n");
apk_help(applet);
return 1;
}

View File

@ -17,16 +17,10 @@
#include "apk_defines.h"
#include "apk_database.h"
#define APK_COMMAND_GROUP_INSTALL 0x0001
#define APK_COMMAND_GROUP_SYSTEM 0x0002
#define APK_COMMAND_GROUP_QUERY 0x0004
#define APK_COMMAND_GROUP_REPO 0x0008
struct apk_option {
int val;
const char *name;
int has_arg;
const char *arg_name;
};
struct apk_option_group {
@ -42,10 +36,9 @@ struct apk_applet {
struct list_head node;
const char *name;
const char *arguments;
const struct apk_option_group *optgroups[4];
unsigned int open_flags, forced_flags, forced_force, command_groups;
unsigned int open_flags, forced_flags, forced_force;
int context_size;
int (*main)(void *ctx, struct apk_database *db, struct apk_string_array *args);
@ -53,6 +46,7 @@ struct apk_applet {
extern const struct apk_option_group optgroup_global, optgroup_commit;
void apk_help(struct apk_applet *applet);
void apk_applet_register(struct apk_applet *);
typedef void (*apk_init_func_t)(void);

View File

@ -54,7 +54,7 @@ static const struct apk_option options_applet[] = {
{ 0x10001, "no-chown" },
{ 'u', "upgrade" },
{ 'l', "latest" },
{ 't', "virtual", required_argument, "NAME" },
{ 't', "virtual", required_argument },
};
static const struct apk_option_group optgroup_applet = {
@ -204,9 +204,7 @@ static int add_main(void *ctx, struct apk_database *db, struct apk_string_array
static struct apk_applet apk_add = {
.name = "add",
.arguments = "PACKAGE...",
.open_flags = APK_OPENF_WRITE,
.command_groups = APK_COMMAND_GROUP_INSTALL,
.context_size = sizeof(struct add_ctx),
.optgroups = { &optgroup_global, &optgroup_commit, &optgroup_applet },
.main = add_main,

View File

@ -346,7 +346,6 @@ static int audit_main(void *ctx, struct apk_database *db, struct apk_string_arra
static struct apk_applet apk_audit = {
.name = "audit",
.arguments = "[directory to audit]...",
.open_flags = APK_OPENF_READ|APK_OPENF_NO_SCRIPTS|APK_OPENF_NO_REPOS,
.context_size = sizeof(struct audit_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },

View File

@ -182,9 +182,7 @@ err:
static struct apk_applet apk_cache = {
.name = "cache",
.arguments = "sync | clean | download",
.open_flags = APK_OPENF_READ|APK_OPENF_NO_SCRIPTS|APK_OPENF_CACHE_WRITE,
.command_groups = APK_COMMAND_GROUP_SYSTEM,
.context_size = sizeof(struct cache_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },
.main = cache_main,

View File

@ -167,9 +167,7 @@ static int del_main(void *pctx, struct apk_database *db, struct apk_string_array
static struct apk_applet apk_del = {
.name = "del",
.arguments = "PACKAGE...",
.open_flags = APK_OPENF_WRITE | APK_OPENF_NO_AUTOUPDATE,
.command_groups = APK_COMMAND_GROUP_INSTALL,
.context_size = sizeof(struct del_ctx),
.optgroups = { &optgroup_global, &optgroup_commit, &optgroup_applet },
.main = del_main,

View File

@ -168,9 +168,7 @@ static int dot_main(void *pctx, struct apk_database *db, struct apk_string_array
static struct apk_applet apk_dot = {
.name = "dot",
.arguments = "PKGMASK...",
.open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE,
.command_groups = APK_COMMAND_GROUP_QUERY,
.context_size = sizeof(struct dot_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },
.main = dot_main,

View File

@ -96,7 +96,7 @@ static const struct apk_option options_applet[] = {
{ 'R', "recursive" },
{ 0x104, "simulate" },
{ 's', "stdout" },
{ 'o', "output", required_argument, "DIR" },
{ 'o', "output", required_argument },
};
static const struct apk_option_group optgroup_applet = {
@ -343,9 +343,7 @@ static int fetch_main(void *pctx, struct apk_database *db, struct apk_string_arr
static struct apk_applet apk_fetch = {
.name = "fetch",
.arguments = "PACKAGE...",
.open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE,
.command_groups = APK_COMMAND_GROUP_REPO,
.context_size = sizeof(struct fetch_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },
.main = fetch_main,

View File

@ -115,9 +115,7 @@ static int fix_main(void *pctx, struct apk_database *db, struct apk_string_array
static struct apk_applet apk_fix = {
.name = "fix",
.arguments = "PACKAGE...",
.open_flags = APK_OPENF_WRITE,
.command_groups = APK_COMMAND_GROUP_SYSTEM,
.context_size = sizeof(struct fix_ctx),
.optgroups = { &optgroup_global, &optgroup_commit, &optgroup_applet },
.main = fix_main,

View File

@ -56,10 +56,10 @@ static int option_parse_applet(void *ctx, struct apk_db_options *dbopts, int opt
}
static const struct apk_option options_applet[] = {
{ 'o', "output", required_argument, "FILE" },
{ 'x', "index", required_argument, "INDEX" },
{ 'd', "description", required_argument, "TEXT" },
{ 0x10000, "rewrite-arch", required_argument, "ARCH" },
{ 'o', "output", required_argument },
{ 'x', "index", required_argument },
{ 'd', "description", required_argument },
{ 0x10000, "rewrite-arch", required_argument },
};
static const struct apk_option_group optgroup_applet = {
@ -253,9 +253,7 @@ static int index_main(void *ctx, struct apk_database *db, struct apk_string_arra
static struct apk_applet apk_index = {
.name = "index",
.arguments = "FILE...",
.open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE | APK_OPENF_NO_REPOS,
.command_groups = APK_COMMAND_GROUP_REPO,
.context_size = sizeof(struct index_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },
.main = index_main,

View File

@ -476,9 +476,7 @@ static const struct apk_option_group optgroup_applet = {
static struct apk_applet apk_info = {
.name = "info",
.arguments = "PACKAGE...",
.open_flags = APK_OPENF_READ,
.command_groups = APK_COMMAND_GROUP_QUERY,
.context_size = sizeof(struct info_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },
.main = info_main,

View File

@ -261,9 +261,7 @@ static int list_main(void *pctx, struct apk_database *db, struct apk_string_arra
static struct apk_applet apk_list = {
.name = "list",
.arguments = "PATTERN",
.open_flags = APK_OPENF_READ,
.command_groups = APK_COMMAND_GROUP_QUERY,
.context_size = sizeof(struct list_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },
.main = list_main,

View File

@ -124,9 +124,7 @@ static int manifest_main(void *ctx, struct apk_database *db, struct apk_string_a
static struct apk_applet apk_manifest = {
.name = "manifest",
.arguments = "PACKAGE...",
.open_flags = APK_OPENF_READ,
.command_groups = APK_COMMAND_GROUP_REPO,
.main = manifest_main,
};

View File

@ -71,7 +71,6 @@ static int policy_main(void *ctx, struct apk_database *db, struct apk_string_arr
static struct apk_applet apk_policy = {
.name = "policy",
.open_flags = APK_OPENF_READ,
.command_groups = APK_COMMAND_GROUP_QUERY,
.main = policy_main,
};

View File

@ -206,9 +206,7 @@ static int search_main(void *pctx, struct apk_database *db, struct apk_string_ar
static struct apk_applet apk_search = {
.name = "search",
.arguments = "PATTERN",
.open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE,
.command_groups = APK_COMMAND_GROUP_QUERY,
.context_size = sizeof(struct search_ctx),
.optgroups = { &optgroup_global, &optgroup_applet },
.main = search_main,

View File

@ -49,7 +49,6 @@ static struct apk_applet apk_update = {
.name = "update",
.open_flags = APK_OPENF_WRITE,
.forced_force = APK_FORCE_REFRESH,
.command_groups = APK_COMMAND_GROUP_SYSTEM,
.main = update_main,
};

View File

@ -188,7 +188,6 @@ static int upgrade_main(void *ctx, struct apk_database *db, struct apk_string_ar
static struct apk_applet apk_upgrade = {
.name = "upgrade",
.open_flags = APK_OPENF_WRITE,
.command_groups = APK_COMMAND_GROUP_SYSTEM,
.context_size = sizeof(struct upgrade_ctx),
.optgroups = { &optgroup_global, &optgroup_commit, &optgroup_applet },
.main = upgrade_main,

View File

@ -48,9 +48,7 @@ static int verify_main(void *ctx, struct apk_database *db, struct apk_string_arr
static struct apk_applet apk_verify = {
.name = "verify",
.arguments = "FILE...",
.open_flags = APK_OPENF_READ | APK_OPENF_NO_STATE,
.command_groups = APK_COMMAND_GROUP_REPO,
.forced_flags = APK_ALLOW_UNTRUSTED,
.main = verify_main,
};

View File

@ -100,7 +100,7 @@ static const struct apk_option options_applet[] = {
{ 't', "test" },
{ 'c', "check" },
{ 'a', "all" },
{ 'l', "limit", required_argument, "LIMCHARs" },
{ 'l', "limit", required_argument },
};
static const struct apk_option_group optgroup_applet = {

300
src/genhelp.lua Normal file
View File

@ -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. */")

43
src/help.c Normal file
View File

@ -0,0 +1,43 @@
/* help.c - Alpine Package Keeper (APK)
*
* Copyright (C) 2020 Timo Teräs <timo.teras@iki.fi>
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation. See http://www.gnu.org/ for details.
*/
#include <zlib.h>
#include "apk_applet.h"
#include "apk_print.h"
static int is_group(struct apk_applet *applet, const char *topic)
{
if (!applet) return strcasecmp(topic, "apk") == 0;
if (strcasecmp(topic, applet->name) == 0) return 1;
for (int i = 0; applet->optgroups[i] && i < ARRAY_SIZE(applet->optgroups); i++)
if (strcasecmp(applet->optgroups[i]->name, topic) == 0) return 1;
return 0;
}
void apk_help(struct apk_applet *applet)
{
#include "help.h"
char buf[uncompressed_help_size], *ptr, *msg;
unsigned long len = sizeof buf;
int num = 0;
uncompress((unsigned char*) buf, &len, compressed_help, sizeof compressed_help);
for (ptr = buf; *ptr && ptr < &buf[len]; ptr = msg + strlen(msg) + 1) {
msg = ptr + strlen(ptr) + 1;
if (is_group(applet, ptr)) {
fputc('\n', stdout);
fwrite(msg, strlen(msg), 1, stdout);
num++;
}
}
if (num == 0) apk_error("Help not found");
}