~brenns10/sc-argparse

92ec9c0f1080644439eb2b1df24ee2027a2509e2 — Stephen Brennan 4 years ago b52f213
Fix some warnings and document header
2 files changed, 130 insertions(+), 10 deletions(-)

M include/sc-argparse.h
M src/sc-argparse.c
M include/sc-argparse.h => include/sc-argparse.h +119 -1
@@ 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})


M src/sc-argparse.c => src/sc-argparse.c +11 -9
@@ 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);