/** * @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; }