tig-2.2
[tig.git] / src / argv.c
blob786052c46a8ce6d73056bea315821fb7317730c0
1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/argv.h"
16 #include "tig/repo.h"
17 #include "tig/options.h"
18 #include "tig/prompt.h"
20 static bool
21 concat_argv(const char *argv[SIZEOF_ARG], char *buf, size_t buflen, const char *sep, bool quoted)
23 size_t bufpos, argc;
25 for (bufpos = 0, argc = 0; argv[argc]; argc++) {
26 const char *arg_sep = argc ? sep : "";
27 const char *arg = argv[argc];
28 int pos;
30 if (quoted && arg[(pos = strcspn(arg, " \t\""))]) {
31 if (!string_nformat(buf, buflen, &bufpos, "%s\"", arg_sep))
32 return false;
34 while (*arg) {
35 int pos = strcspn(arg, "\"");
36 const char *qesc = arg[pos] == '"' ? "\\\"" : "";
38 if (!string_nformat(buf, buflen, &bufpos, "%.*s%s", pos, arg, qesc))
39 return false;
40 arg += pos + 1;
43 if (!string_nformat(buf, buflen, &bufpos, "\""))
44 return false;
46 continue;
49 if (!string_nformat(buf, buflen, &bufpos, "%s%s", arg_sep, arg))
50 return false;
53 return true;
56 bool
57 argv_to_string_quoted(const char *argv[SIZEOF_ARG], char *buf, size_t buflen, const char *sep)
59 return concat_argv(argv, buf, buflen, sep, true);
62 bool
63 argv_to_string(const char *argv[SIZEOF_ARG], char *buf, size_t buflen, const char *sep)
65 return concat_argv(argv, buf, buflen, sep, false);
68 static char *
69 parse_arg(char **cmd, bool remove_quotes)
71 int quote = 0;
72 char *arg = *cmd;
73 char *next, *pos;
75 for (pos = next = arg; *pos; pos++) {
76 int c = *pos;
78 if (c == '"' || c == '\'') {
79 if (quote == c) {
80 quote = 0;
81 if (remove_quotes) {
82 if (pos == arg) {
83 arg++;
84 next++;
86 continue;
89 } else if (!quote) {
90 quote = c;
91 if (remove_quotes) {
92 if (pos == arg) {
93 arg++;
94 next++;
96 continue;
100 } else if (quote && c == '\\') {
101 if (remove_quotes) {
102 if (pos == arg) {
103 arg++;
104 next++;
106 } else {
107 *next++ = *pos;
109 pos++;
110 if (!*pos)
111 break;
114 if (!quote && isspace(c))
115 break;
117 *next++ = *pos;
120 if (*pos)
121 *cmd = pos + 1;
122 else
123 *cmd = pos;
124 *next = 0;
125 return (!remove_quotes || !quote) ? arg : NULL;
128 static bool
129 split_argv_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd, bool remove_quotes)
131 while (*cmd && *argc < SIZEOF_ARG) {
132 char *arg = parse_arg(&cmd, remove_quotes);
134 if (!arg)
135 break;
136 argv[(*argc)++] = arg;
137 cmd = chomp_string(cmd);
140 if (*argc < SIZEOF_ARG)
141 argv[*argc] = NULL;
142 return *argc < SIZEOF_ARG;
145 bool
146 argv_from_string_no_quotes(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
148 return split_argv_string(argv, argc, cmd, true);
151 bool
152 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
154 return split_argv_string(argv, argc, cmd, false);
157 bool
158 argv_from_env(const char **argv, const char *name)
160 char *env = argv ? getenv(name) : NULL;
161 int argc = 0;
163 if (env && *env)
164 env = strdup(env);
165 return !env || argv_from_string(argv, &argc, env);
168 void
169 argv_free(const char *argv[])
171 int argc;
173 if (!argv)
174 return;
175 for (argc = 0; argv[argc]; argc++)
176 free((void *) argv[argc]);
177 argv[0] = NULL;
180 size_t
181 argv_size(const char **argv)
183 int argc = 0;
185 while (argv && argv[argc])
186 argc++;
188 return argc;
191 bool
192 argv_contains(const char **argv, const char *arg)
194 int i;
196 for (i = 0; argv && argv[i]; i++)
197 if (!strcmp(argv[i], arg))
198 return true;
199 return false;
202 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
204 bool
205 argv_append(const char ***argv, const char *arg)
207 size_t argc = argv_size(*argv);
208 char *alloc;
210 if (!*arg && argc > 0)
211 return true;
213 if (!argv_realloc(argv, argc, 2))
214 return false;
216 alloc = strdup(arg);
218 (*argv)[argc++] = alloc;
219 (*argv)[argc] = NULL;
221 return alloc != NULL;
224 bool
225 argv_append_array(const char ***dst_argv, const char *src_argv[])
227 int i;
229 for (i = 0; src_argv && src_argv[i]; i++)
230 if (!argv_append(dst_argv, src_argv[i]))
231 return false;
232 return true;
235 bool
236 argv_copy(const char ***dst, const char *src[])
238 int argc;
240 argv_free(*dst);
241 for (argc = 0; src[argc]; argc++)
242 if (!argv_append(dst, src[argc]))
243 return false;
244 return true;
248 * Argument formatting.
251 struct format_context;
253 struct format_var {
254 const char *name;
255 size_t namelen;
256 bool (*formatter)(struct format_context *, struct format_var *);
257 void *value_ref;
258 const char *value_if_empty;
261 struct format_context {
262 struct format_var *vars;
263 size_t vars_size;
264 char buf[SIZEOF_STR];
265 size_t bufpos;
266 bool file_filter;
269 #define ARGV_ENV_INIT(type, name, ifempty, initval) initval,
271 struct argv_env argv_env = {
272 ARGV_ENV_INFO(ARGV_ENV_INIT)
275 static bool
276 format_expand_arg(struct format_context *format, const char *name, const char *end)
278 struct format_var *vars = format->vars;
279 int i;
281 if (!prefixcmp(name, "%(prompt")) {
282 const char *prompt = "Command argument: ";
283 char msgbuf[SIZEOF_STR];
284 const char *value;
285 const char *msgstart = name + STRING_SIZE("%(prompt");
286 const int msglen = end - msgstart - 1;
288 if (end && msglen > 0 && string_format(msgbuf, "%.*s", msglen, msgstart)) {
289 const char *msg = msgbuf;
291 while (isspace(*msg))
292 msg++;
293 if (*msg)
294 prompt = msg;
297 value = read_prompt(prompt);
298 if (value == NULL)
299 return false;
300 return string_format_from(format->buf, &format->bufpos, "%s", value);
303 for (i = 0; i < format->vars_size; i++) {
304 if (string_enum_compare(name, vars[i].name, vars[i].namelen))
305 continue;
307 if (vars[i].value_ref == &argv_env.file && !format->file_filter)
308 return true;
310 return vars[i].formatter(format, &vars[i]);
313 return false;
316 static bool
317 format_append_arg(struct format_context *format, const char ***dst_argv, const char *arg)
319 memset(format->buf, 0, sizeof(format->buf));
320 format->bufpos = 0;
322 while (arg) {
323 const char *var = strstr(arg, "%(");
324 const char *closing = var ? strchr(var, ')') : NULL;
325 const char *next = closing ? closing + 1 : NULL;
326 const int len = var ? var - arg : strlen(arg);
328 if (var && !closing)
329 return false;
331 if (len && !string_format_from(format->buf, &format->bufpos, "%.*s", len, arg))
332 return false;
334 if (var && !format_expand_arg(format, var, next))
335 return false;
337 arg = next;
340 return argv_append(dst_argv, format->buf);
343 static bool
344 format_append_argv(struct format_context *format, const char ***dst_argv, const char *src_argv[])
346 int argc;
348 if (!src_argv)
349 return true;
351 for (argc = 0; src_argv[argc]; argc++)
352 if (!format_append_arg(format, dst_argv, src_argv[argc]))
353 return false;
355 return src_argv[argc] == NULL;
358 static bool
359 argv_string_formatter(struct format_context *format, struct format_var *var)
361 argv_string *value_ref = var->value_ref;
362 const char *value = *value_ref;
364 if (!*value)
365 value = var->value_if_empty;
367 if (!*value)
368 return true;
370 return string_format_from(format->buf, &format->bufpos, "%s", value);
373 static bool
374 argv_number_formatter(struct format_context *format, struct format_var *var)
376 unsigned long value = *(unsigned long *) var->value_ref;
378 return string_format_from(format->buf, &format->bufpos, "%ld", value);
381 static bool
382 bool_formatter(struct format_context *format, struct format_var *var)
384 bool value = *(bool *)var->value_ref;
386 return string_format_from(format->buf, &format->bufpos, "%s", value ? "true" : "false");
389 static bool
390 repo_str_formatter(struct format_context *format, struct format_var *var)
392 return argv_string_formatter(format, var);
395 static bool
396 repo_ref_formatter(struct format_context *format, struct format_var *var)
398 return argv_string_formatter(format, var);
401 static bool
402 repo_rev_formatter(struct format_context *format, struct format_var *var)
404 return argv_string_formatter(format, var);
407 bool
408 argv_format(struct argv_env *argv_env, const char ***dst_argv, const char *src_argv[], bool first, bool file_filter)
410 struct format_var vars[] = {
411 #define FORMAT_VAR(type, name, ifempty, initval) \
412 { "%(" #name ")", STRING_SIZE("%(" #name ")"), type ## _formatter, &argv_env->name, ifempty },
413 ARGV_ENV_INFO(FORMAT_VAR)
414 #define FORMAT_REPO_VAR(type, name) \
415 { "%(repo:" #name ")", STRING_SIZE("%(repo:" #name ")"), type ## _formatter, &repo.name, "" },
416 REPO_INFO(FORMAT_REPO_VAR)
418 struct format_context format = { vars, ARRAY_SIZE(vars), "", 0, file_filter };
419 int argc;
421 argv_free(*dst_argv);
423 for (argc = 0; src_argv[argc]; argc++) {
424 const char *arg = src_argv[argc];
426 if (!strcmp(arg, "%(fileargs)")) {
427 if (file_filter && !argv_append_array(dst_argv, opt_file_args))
428 break;
430 } else if (!strcmp(arg, "%(diffargs)")) {
431 if (!format_append_argv(&format, dst_argv, opt_diff_options))
432 break;
434 } else if (!strcmp(arg, "%(blameargs)")) {
435 if (!format_append_argv(&format, dst_argv, opt_blame_options))
436 break;
438 } else if (!strcmp(arg, "%(logargs)")) {
439 if (!format_append_argv(&format, dst_argv, opt_log_options))
440 break;
442 } else if (!strcmp(arg, "%(mainargs)")) {
443 if (!format_append_argv(&format, dst_argv, opt_main_options))
444 break;
446 } else if (!strcmp(arg, "%(cmdlineargs)")) {
447 if (!format_append_argv(&format, dst_argv, opt_cmdline_args))
448 break;
450 } else if (!strcmp(arg, "%(revargs)") ||
451 (first && !strcmp(arg, "%(commit)"))) {
452 if (!argv_append_array(dst_argv, opt_rev_args))
453 break;
455 } else if (!format_append_arg(&format, dst_argv, arg)) {
456 break;
460 return src_argv[argc] == NULL;
463 static inline bool
464 argv_find_rev_flag(const char *argv[], size_t argc, const char *arg, size_t arglen,
465 size_t *search_offset, bool *with_graph, bool *with_reflog)
467 int i;
469 for (i = 0; i < argc; i++) {
470 const char *flag = argv[i];
471 size_t flaglen = strlen(flag);
473 if (flaglen > arglen || strncmp(arg, flag, flaglen))
474 continue;
476 if (search_offset)
477 *search_offset = flaglen;
478 else if (flaglen != arglen && flag[flaglen - 1] != '=')
479 continue;
481 if (with_graph)
482 *with_graph = false;
483 if (with_reflog)
484 *with_reflog = true;
486 return true;
489 return false;
492 bool
493 argv_parse_rev_flag(const char *arg, struct rev_flags *rev_flags)
495 static const char *with_graph[] = {
496 "--after=",
497 "--all",
498 "--all-match",
499 "--ancestry-path",
500 "--author-date-order",
501 "--author=",
502 "--basic-regexp",
503 "--before=",
504 "--boundary",
505 "--branches",
506 "--branches=",
507 "--cherry",
508 "--cherry-mark",
509 "--cherry-pick",
510 "--committer=",
511 "--date-order",
512 "--dense",
513 "--extended-regexp",
514 "--first-parent",
515 "--fixed-strings",
516 "--full-history",
517 "--graph",
518 "--glob=",
519 "--left-only",
520 "--max-parents=",
521 "--merge",
522 "--merges",
523 "--min-parents=",
524 "--no-max-parents",
525 "--no-merges",
526 "--no-min-parents",
527 "--no-walk",
528 "--perl-regexp",
529 "--pickaxe-all",
530 "--pickaxe-regex",
531 "--regexp-ignore-case",
532 "--remotes",
533 "--remotes=",
534 "--remove-empty",
535 "--reverse",
536 "--right-only",
537 "--simplify-by-decoration",
538 "--simplify-merges",
539 "--since=",
540 "--skip=",
541 "--sparse",
542 "--stdin",
543 "--tags",
544 "--tags=",
545 "--topo-order",
546 "--until=",
547 "-E",
548 "-F",
549 "-i",
551 static const char *no_graph[] = {
552 "--follow",
554 static const char *with_reflog[] = {
555 "--walk-reflogs",
556 "-g",
558 static const char *search_no_graph[] = {
559 "--grep-reflog=",
560 "--grep=",
561 "-G",
562 "-S",
564 size_t arglen = strlen(arg);
565 bool graph = true;
566 bool reflog = false;
567 size_t search = 0;
569 if (argv_find_rev_flag(with_graph, ARRAY_SIZE(with_graph), arg, arglen, NULL, NULL, NULL) ||
570 argv_find_rev_flag(no_graph, ARRAY_SIZE(no_graph), arg, arglen, NULL, &graph, NULL) ||
571 argv_find_rev_flag(with_reflog, ARRAY_SIZE(with_reflog), arg, arglen, NULL, NULL, &reflog) ||
572 argv_find_rev_flag(search_no_graph, ARRAY_SIZE(search_no_graph), arg, arglen, &search, &graph, NULL)) {
573 if (rev_flags) {
574 rev_flags->search_offset = search ? search : arglen;
575 rev_flags->with_graph = graph;
576 rev_flags->with_reflog = reflog;
578 return true;
581 return false;
584 char *
585 argv_format_arg(struct argv_env *argv_env, const char *src_arg)
587 const char *src_argv[] = { src_arg, NULL };
588 const char **dst_argv = NULL;
589 char *dst_arg = NULL;
591 if (argv_format(argv_env, &dst_argv, src_argv, false, true))
592 dst_arg = (char *) dst_argv[0];
594 free(dst_argv);
595 return dst_arg;
598 /* vim: set ts=8 sw=8 noexpandtab: */