Commit 9ee1f043 authored by Peter W. Draper's avatar Peter W. Draper
Browse files

Import the argparse library from https://github.com/cofyc/argparse

Provides GNU --long-name arguments, necessary as we are running out of characters. We could use the glibc library function getopt_long, but that is not POSIX
parent 54a5c390
# FAQs
## Why removing parsed command-line switches/options?
It destroys the original `argv` array, not compatible with other arguments parsing
library.
This is because this library is used for short-lived programs, e.g. cli tools
at beginning. It's very convenient to process remain arguments if we remove
parsed command-line arguments, e.g. `<comamnd> [-[s]|--switch]... arguments`.
If you want keep original `argc/argv`, you can make a copy, then pass them to
`argparse_parse`, e.g.
```c
int copy_argc = argc;
const char **copy_argv = argv;
copy_argv = malloc(copy_argc * sizeof(char *));
for (int i = 0; i < argc; i++) {
copy_argv[i] = (char *)argv[i];
}
argparse_parse(&argparse, copy_argc, copy_argv);
```
Issues:
- https://github.com/cofyc/argparse/issues/3
- https://github.com/cofyc/argparse/issues/9
## Why using `intptr_t` to hold associated data? Why not `void *`?
I choose `intptr_t` because it's a integer type which also can be used to hold
a pointer value. Most of the time, we only need a integer to hold
user-provided value, see `OPT_BIT` as example. If you want to provide a pointer
which points to a large amount of data, you can cast it to `intptr_t` and cast
it back to original pointer in callback function.
The MIT License (MIT)
Copyright (c) 2012-2013 Yecheng Fu <cofyc.jackson@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
approvers:
- cofyc
# argparse [![Build Status](https://travis-ci.org/cofyc/argparse.png)](https://travis-ci.org/cofyc/argparse)
argparse - A command line arguments parsing library in C (compatible with C++).
## Description
This module is inspired by parse-options.c (git) and python's argparse
module.
Arguments parsing is common task in cli program, but traditional `getopt`
libraries are not easy to use. This library provides high-level arguments
parsing solutions.
The program defines what arguments it requires, and `argparse` will figure
out how to parse those out of `argc` and `argv`, it also automatically
generates help and usage messages and issues errors when users give the
program invalid arguments.
## Features
- handles both optional and positional arguments
- produces highly informative usage messages
- issues errors when given invalid arguments
There are basically three types of options:
- boolean options
- options with mandatory argument
- options with optional argument
There are basically two forms of options:
- short option consist of one dash (`-`) and one alphanumeric character.
- long option begin with two dashes (`--`) and some alphanumeric characters.
Short options may be bundled, e.g. `-a -b` can be specified as `-ab`.
Options are case-sensitive.
Options and non-option arguments can clearly be separated using the `--` option.
## Examples
```c
#include "argparse.h"
static const char *const usage[] = {
"test_argparse [options] [[--] args]",
"test_argparse [options]",
NULL,
};
#define PERM_READ (1<<0)
#define PERM_WRITE (1<<1)
#define PERM_EXEC (1<<2)
int
main(int argc, const char **argv)
{
int force = 0;
int test = 0;
int num = 0;
const char *path = NULL;
int perms = 0;
struct argparse_option options[] = {
OPT_HELP(),
OPT_GROUP("Basic options"),
OPT_BOOLEAN('f', "force", &force, "force to do"),
OPT_BOOLEAN('t', "test", &test, "test only"),
OPT_STRING('p', "path", &path, "path to read"),
OPT_INTEGER('n', "num", &num, "selected num"),
OPT_GROUP("Bits options"),
OPT_BIT(0, "read", &perms, "read perm", NULL, PERM_READ, OPT_NONEG),
OPT_BIT(0, "write", &perms, "write perm", NULL, PERM_WRITE),
OPT_BIT(0, "exec", &perms, "exec perm", NULL, PERM_EXEC),
OPT_END(),
};
struct argparse argparse;
argparse_init(&argparse, options, usage, 0);
argparse_describe(&argparse, "\nA brief description of what the program does and how it works.", "\nAdditional description of the program after the description of the arguments.");
argc = argparse_parse(&argparse, argc, argv);
if (force != 0)
printf("force: %d\n", force);
if (test != 0)
printf("test: %d\n", test);
if (path != NULL)
printf("path: %s\n", path);
if (num != 0)
printf("num: %d\n", num);
if (argc != 0) {
printf("argc: %d\n", argc);
int i;
for (i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, *(argv + i));
}
}
if (perms) {
printf("perms: %d\n", perms);
}
return 0;
}
```
/**
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
* All rights reserved.
*
* Use of this source code is governed by a MIT-style license that can be found
* in the LICENSE file.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "argparse.h"
#define OPT_UNSET 1
#define OPT_LONG (1 << 1)
static const char *
prefix_skip(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
static int
prefix_cmp(const char *str, const char *prefix)
{
for (;; str++, prefix++)
if (!*prefix) {
return 0;
} else if (*str != *prefix) {
return (unsigned char)*prefix - (unsigned char)*str;
}
}
static void
argparse_error(struct argparse *self, const struct argparse_option *opt,
const char *reason, int flags)
{
(void)self;
if (flags & OPT_LONG) {
fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
} else {
fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
}
exit(1);
}
static int
argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
int flags)
{
const char *s = NULL;
if (!opt->value)
goto skipped;
switch (opt->type) {
case ARGPARSE_OPT_BOOLEAN:
if (flags & OPT_UNSET) {
*(int *)opt->value = *(int *)opt->value - 1;
} else {
*(int *)opt->value = *(int *)opt->value + 1;
}
if (*(int *)opt->value < 0) {
*(int *)opt->value = 0;
}
break;
case ARGPARSE_OPT_BIT:
if (flags & OPT_UNSET) {
*(int *)opt->value &= ~opt->data;
} else {
*(int *)opt->value |= opt->data;
}
break;
case ARGPARSE_OPT_STRING:
if (self->optvalue) {
*(const char **)opt->value = self->optvalue;
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(const char **)opt->value = *++self->argv;
} else {
argparse_error(self, opt, "requires a value", flags);
}
break;
case ARGPARSE_OPT_INTEGER:
errno = 0;
if (self->optvalue) {
*(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects an integer value", flags);
break;
case ARGPARSE_OPT_FLOAT:
errno = 0;
if (self->optvalue) {
*(float *)opt->value = strtof(self->optvalue, (char **)&s);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(float *)opt->value = strtof(*++self->argv, (char **)&s);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects a numerical value", flags);
break;
default:
assert(0);
}
skipped:
if (opt->callback) {
return opt->callback(self, opt);
}
return 0;
}
static void
argparse_options_check(const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
switch (options->type) {
case ARGPARSE_OPT_END:
case ARGPARSE_OPT_BOOLEAN:
case ARGPARSE_OPT_BIT:
case ARGPARSE_OPT_INTEGER:
case ARGPARSE_OPT_FLOAT:
case ARGPARSE_OPT_STRING:
case ARGPARSE_OPT_GROUP:
continue;
default:
fprintf(stderr, "wrong option type: %d", options->type);
break;
}
}
}
static int
argparse_short_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
if (options->short_name == *self->optvalue) {
self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
return argparse_getvalue(self, options, 0);
}
}
return -2;
}
static int
argparse_long_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
const char *rest;
int opt_flags = 0;
if (!options->long_name)
continue;
rest = prefix_skip(self->argv[0] + 2, options->long_name);
if (!rest) {
// negation disabled?
if (options->flags & OPT_NONEG) {
continue;
}
// only OPT_BOOLEAN/OPT_BIT supports negation
if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
ARGPARSE_OPT_BIT) {
continue;
}
if (prefix_cmp(self->argv[0] + 2, "no-")) {
continue;
}
rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
if (!rest)
continue;
opt_flags |= OPT_UNSET;
}
if (*rest) {
if (*rest != '=')
continue;
self->optvalue = rest + 1;
}
return argparse_getvalue(self, options, opt_flags | OPT_LONG);
}
return -2;
}
int
argparse_init(struct argparse *self, struct argparse_option *options,
const char *const *usages, int flags)
{
memset(self, 0, sizeof(*self));
self->options = options;
self->usages = usages;
self->flags = flags;
self->description = NULL;
self->epilog = NULL;
return 0;
}
void
argparse_describe(struct argparse *self, const char *description,
const char *epilog)
{
self->description = description;
self->epilog = epilog;
}
int
argparse_parse(struct argparse *self, int argc, const char **argv)
{
self->argc = argc - 1;
self->argv = argv + 1;
self->out = argv;
argparse_options_check(self->options);
for (; self->argc; self->argc--, self->argv++) {
const char *arg = self->argv[0];
if (arg[0] != '-' || !arg[1]) {
if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
goto end;
}
// if it's not option or is a single char '-', copy verbatim
self->out[self->cpidx++] = self->argv[0];
continue;
}
// short option
if (arg[1] != '-') {
self->optvalue = arg + 1;
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
while (self->optvalue) {
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
}
continue;
}
// if '--' presents
if (!arg[2]) {
self->argc--;
self->argv++;
break;
}
// long option
switch (argparse_long_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
continue;
unknown:
fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
argparse_usage(self);
exit(1);
}
end:
memmove(self->out + self->cpidx, self->argv,
self->argc * sizeof(*self->out));
self->out[self->cpidx + self->argc] = NULL;
return self->cpidx + self->argc;
}
void
argparse_usage(struct argparse *self)
{
if (self->usages) {
fprintf(stdout, "Usage: %s\n", *self->usages++);
while (*self->usages && **self->usages)
fprintf(stdout, " or: %s\n", *self->usages++);
} else {
fprintf(stdout, "Usage:\n");
}
// print description
if (self->description)
fprintf(stdout, "%s\n", self->description);
fputc('\n', stdout);
const struct argparse_option *options;
// figure out best width
size_t usage_opts_width = 0;
size_t len;
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
len = 0;
if ((options)->short_name) {
len += 2;
}
if ((options)->short_name && (options)->long_name) {
len += 2; // separator ", "
}
if ((options)->long_name) {
len += strlen((options)->long_name) + 2;
}
if (options->type == ARGPARSE_OPT_INTEGER) {
len += strlen("=<int>");
}
if (options->type == ARGPARSE_OPT_FLOAT) {
len += strlen("=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
len += strlen("=<str>");
}
len = (len + 3) - ((len + 3) & 3);
if (usage_opts_width < len) {
usage_opts_width = len;
}
}
usage_opts_width += 4; // 4 spaces prefix
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
size_t pos = 0;
int pad = 0;
if (options->type == ARGPARSE_OPT_GROUP) {
fputc('\n', stdout);
fprintf(stdout, "%s", options->help);
fputc('\n', stdout);
continue;
}
pos = fprintf(stdout, " ");
if (options->short_name) {
pos += fprintf(stdout, "-%c", options->short_name);
}
if (options->long_name && options->short_name) {
pos += fprintf(stdout, ", ");
}
if (options->long_name) {
pos += fprintf(stdout, "--%s", options->long_name);
}
if (options->type == ARGPARSE_OPT_INTEGER) {
pos += fprintf(stdout, "=<int>");
}
if (options->type == ARGPARSE_OPT_FLOAT) {
pos += fprintf(stdout, "=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
pos += fprintf(stdout, "=<str>");
}
if (pos <= usage_opts_width) {
pad = usage_opts_width - pos;
} else {
fputc('\n', stdout);
pad = usage_opts_width;
}
fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
}
// print epilog
if (self->epilog)
fprintf(stdout, "%s\n", self->epilog);
}
int
argparse_help_cb(struct argparse *self, const struct argparse_option *option)
{
(void)option;
argparse_usage(self);
exit(0);
}
/**
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
* All rights reserved.
*
* Use of this source code is governed by a MIT-style license that can be found
* in the LICENSE file.
*/
#ifndef ARGPARSE_H
#define ARGPARSE_H
/* For c++ compatibility */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct argparse;
struct argparse_option;
typedef int argparse_callback (struct argparse *self,
const struct argparse_option *option);
enum argparse_flag {
ARGPARSE_STOP_AT_NON_OPTION = 1,
};
enum argparse_option_type {
/* special */
ARGPARSE_OPT_END,
ARGPARSE_OPT_GROUP,
/* options with no arguments */
ARGPARSE_OPT_BOOLEAN,
ARGPARSE_OPT_BIT,
/* options with arguments (optional or required) */
ARGPARSE_OPT_INTEGER,
ARGPARSE_OPT_FLOAT,
ARGPARSE_OPT_STRING,
};
enum argparse_option_flags {
OPT_NONEG = 1, /* disable negation */
};
/**
* argparse option
*
* `type`:
* holds the type of the option, you must have an ARGPARSE_OPT_END last in your
* array.
*
* `short_name`:
* the character to use as a short option name, '\0' if none.
*
* `long_name`:
* the long option name, without the leading dash, NULL if none.
*
* `value`:
* stores pointer to the value to be filled.
*
* `help`:
* the short help message associated to what the option does.
* Must never be NULL (except for ARGPARSE_OPT_END).
*
* `callback`:
* function is called when corresponding argument is parsed.
*
* `data`:
* associated data. Callbacks can use it like they want.