git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] Add a simple option parser.

From
Johannes Schindelin <johannes.schindelin@gmx.de>
Date
Oct 13, 2007, 14:39 UTC
Message-ID
<Pine.LNX.4.64.0710131519510.25221@racer.site>
In-Reply-To
<1192282153-26684-2-git-send-email-madcoder@debian.org>
Hi,
On Sat, 13 Oct 2007, Pierre Habouzit wrote:
> Aggregation of single switches is allowed:
>   -rC0 is the same as -r -C 0 (supposing that -C wants an arg).
I'd be more interested in "-rC 0" working...  Is that supported, too?
Show 29 quoted lines
> diff --git a/parse-options.c b/parse-options.c
> new file mode 100644
> index 0000000..07abb50
> --- /dev/null
> +++ b/parse-options.c
> @@ -0,0 +1,227 @@
> +#include "git-compat-util.h"
> +#include "parse-options.h"
> +#include "strbuf.h"
> +
> +#define OPT_SHORT 1
> +#define OPT_UNSET 2
> +
> +struct optparse_t {
> +	const char **argv;
> +	int argc;
> +	const char *opt;
> +};
> +
> +static inline const char *get_arg(struct optparse_t *p)
> +{
> +	if (p->opt) {
> +		const char *res = p->opt;
> +		p->opt = NULL;
> +		return res;
> +	}
> +	p->argc--;
> +	return *++p->argv;
> +}

This is only used once; I wonder if it is really that more readable having this as a function in its own right.

> +static inline const char *skippfx(const char *str, const char *prefix)

Personally, I do not like abbreviations like that. They do not save that much screen estate (skip_prefix is only 4 characters longer, but much more readable). Same goes for "cnt" later.

Show 41 quoted lines
> +static int get_value(struct optparse_t *p, struct option *opt, int flags)
> +{
> +	if (p->opt && (flags & OPT_UNSET))
> +		return opterror(opt, "takes no value", flags);
> +
> +	switch (opt->type) {
> +	case OPTION_BOOLEAN:
> +		if (!(flags & OPT_SHORT) && p->opt)
> +			return opterror(opt, "takes no value", flags);
> +		if (flags & OPT_UNSET) {
> +			*(int *)opt->value = 0;
> +		} else {
> +			(*(int *)opt->value)++;
> +		}
> +		return 0;
> +
> +	case OPTION_STRING:
> +		if (flags & OPT_UNSET) {
> +			*(const char **)opt->value = (const char *)NULL;
> +		} else {
> +			if (!p->opt && p->argc < 1)
> +				return opterror(opt, "requires a value", flags);
> +			*(const char **)opt->value = get_arg(p);
> +		}
> +		return 0;
> +
> +	case OPTION_INTEGER:
> +		if (flags & OPT_UNSET) {
> +			*(int *)opt->value = 0;
> +		} else {
> +			const char *s;
> +			if (!p->opt && p->argc < 1)
> +				return opterror(opt, "requires a value", flags);
> +			*(int *)opt->value = strtol(*p->argv, (char **)&s, 10);
> +			if (*s)
> +				return opterror(opt, "expects a numerical value", flags);
> +		}
> +		return 0;
> +
> +	default:
> +		die("should not happen, someone must be hit on the forehead");
:-P
Show 17 quoted lines
> +static int parse_long_opt(struct optparse_t *p, const char *arg,
> +                          struct option *options, int count)
> +{
> +	int i;
> +
> +	for (i = 0; i < count; i++) {
> +		const char *rest;
> +		int flags = 0;
> +		
> +		if (!options[i].long_name)
> +			continue;
> +
> +		rest = skippfx(arg, options[i].long_name);
> +		if (!rest && !strncmp(arg, "no-", 3)) {
> +			rest = skippfx(arg + 3, options[i].long_name);
> +			flags |= OPT_SHORT;
> +		}
Would this not be more intuitive as
		if (!prefixcmp(arg, "no-")) {
			arg += 3;
			flags |= OPT_UNSET;
		}
		rest = skip_prefix(arg, options[i].long_name);
Hm?  (Note that I say UNSET, not SHORT... ;-)
Show 5 quoted lines
> +		if (!rest)
> +			continue;
> +		if (*rest) {
> +			if (*rest != '=')
> +				continue;

Is this really no error? For example, "git log --decorate-walls-and-roofs" would not fail...

> +int parse_options(int argc, const char **argv,
> +                  struct option *options, int count,
> +				  const char * const usagestr[], int flags)
Please indent by the same amount.
Show 5 quoted lines
> +		if (arg[1] != '-') {
> +			optp.opt = arg + 1;
> +			do {
> +				if (*optp.opt == 'h')
> +					make_usage(usagestr, options, count);

How about calling this "usage_with_options()"? With that name I expected make_usage() to return a strbuf.

> +		if (!arg[2]) { /* "--" */
> +			if (!(flags & OPT_COPY_DASHDASH))
> +				optp.argc--, optp.argv++;
I would prefer this as 
			if (!(flags & OPT_COPY_DASHDASH)) {
				optp.argc--;
				optp.argv++;
			}

While I'm at it: could you use "args" instead of "optp"? It is misleading both in that it not only contains options (but other arguments, too) as in that it is not a pointer (the trailing "p" is used as an indicator of that very often, including git's source code).

In the same vein, OPT_COPY_DASHDASH should be named PARSE_OPT_KEEP_DASHDASH.

Show 7 quoted lines
> +		if (opts->short_name) {
> +			strbuf_addf(&sb, "-%c", opts->short_name);
> +		}
> +		if (opts->long_name) {
> +			strbuf_addf(&sb, opts->short_name ? ", --%s" : "--%s",
> +						opts->long_name);
> +		}
Please lose the curly brackets.
Show 7 quoted lines
> +		if (sb.len - pos <= USAGE_OPTS_WIDTH) {
> +			int pad = USAGE_OPTS_WIDTH - (sb.len - pos) + USAGE_GAP;
> +			strbuf_addf(&sb, "%*s%s\n", pad, "", opts->help);
> +		} else {
> +			strbuf_addf(&sb, "\n%*s%s\n", USAGE_OPTS_WIDTH + USAGE_GAP, "",
> +						opts->help);
> +		}
Same here.  (And I'd also make sure that the lines are not that long.)
Show 11 quoted lines
> diff --git a/parse-options.h b/parse-options.h
> new file mode 100644
> index 0000000..4b33d17
> --- /dev/null
> +++ b/parse-options.h
> @@ -0,0 +1,37 @@
> +#ifndef PARSE_OPTIONS_H
> +#define PARSE_OPTIONS_H
> +
> +enum option_type {
> +	OPTION_BOOLEAN,

I know that I proposed "BOOLEAN", but actually, you use it more like an "INCREMENTAL", right?

Other than that: I like it very much.

Ciao, Dscho

Previous: Benoit SIGOURENext: Pierre Habouzit
Message 29 of 42 in “[RFC] CLI option parsing and usage generation for porcelains”
  1. Pierre HabouzitOct 13, 2007
  2. Wincent ColaiutaOct 13, 2007
  3. Eric WongOct 14, 2007
  4. Pierre HabouzitOct 14, 2007
  5. parse-options: Allow abbreviated options when unambiguousJohannes Schindelin, Oct 14, 2007
  6. Johannes SchindelinOct 14, 2007
  7. Pierre HabouzitOct 14, 2007
  8. Eric WongOct 14, 2007
  9. Johannes SchindelinOct 14, 2007
  10. Eric WongOct 14, 2007
  11. git-svn and submodules, was Re: [PATCH] parse-options: Allow abbreviated options when unambiguousJohannes Schindelin, Oct 14, 2007
  12. Benoit SIGOUREOct 15, 2007
  13. Andreas EricssonOct 15, 2007
  14. Benoit SIGOUREOct 15, 2007
  15. David KastrupOct 15, 2007
  16. Benoit SIGOUREOct 15, 2007
  17. Andreas EricssonOct 15, 2007
  18. Karl HasselströmOct 15, 2007
  19. .gitignore and svn:ignore [WAS: git-svn and submodules]Chris Shoemaker, Oct 15, 2007
  20. Eric WongOct 16, 2007
  21. Karl HasselströmOct 16, 2007
  22. Chris ShoemakerOct 16, 2007
  23. Linus TorvaldsOct 15, 2007
  24. Performance issue with excludes (was: Re: git-svn and submodules)Benoit SIGOURE, Oct 15, 2007
  25. Linus TorvaldsOct 15, 2007
  26. Benoit SIGOUREOct 15, 2007
  27. Linus TorvaldsOct 15, 2007
  28. Benoit SIGOUREOct 15, 2007
  29. Johannes SchindelinOct 13, 2007
  30. Pierre HabouzitOct 13, 2007
  31. Johannes SchindelinOct 13, 2007
  32. Pierre HabouzitOct 13, 2007
  33. Alex RiesenOct 13, 2007
  34. Pierre HabouzitOct 13, 2007
  35. Alex RiesenOct 13, 2007
  36. Pierre HabouzitOct 13, 2007
  37. Alex RiesenOct 13, 2007
  38. Pierre HabouzitOct 14, 2007
  39. Simplify usage string printingJonas Fonseca, Oct 14, 2007
  40. Pierre HabouzitOct 14, 2007
  41. Update manpages to reflect new short and long option aliasesJonas Fonseca, Oct 14, 2007
  42. Pierre HabouzitOct 14, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.