@@ 1,5 1,19 @@
/**
* sc-argparse.h: simple argument parsing library
+ *
+ * This library aims to make a very simple but generalizable argument parsing
+ * system. While the Unix getopt(3) is simple, it does not support --long
+ * --flags. The non-standard extension getopt_long(3) supports both short and
+ * long flags, but is a GNU extension. For both functions, the usage is
+ * somewhat complex, requiring multiple function calls and magic state being
+ * held within the function.
+ *
+ * This library loosely models its API on the Python argparse library. Ahead of
+ * time, the programmer prepares an array of structs, each one specifying an
+ * argument to the program. The `sc_argparse()` function examines the
+ * command-line args and fills out the specification, validating any assumptions
+ * and returning any errors. After this single function call, the programmer may
+ * retrieve all argument values directly from the specification array.
*/
#ifndef SC_ARGPARSE_H
@@ 7,41 21,145 @@
#include <stdbool.h>
+/**
+ * Types of arguments recognized by sc_argparse.
+ */
enum sc_arg_type {
+ /**
+ * A special argument type to signal the end of the argument list. Do
+ * not use this.
+ */
SC_ARG_TYPE_END = 0,
+ /**
+ * An argument type which will count the occurrences of the long or
+ * short flag in the command line arguments.
+ */
SC_ARG_TYPE_COUNT,
+ /**
+ * An argument which takes a single string value. EG:
+ * --foo=bar, --foo bar, or -f bar.
+ *
+ * NOTE: This argument type cannot handle multiple occurrences in the
+ * command line. If this arg appears multiple times, the value reported
+ * will be the last one.
+ */
SC_ARG_TYPE_STRING,
+ /**
+ * An argument which takes an integer. EG:
+ * --foo=1, --foo 1, or -f 1.
+ *
+ * NOTE: This argument type cannot handle multiple occurrences in the
+ * command line. If this arg appears multiple times, the value reported
+ * will be the last one.
+ */
SC_ARG_TYPE_INT,
};
+/**
+ * A static descriptor for an argument, which encodes all information about the
+ * arg, as well as any output from the argument parsing process.
+ *
+ * @note This struct should not be populated directly, instead it should be
+ * created using macros starting with SC_ARG
+ */
struct sc_arg {
+ /**
+ * The "short" flag (e.g. -a) of this argument; a single character.
+ */
char flag;
+ /**
+ * Set to true when the flag is required to be seen in the args.
+ */
bool required;
+ /**
+ * The argument type
+ */
enum sc_arg_type type;
+ /**
+ * The "long" flag name of this argument, e.g. --foo
+ */
char *name;
+ /**
+ * A short text describing the purpose of the argument
+ */
char *help;
+ /**
+ * (Output by sc_argparse) The value of the argument, if it is a string.
+ */
char *val_string;
+ /**
+ * (Output by sc_argparse) The value of the argument, if it is a count
+ * or int arg.
+ */
int val_int;
+ /**
+ * (Output by sc_argparse) Tracks whether the argument appeared in the
+ * command line.
+ */
bool seen;
};
+/** @brief Parse command line arguments
+ *
+ * Given a specification of arguments (@a argspec), and the command line
+ * argument list (@a argc and @a argv) -- excluding the program name -- parse
+ * all arguments and fill out the "output" fields of the argument spec. Return
+ * the number of command line arguments parsed. The remaining arguments are
+ * positional.
+ *
+ * The argument spec explicitly defines every possible argument which uses
+ * "short" and "long" flags, e.g. -f and --foo. Positional args are not yet
+ * handled.
+ *
+ * This function is in the early stages of its implementation, and has several
+ * limitations. First, in the current implementation, a short flag may not have
+ * an argument. Second, in the current implementation, positional arguments
+ * must all occur after the short/long flags. Third, this implementation does
+ * not check whether required arguments have all been seen.
+ *
+ * @param argspec An array of sc_arg structures describing arguments
+ * @param argc The count of arguments (should not include program name)
+ * @param argv Array of arguments (should not include program name)
+ * @returns The number of arguments processed
+ */
int sc_argparse(struct sc_arg argspec[], int argc, char **argv);
-int sc_argsort(struct sc_arg argspec[], int argc, char **argv);
+/**
+ * Define a string argument with a default value. It is not required to appear
+ * on the command line.
+ */
#define SC_ARG_DEF_STRING(flag, name, default_, help) \
((struct sc_arg){flag, false, SC_ARG_TYPE_STRING, name, help, default_})
+/**
+ * Define a string argument without a default value. It is required to appear on
+ * the command line.
+ */
#define SC_ARG_STRING(flag, name, help) \
((struct sc_arg){flag, true, SC_ARG_TYPE_STRING, name, help})
+/**
+ * Define an integer argument with a default value. It is not required to appear
+ * on the command line.
+ */
#define SC_ARG_DEF_INT(flag, name, default_, help) \
((struct sc_arg){flag, false, SC_ARG_TYPE_INT, name, help, NULL, default_})
+/**
+ * Define an integer argument without a default value. It is required to appear
+ * on the command line.
+ */
#define SC_ARG_INT(flag, name, help) \
((struct sc_arg){flag, true, SC_ARG_TYPE_INT, name, help})
+/**
+ * Define an count argument. It is not required to appear on the command line.
+ */
#define SC_ARG_COUNT(flag, name, help) \
((struct sc_arg){flag, false, SC_ARG_TYPE_COUNT, name, help})
+/**
+ * Signals the end of the argument specification.
+ */
#define SC_ARG_END() \
((struct sc_arg){0})
@@ 11,18 11,18 @@
enum error {
NO_ERROR,
UNKNOWN_SHORT_FLAG,
+ UNKNOWN_LONG_FLAG,
SHORT_FLAG_WITH_PARAM_NOT_AT_END,
SHORT_FLAG_MISSING_VAL,
LONG_FLAG_NO_PARAM_HAS_PARAM,
LONG_FLAG_MISSING_VAL,
+ STOP_PROCESSING,
};
enum arg {
- ERROR,
POSITIONAL, /* a positional arg */
FLAG_ONE, /* flag like "-c", or "--key=val" */
FLAG_TWO, /* flag like "-o filename" or "--key value" */
- STOP, /* -- */
};
struct state {
@@ 40,6 40,7 @@ static struct sc_arg *sc_get_arg_by_flag(struct sc_arg *argspec, char flag)
if (argspec[i].flag == flag)
return &argspec[i];
+ return NULL;
}
static struct sc_arg *sc_get_arg_by_name(struct sc_arg *argspec,
@@ 49,6 50,8 @@ static struct sc_arg *sc_get_arg_by_name(struct sc_arg *argspec,
for (i = 0; argspec[i].type != SC_ARG_TYPE_END; i++)
if (strcmp(name, argspec[i].name) == 0)
return &argspec[i];
+
+ return NULL;
}
static enum error sc_process_short_flag(struct state *state, int i)
@@ 101,7 104,7 @@ static enum error sc_process_long_flag(struct state *state)
*equal = '=';
}
if (!arg_struct)
- return ERROR;
+ return UNKNOWN_LONG_FLAG;
arg_struct->seen = true;
@@ 132,14 135,12 @@ static enum error sc_process_arg(struct state *state)
{
int i;
enum error err = NO_ERROR;
- struct sc_arg *arg_struct;
- char *equal;
char *arg = state->argv[state->argidx];
if (strcmp(arg, "-") == 0) {
state->argv[state->pos++] = state->argv[state->argidx];
} else if (strcmp(arg, "--") == 0) {
- return STOP;
+ return STOP_PROCESSING;
} else if (arg[0] == '-' && arg[1] != '-') {
/* Sigle hyphen means we're dealing with short character flags.
* Iterate over each one.
@@ 169,7 170,7 @@ int sc_argparse(struct sc_arg argspec[], int argc, char **argv)
err = sc_process_arg(&state);
if (err == NO_ERROR) {
continue;
- } else if (err == STOP) {
+ } else if (err == STOP_PROCESSING) {
break;
} else {
printf("ERROR: %d\n", err);
@@ 197,16 198,17 @@ enum my_args {
NUM_ARGS,
};
-void main(int argc, char **argv)
+int main(int argc, char **argv)
{
struct sc_arg args[NUM_ARGS];
int rv;
args[ARG_HOST] = SC_ARG_STRING('H', "--host", "host to connect");
args[ARG_PORT] = SC_ARG_DEF_INT('p', "--port", 80, "port to connect");
args[ARG_VERBOSE] = SC_ARG_COUNT('v', "--verbose", "print more");
+ args[NUM_ARGS-1] = SC_ARG_END();
if ((rv = sc_argparse(args, argc-1, argv+1)) >= 0) {
- printf("Parsing successful!\n", rv);
+ printf("Parsing successful!\n");
printf("Host: %s\n", args[ARG_HOST].val_string);
printf("Port: %d\n", args[ARG_PORT].val_int);
printf("Verbose: %d\n", args[ARG_VERBOSE].val_int);