#include "internal.h"
#pragma mark Definitions
#define CTL_OUTPUT_WIDTH (80)
#define CTL_OUTPUT_OPTARG_PAD (28)
#define CTL_OUTPUT_LIST_PAD (4)
#define SUBCOMMAND_LINKER_SET "__subcommands"
#pragma mark Module Globals
static const os_subcommand_t _help_cmd;
#pragma mark Module Routines
static char *
_os_subcommand_copy_optarg_usage(const os_subcommand_t *osc,
const struct option *opt, os_subcommand_optarg_format_t format,
os_subcommand_option_t *scopt)
{
char optbuff[64] = "";
char argbuff[64] = "";
char *final = NULL;
int ret = -1;
snprintf(optbuff, sizeof(optbuff), "--%s", opt->name);
if (osc->osc_info) {
osc->osc_info(osc, format, opt, scopt);
}
switch (opt->has_arg) {
case no_argument:
break;
case optional_argument:
snprintf(argbuff, sizeof(argbuff), "[=%s]", scopt->osco_argdesc);
break;
case required_argument:
snprintf(argbuff, sizeof(argbuff), "=<%s>", scopt->osco_argdesc);
break;
default:
__builtin_unreachable();
}
ret = asprintf(&final, "%s%s", optbuff, argbuff);
if (ret < 0) {
os_assert_zero(ret);
}
return final;
}
static void
_os_subcommand_print_optarg_usage(const os_subcommand_t *osc,
const struct option *opt, FILE *f)
{
os_subcommand_option_t scopt = {
.osco_flags = 0,
.osco_argdesc = opt->name,
};
char *__os_free usage = NULL;
char *braces[2] = {
"",
"",
};
usage = _os_subcommand_copy_optarg_usage(osc, opt,
OS_SUBCOMMAND_OPTARG_USAGE, &scopt);
if (scopt.osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL) {
braces[0] = "[";
braces[1] = "]";
}
fprintf(f, " %s%s%s", braces[0], usage, braces[1]);
}
static void
_os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f)
{
const struct option *opts = osc->osc_options;
const struct option *curopt = NULL;
size_t i = 0;
fprintf(f, "usage: %s %s", getprogname(), osc->osc_name);
while ((curopt = &opts[i]) && curopt->name) {
_os_subcommand_print_optarg_usage(osc, curopt, f);
i++;
}
fprintf(f, "\n");
}
static void
_os_subcommand_print_optarg_human(const os_subcommand_t *osc,
const struct option *opt, FILE *f)
{
os_subcommand_option_t scopt = {
.osco_flags = 0,
.osco_argdesc = opt->name,
};
char *__os_free usage = NULL;
char *__os_free human = NULL;
usage = _os_subcommand_copy_optarg_usage(osc, opt,
OS_SUBCOMMAND_OPTARG_USAGE, &scopt);
fprintf(f, " %-24s", usage);
human = _os_subcommand_copy_optarg_usage(osc, opt,
OS_SUBCOMMAND_OPTARG_HUMAN, &scopt);
wfprintf_np(f, -CTL_OUTPUT_OPTARG_PAD, CTL_OUTPUT_OPTARG_PAD,
CTL_OUTPUT_WIDTH, "%s", scopt.osco_argdesc);
}
static void
_os_subcommand_print_human(const os_subcommand_t *osc, FILE *f)
{
const struct option *opts = osc->osc_options;
const struct option *curopt = NULL;
size_t i = 0;
_os_subcommand_print_usage(osc, f);
while ((curopt = &opts[i]) && curopt->name) {
_os_subcommand_print_optarg_human(osc, curopt, f);
i++;
}
}
static void
_os_subcommand_print_list(const os_subcommand_t *osc, FILE *f)
{
wfprintf_np(f, CTL_OUTPUT_LIST_PAD, 0, 0, "%-24s %s",
osc->osc_name, osc->osc_desc);
}
static const os_subcommand_t *
_os_subcommand_find(const char *name)
{
const os_subcommand_t **oscip = NULL;
if (strcmp(_help_cmd.osc_name, name) == 0) {
return &_help_cmd;
}
LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) {
const os_subcommand_t *osci = *oscip;
if (strcmp(osci->osc_name, name) == 0) {
return osci;
}
}
return NULL;
}
#pragma mark Default Usage
static void
_usage_default(FILE *f)
{
const os_subcommand_t **oscip = NULL;
crfprintf_np(f, "usage: %s <subcommand> [...] | help [subcommand]",
getprogname());
crfprintf_np(f, "");
crfprintf_np(f, "subcommands:");
LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) {
const os_subcommand_t *osci = *oscip;
_os_subcommand_print_list(osci, f);
}
_os_subcommand_print_list(&_help_cmd, f);
}
static int
_usage(FILE *f)
{
_usage_default(f);
return EX_USAGE;
}
#pragma mark Help Subcommand
static int _help_invoke(const os_subcommand_t *osc,
int argc,
const char *argv[]
);
static const os_subcommand_t _help_cmd = {
.osc_version = OS_SUBCOMMAND_VERSION,
.osc_flags = 0,
.osc_name = "help",
.osc_desc = "prints helpful information",
.osc_optstring = NULL,
.osc_options = NULL,
.osc_info = NULL,
.osc_invoke = &_help_invoke,
};
static void
_help_print_subcommand(const os_subcommand_t *osc, FILE *f)
{
wfprintf_np(f, 4, 4, 76, "%-16s%s", osc->osc_name, osc->osc_desc);
}
static void
_help_print_all(FILE *f)
{
const os_subcommand_t **oscip = NULL;
_usage_default(f);
crfprintf_np(f, "");
crfprintf_np(f, "subcommands:");
LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) {
const os_subcommand_t *osci = *oscip;
if (osci->osc_flags & OS_SUBCOMMAND_FLAG_HIDDEN) {
continue;
}
_help_print_subcommand(osci, f);
}
}
static int
_help_invoke(const os_subcommand_t *osc, int argc, const char *argv[])
{
const os_subcommand_t *target = NULL;
if (argc == 1) {
_help_print_all(stdout);
} else {
target = _os_subcommand_find(argv[1]);
if (!target) {
crfprintf_np(stderr, "unrecognized subcommand: %s", argv[1]);
_usage_default(stderr);
return EX_USAGE;
}
_os_subcommand_print_human(target, stdout);
}
return 0;
}
#pragma mark API
int
os_subcommand_main(int argc, const char *argv[])
{
int exitcode = -1;
const char *cmdname = NULL;
const os_subcommand_t *osci = NULL;
if (argc < 2) {
exitcode = _usage(stderr);
goto __out;
}
argc -= 1;
argv += 1;
cmdname = argv[0];
osci = _os_subcommand_find(cmdname);
if (osci) {
if (osci->osc_flags & OS_SUBCOMMAND_FLAG_REQUIRE_ROOT) {
if (geteuid()) {
crfprintf_np(stderr, "subcommand requires root: %s", cmdname);
exitcode = EX_NOPERM;
goto __out;
}
}
if (osci->osc_flags & OS_SUBCOMMAND_FLAG_TTYONLY) {
if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) {
crfprintf_np(stderr, "subcommand requires a tty: %s", cmdname);
exitcode = EX_UNAVAILABLE;
goto __out;
}
}
exitcode = osci->osc_invoke(osci, argc, argv);
if (exitcode == EX_USAGE) {
_os_subcommand_print_usage(osci, stderr);
}
} else {
crfprintf_np(stderr, "unrecognized subcommand: %s", cmdname);
exitcode = _usage(stderr);
}
__out:
return exitcode;
}