summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile15
-rw-r--r--src/apk.c75
-rw-r--r--src/apk_applet.h10
-rw-r--r--src/app_add.c4
-rw-r--r--src/app_audit.c1
-rw-r--r--src/app_cache.c2
-rw-r--r--src/app_del.c2
-rw-r--r--src/app_dot.c2
-rw-r--r--src/app_fetch.c4
-rw-r--r--src/app_fix.c2
-rw-r--r--src/app_index.c10
-rw-r--r--src/app_info.c2
-rw-r--r--src/app_list.c2
-rw-r--r--src/app_manifest.c2
-rw-r--r--src/app_policy.c1
-rw-r--r--src/app_search.c2
-rw-r--r--src/app_update.c1
-rw-r--r--src/app_upgrade.c1
-rw-r--r--src/app_verify.c2
-rw-r--r--src/app_version.c2
-rw-r--r--src/genhelp.lua300
-rw-r--r--src/help.c43
22 files changed, 379 insertions, 106 deletions
diff --git a/src/Makefile b/src/Makefile
index 37c29a2..bea4cdf 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -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
diff --git a/src/apk.c b/src/apk.c
index 68050cd..e993ac6 100644
--- a/src/apk.c
+++ b/src/apk.c
@@ -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;
}
diff --git a/src/apk_applet.h b/src/apk_applet.h
index 1c637a6..390ec59 100644
--- a/src/apk_applet.h
+++ b/src/apk_applet.h
@@ -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);
diff --git a/src/app_add.c b/src/app_add.c
index f5edae0..1e1fe00 100644
--- a/src/app_add.c
+++ b/src/app_add.c
@@ -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,
diff --git a/src/app_audit.c b/src/app_audit.c
index 696599b..f2df583 100644
--- a/src/app_audit.c
+++ b/src/app_audit.c
@@ -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 },
diff --git a/src/app_cache.c b/src/app_cache.c
index 7ea5356..b499ad2 100644
--- a/src/app_cache.c
+++ b/src/app_cache.c
@@ -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,
diff --git a/src/app_del.c b/src/app_del.c
index a5e8ddd..a978f10 100644
--- a/src/app_del.c
+++ b/src/app_del.c
@@ -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,
diff --git a/src/app_dot.c b/src/app_dot.c
index b85d69b..d567e5a 100644
--- a/src/app_dot.c
+++ b/src/app_dot.c
@@ -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,
diff --git a/src/app_fetch.c b/src/app_fetch.c
index 677fabf..224618b 100644
--- a/src/app_fetch.c
+++ b/src/app_fetch.c
@@ -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,
diff --git a/src/app_fix.c b/src/app_fix.c
index ed1ff76..6f332af 100644
--- a/src/app_fix.c
+++ b/src/app_fix.c
@@ -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,
diff --git a/src/app_index.c b/src/app_index.c
index 3e3e437..094388b 100644
--- a/src/app_index.c
+++ b/src/app_index.c
@@ -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,
diff --git a/src/app_info.c b/src/app_info.c
index ee9a0aa..9b2bcb1 100644
--- a/src/app_info.c
+++ b/src/app_info.c
@@ -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,
diff --git a/src/app_list.c b/src/app_list.c
index 3315ea3..f46d30f 100644
--- a/src/app_list.c
+++ b/src/app_list.c
@@ -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,
diff --git a/src/app_manifest.c b/src/app_manifest.c
index fe64bea..757c8a9 100644
--- a/src/app_manifest.c
+++ b/src/app_manifest.c
@@ -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,
};
diff --git a/src/app_policy.c b/src/app_policy.c
index 2d6ae4f..4992858 100644
--- a/src/app_policy.c
+++ b/src/app_policy.c
@@ -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,
};
diff --git a/src/app_search.c b/src/app_search.c
index 9bf64b2..5d70432 100644
--- a/src/app_search.c
+++ b/src/app_search.c
@@ -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,
diff --git a/src/app_update.c b/src/app_update.c
index bbb2b09..f79655e 100644
--- a/src/app_update.c
+++ b/src/app_update.c
@@ -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,
};
diff --git a/src/app_upgrade.c b/src/app_upgrade.c
index 02b1ae7..3f7f096 100644
--- a/src/app_upgrade.c
+++ b/src/app_upgrade.c
@@ -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,
diff --git a/src/app_verify.c b/src/app_verify.c
index 296253c..264610f 100644
--- a/src/app_verify.c
+++ b/src/app_verify.c
@@ -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,
};
diff --git a/src/app_version.c b/src/app_version.c
index a29d899..33d787e 100644
--- a/src/app_version.c
+++ b/src/app_version.c
@@ -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 = {
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. */")
diff --git a/src/help.c b/src/help.c
new file mode 100644
index 0000000..a27992a
--- /dev/null
+++ b/src/help.c
@@ -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");
+}