mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-12-22 17:34:54 +01:00
733ccfabca
Now that it's easy to add argument specific modifiers in opt descriptions, add a new .allow_empty field to allow empty strings for individual string arguments while retaining strict checks elsewhere. Use this for notmuch insert --folder, where the empty string means top level folder.
251 lines
6.1 KiB
C
251 lines
6.1 KiB
C
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "error_util.h"
|
|
#include "command-line-arguments.h"
|
|
|
|
/*
|
|
Search the array of keywords for a given argument, assigning the
|
|
output variable to the corresponding value. Return false if nothing
|
|
matches.
|
|
*/
|
|
|
|
static bool
|
|
_process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
|
|
|
|
const notmuch_keyword_t *keywords;
|
|
|
|
if (next == '\0') {
|
|
/* No keyword given */
|
|
arg_str = "";
|
|
}
|
|
|
|
for (keywords = arg_desc->keywords; keywords->name; keywords++) {
|
|
if (strcmp (arg_str, keywords->name) != 0)
|
|
continue;
|
|
|
|
if (arg_desc->opt_flags)
|
|
*arg_desc->opt_flags |= keywords->value;
|
|
else
|
|
*arg_desc->opt_keyword = keywords->value;
|
|
|
|
return true;
|
|
}
|
|
if (next != '\0')
|
|
fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name);
|
|
else
|
|
fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name);
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
_process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
|
|
bool value;
|
|
|
|
if (next == '\0' || strcmp (arg_str, "true") == 0) {
|
|
value = true;
|
|
} else if (strcmp (arg_str, "false") == 0) {
|
|
value = false;
|
|
} else {
|
|
fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
|
|
return false;
|
|
}
|
|
|
|
*arg_desc->opt_bool = value;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
_process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
|
|
|
|
char *endptr;
|
|
if (next == '\0' || arg_str[0] == '\0') {
|
|
fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name);
|
|
return false;
|
|
}
|
|
|
|
*arg_desc->opt_int = strtol (arg_str, &endptr, 10);
|
|
if (*endptr == '\0')
|
|
return true;
|
|
|
|
fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
|
|
arg_str, arg_desc->name);
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
_process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
|
|
|
|
if (next == '\0') {
|
|
fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name);
|
|
return false;
|
|
}
|
|
if (arg_str[0] == '\0' && ! arg_desc->allow_empty) {
|
|
fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
|
|
return false;
|
|
}
|
|
*arg_desc->opt_string = arg_str;
|
|
return true;
|
|
}
|
|
|
|
/* Return number of non-NULL opt_* fields in opt_desc. */
|
|
static int _opt_set_count (const notmuch_opt_desc_t *opt_desc)
|
|
{
|
|
return
|
|
!!opt_desc->opt_inherit +
|
|
!!opt_desc->opt_bool +
|
|
!!opt_desc->opt_int +
|
|
!!opt_desc->opt_keyword +
|
|
!!opt_desc->opt_flags +
|
|
!!opt_desc->opt_string +
|
|
!!opt_desc->opt_position;
|
|
}
|
|
|
|
/* Return true if opt_desc is valid. */
|
|
static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
|
|
{
|
|
int n = _opt_set_count (opt_desc);
|
|
|
|
if (n > 1)
|
|
INTERNAL_ERROR ("more than one non-NULL opt_* field for argument \"%s\"",
|
|
opt_desc->name);
|
|
|
|
return n > 0;
|
|
}
|
|
|
|
/*
|
|
Search for the {pos_arg_index}th position argument, return false if
|
|
that does not exist.
|
|
*/
|
|
|
|
bool
|
|
parse_position_arg (const char *arg_str, int pos_arg_index,
|
|
const notmuch_opt_desc_t *arg_desc) {
|
|
|
|
int pos_arg_counter = 0;
|
|
while (_opt_valid (arg_desc)) {
|
|
if (arg_desc->opt_position) {
|
|
if (pos_arg_counter == pos_arg_index) {
|
|
*arg_desc->opt_position = arg_str;
|
|
if (arg_desc->present)
|
|
*arg_desc->present = true;
|
|
return true;
|
|
}
|
|
pos_arg_counter++;
|
|
}
|
|
arg_desc++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Search for a non-positional (i.e. starting with --) argument matching arg,
|
|
* parse a possible value, and assign to *output_var
|
|
*/
|
|
|
|
int
|
|
parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_index)
|
|
{
|
|
assert(argv);
|
|
|
|
const char *_arg = argv[opt_index];
|
|
|
|
assert(_arg);
|
|
assert(options);
|
|
|
|
const char *arg = _arg + 2; /* _arg starts with -- */
|
|
const notmuch_opt_desc_t *try;
|
|
|
|
const char *next_arg = NULL;
|
|
if (opt_index < argc - 1 && strncmp (argv[opt_index + 1], "--", 2) != 0)
|
|
next_arg = argv[opt_index + 1];
|
|
|
|
for (try = options; _opt_valid (try); try++) {
|
|
if (try->opt_inherit) {
|
|
int new_index = parse_option (argc, argv, try->opt_inherit, opt_index);
|
|
if (new_index >= 0)
|
|
return new_index;
|
|
}
|
|
|
|
if (! try->name)
|
|
continue;
|
|
|
|
if (strncmp (arg, try->name, strlen (try->name)) != 0)
|
|
continue;
|
|
|
|
char next = arg[strlen (try->name)];
|
|
const char *value = arg + strlen(try->name) + 1;
|
|
|
|
/*
|
|
* If we have not reached the end of the argument (i.e. the
|
|
* next character is not a space or delimiter) then the
|
|
* argument could still match a longer option name later in
|
|
* the option table.
|
|
*/
|
|
if (next != '=' && next != ':' && next != '\0')
|
|
continue;
|
|
|
|
if (next == '\0' && next_arg != NULL && ! try->opt_bool) {
|
|
next = ' ';
|
|
value = next_arg;
|
|
opt_index ++;
|
|
}
|
|
|
|
bool opt_status = false;
|
|
if (try->opt_keyword || try->opt_flags)
|
|
opt_status = _process_keyword_arg (try, next, value);
|
|
else if (try->opt_bool)
|
|
opt_status = _process_boolean_arg (try, next, value);
|
|
else if (try->opt_int)
|
|
opt_status = _process_int_arg (try, next, value);
|
|
else if (try->opt_string)
|
|
opt_status = _process_string_arg (try, next, value);
|
|
else
|
|
INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
|
|
|
|
if (! opt_status)
|
|
return -1;
|
|
|
|
if (try->present)
|
|
*try->present = true;
|
|
|
|
return opt_index+1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* See command-line-arguments.h for description */
|
|
int
|
|
parse_arguments (int argc, char **argv,
|
|
const notmuch_opt_desc_t *options, int opt_index) {
|
|
|
|
int pos_arg_index = 0;
|
|
bool more_args = true;
|
|
|
|
while (more_args && opt_index < argc) {
|
|
if (strncmp (argv[opt_index],"--",2) != 0) {
|
|
|
|
more_args = parse_position_arg (argv[opt_index], pos_arg_index, options);
|
|
|
|
if (more_args) {
|
|
pos_arg_index++;
|
|
opt_index++;
|
|
}
|
|
|
|
} else {
|
|
int prev_opt_index = opt_index;
|
|
|
|
if (strlen (argv[opt_index]) == 2)
|
|
return opt_index+1;
|
|
|
|
opt_index = parse_option (argc, argv, options, opt_index);
|
|
if (opt_index < 0) {
|
|
fprintf (stderr, "Unrecognized option: %s\n", argv[prev_opt_index]);
|
|
more_args = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return opt_index;
|
|
}
|