Skip to content

Commit 21d4783

Browse files
MadCodergitster
authored andcommitted
Add a parseopt mode to git-rev-parse to bring parse-options to shell scripts.
Signed-off-by: Pierre Habouzit <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent fe61935 commit 21d4783

File tree

2 files changed

+200
-2
lines changed

2 files changed

+200
-2
lines changed

Documentation/git-rev-parse.txt

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ distinguish between them.
2323

2424
OPTIONS
2525
-------
26+
--parseopt::
27+
Use `git-rev-parse` in option parsing mode (see PARSEOPT section below).
28+
29+
--keep-dash-dash::
30+
Only meaningful in `--parseopt` mode. Tells the option parser to echo
31+
out the first `--` met instead of skipping it.
32+
2633
--revs-only::
2734
Do not output flags and parameters not meant for
2835
`git-rev-list` command.
@@ -288,10 +295,75 @@ Here are a handful examples:
288295
C^@ I J F
289296
F^! D G H D F
290297

298+
PARSEOPT
299+
--------
300+
301+
In `--parseopt` mode, `git-rev-parse` helps massaging options to bring to shell
302+
scripts the same facilities C builtins have. It works as an option normalizer
303+
(e.g. splits single switches aggregate values), a bit like `getopt(1)` does.
304+
305+
It takes on the standard input the specification of the options to parse and
306+
understand, and echoes on the standard output a line suitable for `sh(1)` `eval`
307+
to replace the arguments with normalized ones. In case of error, it outputs
308+
usage on the standard error stream, and exits with code 129.
309+
310+
Input Format
311+
~~~~~~~~~~~~
312+
313+
`git-rev-parse --parseopt` input format is fully text based. It has two parts,
314+
separated by a line that contains only `--`. The lines before the separator
315+
(should be more than one) are used for the usage.
316+
The lines after the separator describe the options.
317+
318+
Each line of options has this format:
319+
320+
------------
321+
<opt_spec><arg_spec>? SP+ help LF
322+
------------
323+
324+
`<opt_spec>`::
325+
its format is the short option character, then the long option name
326+
separated by a comma. Both parts are not required, though at least one
327+
is necessary. `h,help`, `dry-run` and `f` are all three correct
328+
`<opt_spec>`.
329+
330+
`<arg_spec>`::
331+
an `<arg_spec>` tells the option parser if the option has an argument
332+
(`=`), an optional one (`?` though its use is discouraged) or none
333+
(no `<arg_spec>` in that case).
334+
335+
The remainder of the line, after stripping the spaces, is used
336+
as the help associated to the option.
337+
338+
Blank lines are ignored, and lines that don't match this specification are used
339+
as option group headers (start the line with a space to create such
340+
lines on purpose).
341+
342+
Example
343+
~~~~~~~
344+
345+
------------
346+
OPTS_SPEC="\
347+
some-command [options] <args>...
348+
349+
some-command does foo and bar!
350+
--
351+
h,help show the help
352+
353+
foo some nifty option --foo
354+
bar= some cool option --bar with an argument
355+
356+
An option group Header
357+
C? option C with an optional argument"
358+
359+
eval `echo "$OPTS_SPEC" | git-rev-parse --parseopt -- "$@" || echo exit $?`
360+
------------
361+
362+
291363
Author
292364
------
293-
Written by Linus Torvalds <[email protected]> and
294-
Junio C Hamano <[email protected]>
365+
Written by Linus Torvalds <[email protected]> .
366+
Junio C Hamano <[email protected]> and Pierre Habouzit <[email protected]>
295367

296368
Documentation
297369
--------------

builtin-rev-parse.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "refs.h"
99
#include "quote.h"
1010
#include "builtin.h"
11+
#include "parse-options.h"
1112

1213
#define DO_REVS 1
1314
#define DO_NOREV 2
@@ -209,13 +210,138 @@ static int try_difference(const char *arg)
209210
return 0;
210211
}
211212

213+
static int parseopt_dump(const struct option *o, const char *arg, int unset)
214+
{
215+
struct strbuf *parsed = o->value;
216+
if (unset)
217+
strbuf_addf(parsed, " --no-%s", o->long_name);
218+
else if (o->short_name)
219+
strbuf_addf(parsed, " -%c", o->short_name);
220+
else
221+
strbuf_addf(parsed, " --%s", o->long_name);
222+
if (arg) {
223+
strbuf_addch(parsed, ' ');
224+
sq_quote_buf(parsed, arg);
225+
}
226+
return 0;
227+
}
228+
229+
static const char *skipspaces(const char *s)
230+
{
231+
while (isspace(*s))
232+
s++;
233+
return s;
234+
}
235+
236+
static int cmd_parseopt(int argc, const char **argv, const char *prefix)
237+
{
238+
static int keep_dashdash = 0;
239+
static char const * const parseopt_usage[] = {
240+
"git-rev-parse --parseopt [options] -- [<args>...]",
241+
NULL
242+
};
243+
static struct option parseopt_opts[] = {
244+
OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
245+
"keep the `--` passed as an arg"),
246+
OPT_END(),
247+
};
248+
249+
struct strbuf sb, parsed;
250+
const char **usage = NULL;
251+
struct option *opts = NULL;
252+
int onb = 0, osz = 0, unb = 0, usz = 0;
253+
254+
strbuf_init(&parsed, 0);
255+
strbuf_addstr(&parsed, "set --");
256+
argc = parse_options(argc, argv, parseopt_opts, parseopt_usage,
257+
PARSE_OPT_KEEP_DASHDASH);
258+
if (argc < 1 || strcmp(argv[0], "--"))
259+
usage_with_options(parseopt_usage, parseopt_opts);
260+
261+
strbuf_init(&sb, 0);
262+
/* get the usage up to the first line with a -- on it */
263+
for (;;) {
264+
if (strbuf_getline(&sb, stdin, '\n') == EOF)
265+
die("premature end of input");
266+
ALLOC_GROW(usage, unb + 1, usz);
267+
if (!strcmp("--", sb.buf)) {
268+
if (unb < 1)
269+
die("no usage string given before the `--' separator");
270+
usage[unb] = NULL;
271+
break;
272+
}
273+
usage[unb++] = strbuf_detach(&sb, NULL);
274+
}
275+
276+
/* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
277+
while (strbuf_getline(&sb, stdin, '\n') != EOF) {
278+
const char *s;
279+
struct option *o;
280+
281+
if (!sb.len)
282+
continue;
283+
284+
ALLOC_GROW(opts, onb + 1, osz);
285+
memset(opts + onb, 0, sizeof(opts[onb]));
286+
287+
o = &opts[onb++];
288+
s = strchr(sb.buf, ' ');
289+
if (!s || *sb.buf == ' ') {
290+
o->type = OPTION_GROUP;
291+
o->help = xstrdup(skipspaces(s));
292+
continue;
293+
}
294+
295+
o->type = OPTION_CALLBACK;
296+
o->help = xstrdup(skipspaces(s));
297+
o->value = &parsed;
298+
o->callback = &parseopt_dump;
299+
switch (s[-1]) {
300+
case '=':
301+
s--;
302+
break;
303+
case '?':
304+
o->flags = PARSE_OPT_OPTARG;
305+
s--;
306+
break;
307+
default:
308+
o->flags = PARSE_OPT_NOARG;
309+
break;
310+
}
311+
312+
if (s - sb.buf == 1) /* short option only */
313+
o->short_name = *sb.buf;
314+
else if (sb.buf[1] != ',') /* long option only */
315+
o->long_name = xmemdupz(sb.buf, s - sb.buf);
316+
else {
317+
o->short_name = *sb.buf;
318+
o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
319+
}
320+
}
321+
strbuf_release(&sb);
322+
323+
/* put an OPT_END() */
324+
ALLOC_GROW(opts, onb + 1, osz);
325+
memset(opts + onb, 0, sizeof(opts[onb]));
326+
argc = parse_options(argc, argv, opts, usage,
327+
keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0);
328+
329+
strbuf_addf(&parsed, " --");
330+
sq_quote_argv(&parsed, argv, argc, 0);
331+
puts(parsed.buf);
332+
return 0;
333+
}
334+
212335
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
213336
{
214337
int i, as_is = 0, verify = 0;
215338
unsigned char sha1[20];
216339

217340
git_config(git_default_config);
218341

342+
if (argc > 1 && !strcmp("--parseopt", argv[1]))
343+
return cmd_parseopt(argc - 1, argv + 1, prefix);
344+
219345
for (i = 1; i < argc; i++) {
220346
const char *arg = argv[i];
221347

0 commit comments

Comments
 (0)