You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
3.9 KiB
170 lines
3.9 KiB
2 months ago
|
/*
|
||
|
* UCW Library -- Formatting of command-line option help
|
||
|
*
|
||
|
* (c) 2013 Maria Matejka <mq@ucw.cz>
|
||
|
* (c) 2014 Martin Mares <mj@ucw.cz>
|
||
|
*
|
||
|
* This software may be freely distributed and used according to the terms
|
||
|
* of the GNU Lesser General Public License.
|
||
|
*/
|
||
|
|
||
|
#include <ucw/lib.h>
|
||
|
#include <ucw/opt.h>
|
||
|
#include <ucw/opt-internal.h>
|
||
|
#include <ucw/mempool.h>
|
||
|
#include <ucw/gary.h>
|
||
|
|
||
|
#include <string.h>
|
||
|
|
||
|
struct help {
|
||
|
struct mempool *pool;
|
||
|
struct help_line *lines; // A growing array of lines
|
||
|
};
|
||
|
|
||
|
struct help_line {
|
||
|
const char *extra;
|
||
|
char *fields[3];
|
||
|
};
|
||
|
|
||
|
static void opt_help_scan_item(struct help *h, struct opt_precomputed *opt)
|
||
|
{
|
||
|
const struct opt_item *item = opt->item;
|
||
|
|
||
|
if (!item->help)
|
||
|
return;
|
||
|
|
||
|
bool force_col1 = 0;
|
||
|
if (item->cls == OPT_CL_HELP) {
|
||
|
if (item->flags & OPT_HELP_COL) {
|
||
|
force_col1 = 1;
|
||
|
} else {
|
||
|
struct help_line *l = GARY_PUSH(h->lines);
|
||
|
l->extra = item->help;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (item->letter >= OPT_POSITIONAL_TAIL)
|
||
|
return;
|
||
|
|
||
|
struct help_line *first = GARY_PUSH(h->lines);
|
||
|
char *text = mp_strdup(h->pool, item->help);
|
||
|
struct help_line *l = first;
|
||
|
while (text) {
|
||
|
char *eol = strchr(text, '\n');
|
||
|
if (eol)
|
||
|
*eol++ = 0;
|
||
|
|
||
|
int field = (l == first && !force_col1 ? 1 : 0);
|
||
|
char *f = text;
|
||
|
while (f) {
|
||
|
char *tab = strchr(f, '\t');
|
||
|
if (tab)
|
||
|
*tab++ = 0;
|
||
|
if (field < 3)
|
||
|
l->fields[field++] = f;
|
||
|
f = tab;
|
||
|
}
|
||
|
|
||
|
text = eol;
|
||
|
if (text)
|
||
|
l = GARY_PUSH(h->lines);
|
||
|
}
|
||
|
|
||
|
if (item->name) {
|
||
|
char *val = first->fields[1] ? : "";
|
||
|
if (opt->flags & OPT_REQUIRED_VALUE)
|
||
|
val = mp_printf(h->pool, "=%s", val);
|
||
|
else if (!(opt->flags & OPT_NO_VALUE))
|
||
|
val = mp_printf(h->pool, "[=%s]", val);
|
||
|
first->fields[1] = mp_printf(h->pool, "--%s%s", item->name, val);
|
||
|
}
|
||
|
|
||
|
if (item->letter) {
|
||
|
if (item->name)
|
||
|
first->fields[0] = mp_printf(h->pool, "-%c, ", item->letter);
|
||
|
else {
|
||
|
char *val = first->fields[1] ? : "";
|
||
|
if (!(opt->flags & OPT_REQUIRED_VALUE) && !(opt->flags & OPT_NO_VALUE))
|
||
|
val = mp_printf(h->pool, "[%s]", val);
|
||
|
first->fields[0] = mp_printf(h->pool, "-%c%s", item->letter, val);
|
||
|
first->fields[1] = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void opt_help_scan(struct help *h, const struct opt_section *sec)
|
||
|
{
|
||
|
for (const struct opt_item * item = sec->opt; item->cls != OPT_CL_END; item++) {
|
||
|
if (item->cls == OPT_CL_SECTION)
|
||
|
opt_help_scan(h, item->u.section);
|
||
|
else if (item->cls == OPT_CL_HOOK)
|
||
|
;
|
||
|
else {
|
||
|
struct opt_precomputed opt;
|
||
|
opt_precompute(&opt, item);
|
||
|
opt_help_scan_item(h, &opt);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void opt_help(const struct opt_section * sec) {
|
||
|
// Prepare help text
|
||
|
struct help h;
|
||
|
h.pool = mp_new(4096);
|
||
|
GARY_INIT_ZERO(h.lines, 0);
|
||
|
opt_help_scan(&h, sec);
|
||
|
|
||
|
// Calculate natural width of each column
|
||
|
uint n = GARY_SIZE(h.lines);
|
||
|
uint widths[3] = { 0, 0, 0 };
|
||
|
for (uint i=0; i<n; i++) {
|
||
|
struct help_line *l = &h.lines[i];
|
||
|
for (uint f=0; f<3; f++) {
|
||
|
if (!l->fields[f])
|
||
|
l->fields[f] = "";
|
||
|
uint w = strlen(l->fields[f]);
|
||
|
widths[f] = MAX(widths[f], w);
|
||
|
}
|
||
|
}
|
||
|
if (widths[0] > 4) {
|
||
|
/*
|
||
|
* This is tricky: if there are short options, which have an argument,
|
||
|
* but no long variant, we are willing to let column 0 overflow to column 1.
|
||
|
*/
|
||
|
widths[1] = MAX(widths[1], widths[0] - 4);
|
||
|
widths[0] = 4;
|
||
|
}
|
||
|
widths[1] += 4;
|
||
|
|
||
|
// Print columns
|
||
|
for (uint i=0; i<n; i++) {
|
||
|
struct help_line *l = &h.lines[i];
|
||
|
if (l->extra)
|
||
|
puts(l->extra);
|
||
|
else {
|
||
|
int t = 0;
|
||
|
for (uint f=0; f<3; f++) {
|
||
|
t += widths[f];
|
||
|
t -= printf("%s", l->fields[f]);
|
||
|
while (t > 0) {
|
||
|
putchar(' ');
|
||
|
t--;
|
||
|
}
|
||
|
}
|
||
|
putchar('\n');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Clean up
|
||
|
GARY_FREE(h.lines);
|
||
|
mp_delete(h.pool);
|
||
|
}
|
||
|
|
||
|
void opt_handle_help(const struct opt_item * opt UNUSED, const char * value UNUSED, void * data)
|
||
|
{
|
||
|
struct opt_context *oc = data;
|
||
|
opt_help(oc->options);
|
||
|
exit(0);
|
||
|
}
|