/copt

Primary LanguageCMIT LicenseMIT

copt

I recently wrote an AI program that that can submit queries to multiple different AI providers. Each backend is defined in its own c module that conforms to a common interface. I wanted these modules to be able to have their own command line options, since some options might only make sense for a particular backend. It seemed like it would be best if those options were defined in the c module to which they belonged.

For chewie, I implemented this such that each module defines an array of NULL-terminated option specifications that are merged together before all options are processed. I wanted a more efficient way of doing this. I realize it doesn't matter so much, the time to do the merge and process the arguments isn't critical, but it still bugged me. copt is my solution. It uses the same option data structures and algorithm, but it allows options to be defined in multiple modules and merged together during link time, not at runtime.

This is accomplished by putting the different option arrays in a special section. The section layout is defined in copt.ld and should be included in the linker command with -Tcopt.ld. copt.h exports a copt_options_t macro for declaring the array of options in the appropriate section:

copt_options_t options[] = {
    &opt_a,
    &opt_b,
    &opt_c
};

copt_t, which is the type for each individual option element, doesn't need to be defined in a special section:

copt_t opt_a = {
    .option = "-a",
    .description = "option a",
    .type = COPT_ARG_NONE,
    .value = NULL,
    .present = false,
    .validate = validate_a
};

copt_t opt_b = {
    .option = "b",
    .description = "option b",
    .type = COPT_ARG_REQUIRED,
    .value = NULL,
    .present = false,
    .validate = validate_b
};

copt_t opt_c = {
    .option = "c",
    .description = "option c",
    .type = COPT_ARG_OPTIONAL,
    .value = NULL,
    .present = false,
    .validate = validate_c,
    .missing = missing_c
};

The copt_options_t type is defined with a used attribute so that the data is not optimized away if -flto is used. During link-time, two variables __start_copt and __stop_copt define the start and stop of the merged options array. This isn't of concern to the application, so much, but it is used internally by copt.c to access the merged array of options.

The copt_t has the following structure:

typedef struct copt_t {
    const char *option;
    const char *description;
    const char *value;
    copt_error_code_t (*validate)(struct copt_t *option, void *user_data);
    copt_error_code_t (*missing)(struct copt_t *option, void *user_data);
    copt_arg_t type;
    bool present;
} copt_t;

The option field is the option specification. It can be NULL, in which case the copt_t element will not be used for option processing, but its description will contribute to the option help output.

description is the explanatory text that will be printed for this particular option.

value should be initialized to NULL. It will be set to the value passed on the command line for the option, if any.

validate is an optional function that will be called when this particular option is encountered. It returns a copt_error_code_t. If the return code is anything other than COPT_OK, option parsing will stop and copt_parse() will return the error code.

missing is an optional function that will be called if this particular option is not given on the command line. It must return a copt_error_code_t that is returned immediately to the caller of copt_parse() if it is any code other than COPT_OK.

type is the argument type for this particular option:

  • COPT_ARG_NONE for no argument taken.
  • COPT_ARG_REQUIRED should be used if an argument is required for this option.
  • COPT_ARG_OPTIONAL should be used if an argument can be present, but isn't required.
  • COPT_APPLICATION_ERROR the application can return this value or above to stop processing and return the code to the application.

present should be initialized to false. It will be set to true only if the option was given on the command line.

Once all of the data structures are defined, command line parsing is initiated simply by called the copt_parse() function:

copt_error_t copt_parse(int argc, char **argv, void *user_data);

Simply pass it the argc and argv parameters that are passed to main(), along with an optional user_data value that is passed along to validate() and missing(). user_data is otherwise unused by copt and is present solely for the convenience of the application.

If the application needs to display command line option help, the copt_show_syntax() function can be used. It will iterate through all options, in the order the linker places them in the final executable, and displays the option value (if not NULL) along with the description value (if not NULL).