Commit 4ca9e1cd authored by spiiroin's avatar spiiroin

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.
parent 9303bddd
......@@ -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\
......
/**
* @file mce-command-line.c
*
* Command line parameter parsing module for the Mode Control Entity
* <p>
* Copyright © 2014 Jolla Ltd.
* <p>
* @author Simo Piiroinen <simo.piiroinen@jollamobile.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "mce-command-line.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
/* ========================================================================= *
* 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;
}
/**
* @file mce-command-line.h
*
* Command line parameter parsing module for the Mode Control Entity
* <p>
* Copyright © 2014 Jolla Ltd.
* <p>
* @author Simo Piiroinen <simo.piiroinen@jollamobile.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MCE_COMMAND_LINE_H_
# define MCE_COMMAND_LINE_H_
# include <stdbool.h>
# 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_ */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment