diff --git a/CMakeLists.txt b/CMakeLists.txt index 077b109..7b0c049 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ foreach (page zyklonb kike) set (page_output "${PROJECT_BINARY_DIR}/${page}.1") list (APPEND project_MAN_PAGES "${page_output}") add_custom_command (OUTPUT ${page_output} - COMMAND ${HELP2MAN_EXECUTABLE} -N --no-discard-stderr # FIXME + COMMAND ${HELP2MAN_EXECUTABLE} -N "${PROJECT_BINARY_DIR}/${page}" -o ${page_output} DEPENDS ${page} COMMENT "Generating man page for ${page}" VERBATIM) diff --git a/common.c b/common.c index 8c85b2f..e67f57a 100644 --- a/common.c +++ b/common.c @@ -1995,3 +1995,152 @@ call_write_default_config (const char *hint, const struct config_item *table) print_status ("configuration written to `%s'", filename); free (filename); } + +// --- Option handler ---------------------------------------------------------- + +// Simple wrapper for the getopt_long API to make it easier to use and maintain. + +#define OPT_USAGE_ALIGNMENT_COLUMN 30 ///< Alignment for option descriptions + +enum +{ + OPT_OPTIONAL_ARG = (1 << 0), ///< The argument is optional + OPT_LONG_ONLY = (1 << 1) ///< Ignore the short name in opt_string +}; + +// All options need to have both a short name, and a long name. The short name +// is what is returned from opt_handler_get(). It is possible to define a value +// completely out of the character range combined with the OPT_LONG_ONLY flag. +// +// When `arg_hint' is defined, the option is assumed to have an argument. + +struct opt +{ + int short_name; ///< The single-letter name + const char *long_name; ///< The long name + const char *arg_hint; ///< Option argument hint + int flags; ///< Option flags + const char *description; ///< Option description +}; + +struct opt_handler +{ + int argc; ///< The number of program arguments + char **argv; ///< Program arguments + + const char *arg_hint; ///< Program arguments hint + const char *description; ///< Description of the program + + const struct opt *opts; ///< The list of options + size_t opts_len; ///< The length of the option array + + struct option *options; ///< The list of options for getopt + char *opt_string; ///< The `optstring' for getopt +}; + +static void +opt_handler_free (struct opt_handler *self) +{ + free (self->options); + free (self->opt_string); +} + +static void +opt_handler_init (struct opt_handler *self, int argc, char **argv, + const struct opt *opts, const char *arg_hint, const char *description) +{ + memset (self, 0, sizeof *self); + self->argc = argc; + self->argv = argv; + self->arg_hint = arg_hint; + self->description = description; + + size_t len = 0; + for (const struct opt *iter = opts; iter->long_name; iter++) + len++; + + self->opts = opts; + self->opts_len = len; + self->options = xcalloc (len + 1, sizeof *self->options); + + struct str opt_string; + str_init (&opt_string); + + for (size_t i = 0; i < len; i++) + { + const struct opt *opt = opts + i; + struct option *mapped = self->options + i; + + mapped->name = opt->long_name; + if (!opt->arg_hint) + mapped->has_arg = no_argument; + else if (opt->flags & OPT_OPTIONAL_ARG) + mapped->has_arg = optional_argument; + else + mapped->has_arg = required_argument; + mapped->val = opt->short_name; + + if (opt->flags & OPT_LONG_ONLY) + continue; + + str_append_c (&opt_string, opt->short_name); + if (opt->arg_hint) + { + str_append_c (&opt_string, ':'); + if (opt->flags & OPT_OPTIONAL_ARG) + str_append_c (&opt_string, ':'); + } + } + + self->opt_string = str_steal (&opt_string); +} + +static void +opt_handler_usage (struct opt_handler *self, FILE *stream) +{ + struct str usage; + str_init (&usage); + + str_append_printf (&usage, "Usage: %s [OPTION]... %s\n", + self->argv[0], self->arg_hint ? self->arg_hint : ""); + str_append_printf (&usage, "%s\n\n", self->description); + + for (size_t i = 0; i < self->opts_len; i++) + { + struct str row; + str_init (&row); + + const struct opt *opt = self->opts + i; + if (!(opt->flags & OPT_LONG_ONLY)) + str_append_printf (&row, " -%c, ", opt->short_name); + else + str_append (&row, " "); + str_append_printf (&row, "--%s", opt->long_name); + if (opt->arg_hint) + str_append_printf (&row, (opt->flags & OPT_OPTIONAL_ARG) + ? " [%s]" : " %s", opt->arg_hint); + + // TODO: keep the indent if there are multiple lines + if (row.len + 2 <= OPT_USAGE_ALIGNMENT_COLUMN) + { + str_append (&row, " "); + str_append_printf (&usage, "%-*s%s\n", + OPT_USAGE_ALIGNMENT_COLUMN, row.str, opt->description); + } + else + str_append_printf (&usage, "%s\n%-*s%s\n", row.str, + OPT_USAGE_ALIGNMENT_COLUMN, "", opt->description); + + str_free (&row); + } + + fputs (usage.str, stream); + str_free (&usage); +} + +static int +opt_handler_get (struct opt_handler *self) +{ + return getopt_long (self->argc, self->argv, + self->opt_string, self->options, NULL); +} diff --git a/kike.c b/kike.c index f9eb5c3..422d732 100644 --- a/kike.c +++ b/kike.c @@ -1,7 +1,7 @@ /* * kike.c: the experimental IRC daemon * - * Copyright (c) 2014, Přemysl Janouch + * Copyright (c) 2014 - 2015, Přemysl Janouch * All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any @@ -3072,63 +3072,47 @@ daemonize (void) exit_fatal ("failed to reopen FD's: %s", strerror (errno)); } -static void -print_usage (const char *program_name) -{ - fprintf (stderr, - "Usage: %s [OPTION]...\n" - "Experimental IRC server.\n" - "\n" - " -d, --debug run in debug mode (do not daemonize)\n" - " -h, --help display this help and exit\n" - " -V, --version output version information and exit\n" - " --write-default-cfg [filename]\n" - " write a default configuration file and exit\n", - program_name); -} - int main (int argc, char *argv[]) { - const char *invocation_name = argv[0]; - - static struct option opts[] = + static const struct opt opts[] = { - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "write-default-cfg", optional_argument, NULL, 'w' }, - { NULL, 0, NULL, 0 } + { 'd', "debug", NULL, 0, "run in debug mode (do not daemonize)" }, + { 'h', "help", NULL, 0, "display this help and exit" }, + { 'V', "version", NULL, 0, "output version information and exit" }, + { 'w', "write-default-cfg", "FILENAME", + OPT_OPTIONAL_ARG | OPT_LONG_ONLY, + "write a default configuration file and exit" }, + { 0, NULL, NULL, 0, NULL } }; - while (1) + struct opt_handler oh; + opt_handler_init (&oh, argc, argv, opts, NULL, "Experimental IRC daemon."); + + int c; + while ((c = opt_handler_get (&oh)) != -1) + switch (c) { - int c, opt_index; - - c = getopt_long (argc, argv, "dhV", opts, &opt_index); - if (c == -1) - break; - - switch (c) - { - case 'd': - g_debug_mode = true; - break; - case 'h': - print_usage (invocation_name); - exit (EXIT_SUCCESS); - case 'V': - printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); - exit (EXIT_SUCCESS); - case 'w': - call_write_default_config (optarg, g_config_table); - exit (EXIT_SUCCESS); - default: - print_error ("wrong options"); - exit (EXIT_FAILURE); - } + case 'd': + g_debug_mode = true; + break; + case 'h': + opt_handler_usage (&oh, stdout); + exit (EXIT_SUCCESS); + case 'V': + printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); + exit (EXIT_SUCCESS); + case 'w': + call_write_default_config (optarg, g_config_table); + exit (EXIT_SUCCESS); + default: + print_error ("wrong options"); + opt_handler_usage (&oh, stderr); + exit (EXIT_FAILURE); } + opt_handler_free (&oh); + print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting"); setup_signal_handlers (); diff --git a/zyklonb.c b/zyklonb.c index 257de7d..f9624b9 100644 --- a/zyklonb.c +++ b/zyklonb.c @@ -1,7 +1,7 @@ /* * zyklonb.c: the experimental IRC bot * - * Copyright (c) 2014, Přemysl Janouch + * Copyright (c) 2014 - 2015, Přemysl Janouch * All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any @@ -2150,65 +2150,50 @@ on_signal_pipe_readable (const struct pollfd *fd, struct bot_context *ctx) } } -static void -print_usage (const char *program_name) -{ - fprintf (stderr, - "Usage: %s [OPTION]...\n" - "Experimental IRC bot.\n" - "\n" - " -d, --debug run in debug mode\n" - " -h, --help display this help and exit\n" - " -V, --version output version information and exit\n" - " --write-default-cfg [filename]\n" - " write a default configuration file and exit\n", - program_name); -} - int main (int argc, char *argv[]) { - const char *invocation_name = argv[0]; str_vector_init (&g_original_argv); str_vector_add_vector (&g_original_argv, argv); - static struct option opts[] = + static const struct opt opts[] = { - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "write-default-cfg", optional_argument, NULL, 'w' }, - { NULL, 0, NULL, 0 } + { 'd', "debug", NULL, 0, "run in debug mode" }, + { 'h', "help", NULL, 0, "display this help and exit" }, + { 'V', "version", NULL, 0, "output version information and exit" }, + { 'w', "write-default-cfg", "FILENAME", + OPT_OPTIONAL_ARG | OPT_LONG_ONLY, + "write a default configuration file and exit" }, + { 0, NULL, NULL, 0, NULL } }; - while (1) + struct opt_handler oh; + opt_handler_init (&oh, argc, argv, opts, NULL, "Experimental IRC bot."); + + int c; + while ((c = opt_handler_get (&oh)) != -1) + switch (c) { - int c, opt_index; - - c = getopt_long (argc, argv, "dhV", opts, &opt_index); - if (c == -1) - break; - - switch (c) - { - case 'd': - g_debug_mode = true; - break; - case 'h': - print_usage (invocation_name); - exit (EXIT_SUCCESS); - case 'V': - printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); - exit (EXIT_SUCCESS); - case 'w': - call_write_default_config (optarg, g_config_table); - exit (EXIT_SUCCESS); - default: - print_error ("wrong options"); - exit (EXIT_FAILURE); - } + case 'd': + g_debug_mode = true; + break; + case 'h': + opt_handler_usage (&oh, stdout); + exit (EXIT_SUCCESS); + case 'V': + printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); + exit (EXIT_SUCCESS); + case 'w': + call_write_default_config (optarg, g_config_table); + exit (EXIT_SUCCESS); + default: + print_error ("wrong options"); + opt_handler_usage (&oh, stderr); + exit (EXIT_FAILURE); } + opt_handler_free (&oh); + print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting"); setup_signal_handlers ();