| /* libnih |
| * |
| * command.c - command parser based on nih_option_parser |
| * |
| * Copyright © 2009 Scott James Remnant <scott@netsplit.com>. |
| * Copyright © 2009 Canonical Ltd. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR |
| * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif /* HAVE_CONFIG_H */ |
| |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <nih/macros.h> |
| #include <nih/alloc.h> |
| #include <nih/string.h> |
| #include <nih/main.h> |
| #include <nih/option.h> |
| #include <nih/command.h> |
| #include <nih/logging.h> |
| |
| |
| /* Prototypes for static functions */ |
| static NihCommand *nih_command_get (NihCommand *commands, |
| const char *command); |
| static int nih_command_handle (const void *parent, |
| int argc, char *argv[], |
| NihOption *options, |
| NihCommand *commands, |
| NihCommand *command); |
| static void nih_command_help (NihCommand *commands); |
| static void nih_command_group_help (NihCommandGroup *group, |
| NihCommand *commands, |
| NihCommandGroup **groups); |
| |
| |
| /** |
| * default_commands: |
| * |
| * These default commands are appended to those defined by the user |
| * so they can be overriden. |
| **/ |
| static const NihCommand default_commands[] = { |
| { "help", NULL, |
| N_("display list of commands"), NULL, NULL, NULL, NULL }, |
| |
| NIH_COMMAND_LAST |
| }; |
| |
| /** |
| * no_options: |
| * |
| * This is used whenever the options member of NihCommand is NULL. |
| **/ |
| static const NihOption no_options[] = { |
| NIH_OPTION_LAST |
| }; |
| |
| |
| /** |
| * nih_command_parser: |
| * @parent: parent for arguments arrays, |
| * @argc: number of arguments, |
| * @argv: command-line arguments, |
| * @options: global options, |
| * @commands: commands to look for. |
| * |
| * Parses the command-line arguments given in @argv until the first |
| * non-option argument is found. Options preceeding that are handled |
| * according to @options by nih_option_parser(). |
| * |
| * The argument is looked up in @commands, and if found, that is used to |
| * process the remaining options and arguments. |
| * |
| * Alternatively if the program name can be found in @commands, then the |
| * entire @argv list is treated as the command instead of locating the |
| * first non-option. |
| * |
| * Reminaing arguments are passed to the action function of the @commands |
| * member found. |
| * |
| * The usage stem and string are constructed automatically, calling |
| * nih_option_set_usage() or nih_option_set_usage_stem() before this |
| * function will have no effect. |
| * |
| * If @parent is not NULL, it should be a pointer to another object which |
| * will be used as a parent for arguments arrays. When all parents |
| * of the array are freed, the array will also be freed. |
| * |
| * Errors are handled by printing a message to standard error. |
| * |
| * Returns: return value from action function or negative value on error. |
| **/ |
| int |
| nih_command_parser (const void *parent, |
| int argc, |
| char *argv[], |
| NihOption *options, |
| NihCommand *commands) |
| { |
| nih_local NihCommand *cmds = NULL; |
| NihCommand *cmd; |
| nih_local char *footer = NULL, *stem = NULL; |
| nih_local char **args = NULL; |
| char **arg; |
| int ret; |
| |
| nih_assert (argc > 0); |
| nih_assert (argv != NULL); |
| nih_assert (options != NULL); |
| nih_assert (commands != NULL); |
| nih_assert (program_name != NULL); |
| |
| cmds = nih_command_join (NULL, commands, default_commands); |
| |
| /* First check the program name for a valid command */ |
| cmd = nih_command_get (cmds, program_name); |
| if (cmd) |
| return nih_command_handle (parent, argc, argv, |
| options, cmds, cmd); |
| |
| /* Set help strings to make ordinary --help look right */ |
| footer = NIH_MUST (nih_sprintf (NULL, _("For a list of commands, " |
| "try `%s help'."), |
| program_name)); |
| nih_option_set_footer (footer); |
| nih_option_set_usage (_("COMMAND [OPTION]... [ARG]...")); |
| |
| /* Parse options up until the first non-opt argument */ |
| args = nih_option_parser (NULL, argc, argv, options, TRUE); |
| |
| /* Clean up help strings */ |
| nih_option_set_footer (NULL); |
| nih_option_set_usage (NULL); |
| |
| /* Check for option parsing errors */ |
| if (! args) |
| return -1; |
| |
| /* Check we actually got a command */ |
| if (! args[0]) { |
| fprintf (stderr, _("%s: missing command\n"), program_name); |
| nih_main_suggest_help (); |
| return -1; |
| } |
| |
| /* Find that command */ |
| cmd = nih_command_get (cmds, args[0]); |
| if (! cmd) { |
| fprintf (stderr, _("%s: invalid command: %s\n"), |
| program_name, args[0]); |
| nih_main_suggest_help (); |
| return -1; |
| } |
| |
| /* Count the number of arguments in the args array */ |
| for (arg = args; *arg; arg++) |
| ; |
| |
| /* Set the usage stem to include the command name */ |
| stem = NIH_MUST (nih_sprintf (NULL, _("%s [OPTION]..."), |
| cmd->command)); |
| nih_option_set_usage_stem (stem); |
| |
| /* Handle the command */ |
| ret = nih_command_handle (parent, arg - args, args, |
| options, cmds, cmd); |
| |
| /* Clean up usage stem */ |
| nih_option_set_usage_stem (NULL); |
| |
| return ret; |
| } |
| |
| |
| /** |
| * nih_command_join: |
| * @parent: parent object for new array, |
| * @a: first command array, |
| * @b: second command array. |
| * |
| * Joins the two command arrays together to produce a combined array |
| * containing the commands from @a followed by the commands from @b. |
| * |
| * The new list is allocated with nih_alloc(), but the members are just |
| * copied in from @a and @b including any pointers therein. Freeing the |
| * new array with nih_free() is entirely safe. |
| * |
| * If @parent is not NULL, it should be a pointer to another object which |
| * will be used as a parent for the returned array. When all parents |
| * of the returned array are freed, the returned array will also be |
| * freed. |
| * |
| * Returns: combined command array. |
| **/ |
| NihCommand * |
| nih_command_join (const void *parent, |
| const NihCommand *a, |
| const NihCommand *b) |
| { |
| const NihCommand *cmd; |
| NihCommand *cmds; |
| size_t alen = 0, blen = 0; |
| |
| nih_assert (a != NULL); |
| nih_assert (b != NULL); |
| |
| /* Count commands in first list */ |
| for (cmd = a; cmd->command; cmd++) |
| alen++; |
| |
| /* Count commands in second list */ |
| for (cmd = b; cmd->command; cmd++) |
| blen++; |
| |
| /* Allocate combined list */ |
| cmds = NIH_MUST (nih_alloc (parent, |
| sizeof (NihCommand) * (alen + blen + 1))); |
| |
| /* Copy options, making sure to copy the last option from b */ |
| memcpy (cmds, a, sizeof (NihCommand) * alen); |
| memcpy (cmds + alen, b, sizeof (NihCommand) * (blen + 1)); |
| |
| return cmds; |
| } |
| |
| /** |
| * nih_command_get: |
| * @commands: command list, |
| * @command: command to find. |
| * |
| * Find the command structure with the given @command in the @commands list. |
| * |
| * Returns; pointer to command or NULL if not found. |
| **/ |
| static NihCommand * |
| nih_command_get (NihCommand *commands, |
| const char *command) |
| { |
| NihCommand *cmd; |
| |
| for (cmd = commands; cmd->command; cmd++) |
| if (! strcmp (command, cmd->command)) |
| return cmd; |
| |
| return NULL; |
| } |
| |
| |
| /** |
| * nih_command_handle: |
| * @parent: parent for arguments arrays, |
| * @argc: number of arguments, |
| * @argv: command-line arguments, |
| * @options: global options, |
| * @commands: commands looked for, |
| * @command: NihCommand invoked. |
| * |
| * This function is called to handle a @command that was either invoked |
| * directly by program name, or found as an argument on the command line. |
| * The list of commands looked for should be in @commands so that the |
| * "help" command can be handled. |
| * |
| * @argv should be the list of arguments starting from the name of the |
| * command, which is skipped. @options is added to any options specified |
| * in @command so that global options are always available. |
| * |
| * After parsing the options, remaining arguments are passed to the action |
| * function of @command. |
| * |
| * If @parent is not NULL, it should be a pointer to another object which |
| * will be used as a parent for the arguments arrays. When all parents |
| * of the array are freed, the array will also be freed. |
| * |
| * Errors are handled by printing a message to standard error. |
| * |
| * Returns: return value from action or negative value on error. |
| **/ |
| static int |
| nih_command_handle (const void *parent, |
| int argc, |
| char *argv[], |
| NihOption *options, |
| NihCommand *commands, |
| NihCommand *command) |
| { |
| char **args; |
| const NihOption *cmd_opts; |
| nih_local NihOption *opts = NULL; |
| int ret; |
| |
| nih_assert (argc > 0); |
| nih_assert (argv != NULL); |
| nih_assert (options != NULL); |
| nih_assert (commands != NULL); |
| nih_assert (command != NULL); |
| |
| /* Join the command and global options together, allow command to |
| * take precedence |
| */ |
| cmd_opts = command->options ? command->options : no_options; |
| opts = nih_option_join (NULL, cmd_opts, options); |
| |
| /* Set up the option parser from the command information */ |
| nih_option_set_usage (_(command->usage)); |
| nih_option_set_synopsis (_(command->synopsis)); |
| nih_option_set_help (_(command->help)); |
| |
| /* Parse the remaining arguments against all of the options */ |
| args = nih_option_parser (parent, argc, argv, opts, FALSE); |
| |
| /* Clean up help strings again */ |
| nih_option_set_usage (NULL); |
| nih_option_set_synopsis (NULL); |
| nih_option_set_help (NULL); |
| |
| /* Check for option parsing failure */ |
| if (! args) |
| return -1; |
| |
| /* Handle the special cased commands first */ |
| if (! strcmp (command->command, "help")) { |
| nih_command_help (commands); |
| exit (0); |
| } |
| |
| /* Delegate to the command handler */ |
| ret = command->action (command, args); |
| |
| nih_free (args); |
| return ret; |
| } |
| |
| |
| /** |
| * nih_command_help: |
| * @commands: list of commands. |
| * |
| * Output a list of the known commands to standard output grouped by the |
| * group member of the command. |
| **/ |
| static void |
| nih_command_help (NihCommand *commands) |
| { |
| NihCommand *cmd; |
| nih_local NihCommandGroup **groups = NULL; |
| size_t group, ngroups; |
| int other = FALSE; |
| |
| nih_assert (program_name != NULL); |
| |
| groups = NULL; |
| ngroups = 0; |
| |
| /* Count the number of command groups */ |
| for (cmd = commands; cmd->command; cmd++) { |
| if (! cmd->group) { |
| other = TRUE; |
| continue; |
| } |
| |
| for (group = 0; group < ngroups; group++) { |
| if (groups[group] == cmd->group) |
| break; |
| } |
| |
| if (group < ngroups) |
| continue; |
| |
| groups = NIH_MUST (nih_realloc (groups, NULL, |
| (sizeof (NihCommandGroup *) |
| * (ngroups + 1)))); |
| groups[ngroups++] = cmd->group; |
| } |
| |
| /* Iterate the command groups we found in order, and display |
| * only their commands |
| */ |
| for (group = 0; group < ngroups; group++) |
| nih_command_group_help (groups[group], commands, groups); |
| |
| /* Display the other group */ |
| if (other) |
| nih_command_group_help (NULL, commands, groups); |
| |
| /* Say how to find out about a command */ |
| printf (_("For more information on a command, " |
| "try `%s COMMAND --help'.\n"), program_name); |
| } |
| |
| /** |
| * nih_command_group_help: |
| * @group: group to display, |
| * @commands: list of commands, |
| * @groups: all groups. |
| * |
| * Output a list of commands in the given @group to standard output. |
| **/ |
| static void |
| nih_command_group_help (NihCommandGroup *group, |
| NihCommand *commands, |
| NihCommandGroup **groups) |
| { |
| NihCommand *cmd; |
| size_t width; |
| |
| nih_assert (commands != NULL); |
| |
| if (group) { |
| printf (_("%s commands:\n"), _(group->title)); |
| } else if (groups) { |
| printf (_("Other commands:\n")); |
| } else { |
| printf (_("Commands:\n")); |
| } |
| |
| width = nih_max (nih_str_screen_width (), 50U) - 30; |
| |
| for (cmd = commands; cmd->command; cmd++) { |
| nih_local char *str = NULL; |
| char *ptr; |
| size_t len = 0; |
| |
| if (cmd->group != group) |
| continue; |
| |
| if (! cmd->synopsis) |
| continue; |
| |
| /* Indent by two spaces */ |
| printf (" "); |
| len += 2; |
| |
| /* Output the command */ |
| printf ("%s", cmd->command); |
| len += strlen (cmd->command); |
| |
| /* Format the synopsis string to fit in the latter |
| * half of the screen |
| */ |
| str = NIH_MUST (nih_str_wrap (NULL, cmd->synopsis, |
| width, 0, 2)); |
| |
| /* Write the description to the screen */ |
| ptr = str; |
| while (ptr && *ptr) { |
| size_t linelen; |
| |
| /* Not enough room on this line */ |
| if (len > 28) { |
| printf ("\n"); |
| len = 0; |
| } |
| |
| /* Pad line up to the right column */ |
| while (len < 30) { |
| printf (" "); |
| len++; |
| } |
| |
| /* Output the line up until the next line */ |
| linelen = strcspn (ptr, "\n"); |
| printf ("%.*s\n", (int)linelen, ptr); |
| len = 0; |
| |
| /* Skip to the next line */ |
| ptr += linelen; |
| if (*ptr == '\n') |
| ptr++; |
| } |
| } |
| |
| printf ("\n"); |
| } |