From 4ca9e1cd2be5bceb7c3c6c12d261f17211d47048 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Mon, 12 May 2014 09:47:43 +0300 Subject: [PATCH] Add module for command line option parsing Especially mcetool has already a lot of options and there are multiple places that need to be edited when new options are added. This easily leads into situations where something is forgotten or wrong - like usage information that is either completely missing or does not match the actual command line parsing logic. The basic idea is to make command line parsing as data driven as possible by defining a structure to hold - long option name and short option character - option and parameter description texts - callback functions for actual processing Arrays of these can then be used to construct data needed by normal getopt_long() and provide --help output in consistent manner. --- .depend | 8 ++ mce-command-line.c | 313 +++++++++++++++++++++++++++++++++++++++++++++ mce-command-line.h | 91 +++++++++++++ 3 files changed, 412 insertions(+) create mode 100644 mce-command-line.c create mode 100644 mce-command-line.h diff --git a/.depend b/.depend index 3a1803ba..7b24c378 100644 --- a/.depend +++ b/.depend @@ -90,6 +90,14 @@ libwakelock.pic.o:\ libwakelock.c\ libwakelock.h\ +mce-command-line.o:\ + mce-command-line.c\ + mce-command-line.h\ + +mce-command-line.pic.o:\ + mce-command-line.c\ + mce-command-line.h\ + mce-conf.o:\ mce-conf.c\ datapipe.h\ diff --git a/mce-command-line.c b/mce-command-line.c new file mode 100644 index 00000000..f374ffee --- /dev/null +++ b/mce-command-line.c @@ -0,0 +1,313 @@ +/** + * @file mce-command-line.c + * + * Command line parameter parsing module for the Mode Control Entity + *

+ * Copyright © 2014 Jolla Ltd. + *

+ * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#include "mce-command-line.h" + +#include +#include +#include +#include + +/* ========================================================================= * + * CONSTANTS + * ========================================================================= */ + +/** Maximum number of short options supported; A-Z a-z 0-9 */ +#define FLAGS_MAX ('z' - 'a' + 1 + 'Z' - 'A' + 1 + '9' - '0' + 1) + +/* ========================================================================= * + * LOCAL FUNCTIONS + * ========================================================================= */ + +// mce_opt_xxx +static bool mce_opt_handle(const mce_opt_t *self, char *optarg); +static void mce_opt_show(const mce_opt_t *self); + +// mce_options_xxx +static size_t mce_options_get_count(const mce_opt_t *opts); +static int mce_options_find_by_flag(const mce_opt_t *opts, int val); +static void mce_options_reflow_lines(const char *text); +static void mce_options_emit_long_help(const mce_opt_t *opts, const char *arg); +static void mce_options_emit_short_help(const mce_opt_t *opts); +static void mce_options_sanity_check(const mce_opt_t *opts); + +/* ========================================================================= * + * mce_opt_xxx + * ========================================================================= */ + +static bool +mce_opt_handle(const mce_opt_t *self, char *arg) +{ + if( !arg ) + return self->without_arg(0); + + return self->with_arg(arg); +} + +static void +mce_opt_show(const mce_opt_t *self) +{ + if( self->flag ) + printf(" -%c,", self->flag); + else + printf(" "); + + printf(" --%s", self->name); + if( self->with_arg ) { + if( self->without_arg ) + putchar('['); + printf("=<%s>", self->values ?: "???"); + if( self->without_arg ) + putchar(']'); + } + + printf("\n"); +} + +/* ========================================================================= * + * mce_options_xxx + * ========================================================================= */ + +static size_t +mce_options_get_count(const mce_opt_t *opts) +{ + size_t count = 0; + + while( opts[count].name ) ++count; + + return count; +} + +static int +mce_options_find_by_flag(const mce_opt_t *opts, int val) +{ + for( size_t i = 0; ; ++i ) { + if( !opts[i].name ) + return -1; + + if( opts[i].flag == val ) + return (int)i; + } +} + +static void +mce_options_reflow_lines(const char *text) +{ + if( !text ) + goto EXIT; + + for( ;; ) { + size_t n = strcspn(text, "\n"); + printf("\t%.*s\n", (int)n, text); + if( *(text += n) ) + ++text; + if( !*text ) + break; + } + printf("\n"); + +EXIT: + return; +} + +static void +mce_options_emit_long_help(const mce_opt_t *opts, const char *arg) +{ + if( arg ) { + while( *arg == '-' ) + ++arg; + + if( !*arg || !strcmp(arg, "all") ) + arg = 0; + } + + for( const mce_opt_t *opt = opts; opt->name; ++opt ) { + if( arg && !strstr(opt->name, arg) ) + continue; + + mce_opt_show(opt); + mce_options_reflow_lines(opt->usage); + } +} + +static void +mce_options_emit_long_help_keys(const mce_opt_t *opts, char **keys) +{ + for( const mce_opt_t *opt = opts; opt->name; ++opt ) { + for( size_t i = 0; keys[i]; ++i ) { + if( !strstr(opt->name, keys[i]) ) + continue; + mce_opt_show(opt); + mce_options_reflow_lines(opt->usage); + break; + } + } +} + +static void +mce_options_emit_short_help(const mce_opt_t *opts) +{ + for( const mce_opt_t *opt = opts; opt->name; ++opt ) + mce_opt_show(opt); +} + +static void +mce_options_sanity_check(const mce_opt_t *opts) +{ + for( const mce_opt_t *first = opts; first->name; ++first ) { + for( const mce_opt_t *later = first + 1; later->name; ++later ) { + if( !strcmp(first->name, later->name) ) { + fprintf(stderr, "duplicate long option '%s'\n", first->name); + exit(EXIT_FAILURE); + } + if( first->flag && first->flag == later->flag ) { + fprintf(stderr, "duplicate short option '%c'\n", first->flag); + exit(EXIT_FAILURE); + } + } + } +} + +/* ========================================================================= * + * mce_command_line_xxx + * ========================================================================= */ + +void +mce_command_line_usage_keys(const mce_opt_t *opts, char **keys) +{ + if( keys && *keys ) + mce_options_emit_long_help_keys(opts, keys); +} + +/** Print usage information from provided look up table + * + * Helper function for implementing -h / --help handler. + * + * If NULL arg is passed, will print short list of supported options + * and arguments that can be passed. + * + * If non NULL arg is passed will print full usage information for options + * that have arg as substring of name property. + * + * As a special case passing "" or "all" will print full information for + * all options. + * + * @param opts array of command line option information + * @param arg option argument from command line + */ +void +mce_command_line_usage(const mce_opt_t *opts, const char *arg) +{ + if( arg ) + mce_options_emit_long_help(opts, arg); + else + mce_options_emit_short_help(opts); +} + +/** Parse command line options using the provided look up table + * + * Actual parsing happens via standard getopt_long() function. The + * arrays needed by it are constructed from opts array given as input. + * + * If both with_arg and witout_arg callbacks are defined, providing + * option argument is optional. + * + * Since getopt_long() is used, non-option arguments can be processed + * after mce_command_line_parse() returns from argv[optind ... argc-1]. + * + * @return true on success, false in case of errors + */ +bool +mce_command_line_parse(const mce_opt_t *opts, int argc, char **argv) +{ + bool success = false; + size_t opt_cnt = mce_options_get_count(opts); + struct option *opt_arr = calloc(opt_cnt + 1, sizeof *opt_arr); + size_t flg_cnt = 0; + char flg_arr[FLAGS_MAX * 3 + 1]; + + /* Check that option look up table does not contain duplicates etc */ + + mce_options_sanity_check(opts); + + /* Generate getopt_long() compatible look up tables */ + + for( size_t i = 0; i < opt_cnt; ++i ) { + const mce_opt_t *opt = opts + i; + struct option *std = opt_arr + i; + + /* Initialize long option entry to sane defaults */ + std->name = opt->name; + std->flag = 0; + std->val = 0; + std->has_arg = no_argument; + + /* Decide between no / optional / required arg */ + if( opt->with_arg ) { + if( opt->without_arg ) + std->has_arg = optional_argument; + else + std->has_arg = required_argument; + } + + /* Fill in short option array too */ + if( opt->flag ) { + flg_arr[flg_cnt++] = (char)opt->flag; + switch( std->has_arg ) { + case optional_argument: flg_arr[flg_cnt++] = ':'; // fall through + case required_argument: flg_arr[flg_cnt++] = ':'; // fall through + default: break; + } + } + } + opt_arr[opt_cnt].name = 0; + flg_arr[flg_cnt] = 0; + + /* Do the actual command line argument handling with getopt_long() */ + + for( ;; ) { + int id = -1; + int rc = getopt_long(argc, argv, flg_arr, opt_arr, &id); + + if( rc == -1 ) + break; + + if( rc == '?' ) + goto EXIT; + + if( rc != 0 ) + id = mce_options_find_by_flag(opts, rc); + + if( id == -1 ) + goto EXIT; + + if( !mce_opt_handle(opts + id, optarg) ) + goto EXIT; + } + + success = true; + +EXIT: + free(opt_arr); + + return success; +} diff --git a/mce-command-line.h b/mce-command-line.h new file mode 100644 index 00000000..76b08368 --- /dev/null +++ b/mce-command-line.h @@ -0,0 +1,91 @@ +/** + * @file mce-command-line.h + * + * Command line parameter parsing module for the Mode Control Entity + *

+ * Copyright © 2014 Jolla Ltd. + *

+ * @author Simo Piiroinen + * + * mce is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * mce is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mce. If not, see . + */ + +#ifndef MCE_COMMAND_LINE_H_ +# define MCE_COMMAND_LINE_H_ + +# include + +# ifdef __cplusplus +extern "C" { +# elif 0 +} /* fool JED indentation ... */ +# endif + +/* ========================================================================= * + * TYPES + * ========================================================================= */ + +typedef struct mce_opt_t mce_opt_t; + +/** Option handler callback + * + * Both with_arg and without_arg callbacks use this type. This allows + * the same callback function to be used for handling both. The arg + * is NULL only when without_arg handler is called. + * + * @param arg option arg or NULL + * + * @return false to stop command line parsing and return error from + * mce_command_line_parse(), or true to keep going + */ +typedef bool (*mce_opt_parser_fn)(const char *arg); + +/** Information about command line option + * + * If both with_arg and witout_arg callbacks are defined, providing + * option argument is optional. + */ +struct mce_opt_t +{ + /** Long option name; NULL for sentinel element */ + const char *name; + + /** Short option flag character; 0 for none */ + int flag; + + /** Description text for option argument; NULL if not used */ + const char *values; + + /** Usage information text for the option */ + const char *usage; + + /** Callback to use when option argument is provided */ + mce_opt_parser_fn with_arg; + + /** Callback to use when no option argument is provided */ + mce_opt_parser_fn without_arg; +}; + +/* ========================================================================= * + * INTERFACE FUNCTIONS + * ========================================================================= */ + +void mce_command_line_usage(const mce_opt_t *opts, const char *arg); +void mce_command_line_usage_keys(const mce_opt_t *opts, char **keys); +bool mce_command_line_parse(const mce_opt_t *opts, int argc, char **argv); + +# ifdef __cplusplus +}; +# endif + +#endif /* MCE_COMMAND_LINE_H_ */