diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1201d83..1b97a01 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/libfetch/Makefile b/libfetch/Makefile index 12fead9..c4b56f5 100644 --- a/libfetch/Makefile +++ b/libfetch/Makefile @@ -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 $( $@ + cmd_generr = $(src)/errlist.sh $(basename $( $@ -$(obj)/%err.h: $(obj)/%.errors - @$(call echo-cmd,generr) $(cmd_generr); $(cmd_generr) +$(obj)/%err.h: $(src)/%.errors + @$(call echo-cmd,generr) $(cmd_generr) 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("", "[...]", 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 + * 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 +#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"); +}