Don't do pointer arithmetic on void*
[tig.git] / src / argv.c
blob33dc778de04cbfdbab3a010da48a6894744f32ab
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/options.h"
17 #include "tig/prompt.h"
19 static bool
20 concat_argv(const char *argv[SIZEOF_ARG], char *buf, size_t buflen, const char *sep, bool quoted)
22 size_t bufpos, argc;
24 for (bufpos = 0, argc = 0; argv[argc]; argc++) {
25 const char *arg_sep = argc ? sep : "";
26 const char *arg = argv[argc];
27 int pos;
29 if (quoted && arg[(pos = strcspn(arg, " \t\""))]) {
30 if (!string_nformat(buf, buflen, &bufpos, "%s\"", arg_sep))
31 return FALSE;
33 while (*arg) {
34 int pos = strcspn(arg, "\"");
35 const char *qesc = arg[pos] == '"' ? "\\\"" : "";
37 if (!string_nformat(buf, buflen, &bufpos, "%.*s%s", pos, arg, qesc))
38 return FALSE;
39 arg += pos + 1;
42 if (!string_nformat(buf, buflen, &bufpos, "\""))
43 return FALSE;
45 continue;
48 if (!string_nformat(buf, buflen, &bufpos, "%s%s", arg_sep, arg))
49 return FALSE;
52 return TRUE;
55 bool
56 argv_to_string_quoted(const char *argv[SIZEOF_ARG], char *buf, size_t buflen, const char *sep)
58 return concat_argv(argv, buf, buflen, sep, TRUE);
61 bool
62 argv_to_string(const char *argv[SIZEOF_ARG], char *buf, size_t buflen, const char *sep)
64 return concat_argv(argv, buf, buflen, sep, FALSE);
67 static char *
68 parse_arg(char **cmd, bool remove_quotes)
70 int quote = 0;
71 char *arg = *cmd;
72 char *next, *pos;
74 for (pos = next = arg; *pos; pos++) {
75 int c = *pos;
77 if (c == '"' || c == '\'') {
78 if (quote == c) {
79 quote = 0;
80 if (remove_quotes) {
81 if (pos == arg) {
82 arg++;
83 next++;
85 continue;
88 } else if (!quote) {
89 quote = c;
90 if (remove_quotes) {
91 if (pos == arg) {
92 arg++;
93 next++;
95 continue;
99 } else if (quote && c == '\\') {
100 if (remove_quotes) {
101 if (pos == arg) {
102 arg++;
103 next++;
105 } else {
106 *next++ = *pos;
108 pos++;
109 if (!*pos)
110 break;
113 if (!quote && isspace(c))
114 break;
116 *next++ = *pos;
119 if (*pos)
120 *cmd = pos + 1;
121 else
122 *cmd = pos;
123 *next = 0;
124 return (!remove_quotes || !quote) ? arg : NULL;
127 static bool
128 split_argv_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd, bool remove_quotes)
130 while (*cmd && *argc < SIZEOF_ARG) {
131 char *arg = parse_arg(&cmd, remove_quotes);
133 if (!arg)
134 break;
135 argv[(*argc)++] = arg;
136 cmd = chomp_string(cmd);
139 if (*argc < SIZEOF_ARG)
140 argv[*argc] = NULL;
141 return *argc < SIZEOF_ARG;
144 bool
145 argv_from_string_no_quotes(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
147 return split_argv_string(argv, argc, cmd, TRUE);
150 bool
151 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
153 return split_argv_string(argv, argc, cmd, FALSE);
156 bool
157 argv_from_env(const char **argv, const char *name)
159 char *env = argv ? getenv(name) : NULL;
160 int argc = 0;
162 if (env && *env)
163 env = strdup(env);
164 return !env || argv_from_string(argv, &argc, env);
167 void
168 argv_free(const char *argv[])
170 int argc;
172 if (!argv)
173 return;
174 for (argc = 0; argv[argc]; argc++)
175 free((void *) argv[argc]);
176 argv[0] = NULL;
179 size_t
180 argv_size(const char **argv)
182 int argc = 0;
184 while (argv && argv[argc])
185 argc++;
187 return argc;
190 bool
191 argv_contains(const char **argv, const char *arg)
193 int i;
195 for (i = 0; argv && argv[i]; i++)
196 if (!strcmp(argv[i], arg))
197 return TRUE;
198 return FALSE;
201 DEFINE_ALLOCATOR(argv_realloc, const char *, SIZEOF_ARG)
203 bool
204 argv_append(const char ***argv, const char *arg)
206 size_t argc = argv_size(*argv);
207 char *alloc;
209 if (!*arg && argc > 0)
210 return TRUE;
212 if (!argv_realloc(argv, argc, 2))
213 return FALSE;
215 alloc = strdup(arg);
217 (*argv)[argc++] = alloc;
218 (*argv)[argc] = NULL;
220 return alloc != NULL;
223 bool
224 argv_append_array(const char ***dst_argv, const char *src_argv[])
226 int i;
228 for (i = 0; src_argv && src_argv[i]; i++)
229 if (!argv_append(dst_argv, src_argv[i]))
230 return FALSE;
231 return TRUE;
234 bool
235 argv_copy(const char ***dst, const char *src[])
237 int argc;
239 argv_free(*dst);
240 for (argc = 0; src[argc]; argc++)
241 if (!argv_append(dst, src[argc]))
242 return FALSE;
243 return TRUE;
247 * Argument formatting.
250 struct format_context;
252 struct format_var {
253 const char *name;
254 size_t namelen;
255 bool (*formatter)(struct format_context *, struct format_var *);
256 void *value_ref;
257 const char *value_if_empty;
260 struct format_context {
261 struct format_var *vars;
262 size_t vars_size;
263 char buf[SIZEOF_STR];
264 size_t bufpos;
265 bool file_filter;
268 #define ARGV_ENV_INIT(type, name, ifempty, initval) initval,
270 struct argv_env argv_env = {
271 ARGV_ENV_INFO(ARGV_ENV_INIT)
274 static bool
275 format_expand_arg(struct format_context *format, const char *name, const char *end)
277 struct format_var *vars = format->vars;
278 int i;
280 if (!prefixcmp(name, "%(prompt")) {
281 const char *prompt = "Command argument: ";
282 char msgbuf[SIZEOF_STR];
283 const char *value;
284 const char *msgstart = name + STRING_SIZE("%(prompt");
285 const int msglen = end - msgstart - 1;
287 if (end && msglen > 0 && string_format(msgbuf, "%.*s", msglen, msgstart)) {
288 const char *msg = msgbuf;
290 while (isspace(*msg))
291 msg++;
292 if (*msg)
293 prompt = msg;
296 value = read_prompt(prompt);
297 if (value == NULL)
298 return FALSE;
299 return string_format_from(format->buf, &format->bufpos, "%s", value);
302 for (i = 0; i < format->vars_size; i++) {
303 if (strncmp(name, vars[i].name, vars[i].namelen))
304 continue;
306 if (vars[i].value_ref == &argv_env.file && !format->file_filter)
307 return TRUE;
309 return vars[i].formatter(format, &vars[i]);
312 return FALSE;
315 static bool
316 format_append_arg(struct format_context *format, const char ***dst_argv, const char *arg)
318 memset(format->buf, 0, sizeof(format->buf));
319 format->bufpos = 0;
321 while (arg) {
322 const char *var = strstr(arg, "%(");
323 const char *closing = var ? strchr(var, ')') : NULL;
324 const char *next = closing ? closing + 1 : NULL;
325 const int len = var ? var - arg : strlen(arg);
327 if (var && !closing)
328 return FALSE;
330 if (len && !string_format_from(format->buf, &format->bufpos, "%.*s", len, arg))
331 return FALSE;
333 if (var && !format_expand_arg(format, var, next))
334 return FALSE;
336 arg = next;
339 return argv_append(dst_argv, format->buf);
342 static bool
343 format_append_argv(struct format_context *format, const char ***dst_argv, const char *src_argv[])
345 int argc;
347 if (!src_argv)
348 return TRUE;
350 for (argc = 0; src_argv[argc]; argc++)
351 if (!format_append_arg(format, dst_argv, src_argv[argc]))
352 return FALSE;
354 return src_argv[argc] == NULL;
357 static bool
358 argv_string_formatter(struct format_context *format, struct format_var *var)
360 argv_string *value_ref = var->value_ref;
361 const char *value = *value_ref;
363 if (!*value)
364 value = var->value_if_empty;
366 if (!*value)
367 return TRUE;
369 return string_format_from(format->buf, &format->bufpos, "%s", value);
372 static bool
373 argv_number_formatter(struct format_context *format, struct format_var *var)
375 unsigned long value = *(unsigned long *) var->value_ref;
377 return string_format_from(format->buf, &format->bufpos, "%ld", value);
380 bool
381 argv_format(struct argv_env *argv_env, const char ***dst_argv, const char *src_argv[], bool first, bool file_filter)
383 struct format_var vars[] = {
384 #define FORMAT_VAR(type, name, ifempty, initval) \
385 { "%(" #name ")", STRING_SIZE("%(" #name ")"), type ## _formatter, &argv_env->name, ifempty },
386 ARGV_ENV_INFO(FORMAT_VAR)
388 struct format_context format = { vars, ARRAY_SIZE(vars), "", 0, file_filter };
389 int argc;
391 argv_free(*dst_argv);
393 for (argc = 0; src_argv[argc]; argc++) {
394 const char *arg = src_argv[argc];
396 if (!strcmp(arg, "%(fileargs)")) {
397 if (file_filter && !argv_append_array(dst_argv, opt_file_args))
398 break;
400 } else if (!strcmp(arg, "%(diffargs)")) {
401 if (!format_append_argv(&format, dst_argv, opt_diff_options))
402 break;
404 } else if (!strcmp(arg, "%(blameargs)")) {
405 if (!format_append_argv(&format, dst_argv, opt_blame_options))
406 break;
408 } else if (!strcmp(arg, "%(logargs)")) {
409 if (!format_append_argv(&format, dst_argv, opt_log_options))
410 break;
412 } else if (!strcmp(arg, "%(mainargs)")) {
413 if (!format_append_argv(&format, dst_argv, opt_main_options))
414 break;
416 } else if (!strcmp(arg, "%(cmdlineargs)")) {
417 if (!format_append_argv(&format, dst_argv, opt_cmdline_args))
418 break;
420 } else if (!strcmp(arg, "%(revargs)") ||
421 (first && !strcmp(arg, "%(commit)"))) {
422 if (!argv_append_array(dst_argv, opt_rev_args))
423 break;
425 } else if (!format_append_arg(&format, dst_argv, arg)) {
426 break;
430 return src_argv[argc] == NULL;
433 static inline bool
434 argv_find_rev_flag(const char *argv[], size_t argc, const char *arg, size_t arglen,
435 size_t *search_offset, bool *with_graph, bool *with_reflog)
437 int i;
439 for (i = 0; i < argc; i++) {
440 const char *flag = argv[i];
441 size_t flaglen = strlen(flag);
443 if (flaglen > arglen || strncmp(arg, flag, flaglen))
444 continue;
446 if (search_offset)
447 *search_offset = flaglen;
448 else if (flaglen != arglen && flag[flaglen - 1] != '=')
449 continue;
451 if (with_graph)
452 *with_graph = FALSE;
453 if (with_reflog)
454 *with_reflog = TRUE;
456 return TRUE;
459 return FALSE;
462 bool
463 argv_parse_rev_flag(const char *arg, struct rev_flags *rev_flags)
465 static const char *with_graph[] = {
466 "--after=",
467 "--all",
468 "--all-match",
469 "--ancestry-path",
470 "--author-date-order",
471 "--author=",
472 "--basic-regexp",
473 "--before=",
474 "--boundary",
475 "--branches",
476 "--branches=",
477 "--cherry",
478 "--cherry-mark",
479 "--cherry-pick",
480 "--committer=",
481 "--date-order",
482 "--dense",
483 "--extended-regexp",
484 "--first-parent",
485 "--fixed-strings",
486 "--full-history",
487 "--graph",
488 "--glob=",
489 "--left-only",
490 "--max-parents=",
491 "--merge",
492 "--merges",
493 "--min-parents=",
494 "--no-max-parents",
495 "--no-merges",
496 "--no-min-parents",
497 "--no-walk",
498 "--perl-regexp",
499 "--pickaxe-all",
500 "--pickaxe-regex",
501 "--regexp-ignore-case",
502 "--remotes",
503 "--remotes=",
504 "--remove-empty",
505 "--reverse",
506 "--right-only",
507 "--simplify-by-decoration",
508 "--simplify-merges",
509 "--since=",
510 "--skip=",
511 "--sparse",
512 "--stdin",
513 "--tags",
514 "--tags=",
515 "--topo-order",
516 "--until=",
517 "-E",
518 "-F",
519 "-i",
521 static const char *no_graph[] = {
522 "--follow",
524 static const char *with_reflog[] = {
525 "--walk-reflogs",
526 "-g",
528 static const char *search_no_graph[] = {
529 "--grep-reflog=",
530 "--grep=",
531 "-G",
532 "-S",
534 size_t arglen = strlen(arg);
535 bool graph = TRUE;
536 bool reflog = FALSE;
537 size_t search = 0;
539 if (argv_find_rev_flag(with_graph, ARRAY_SIZE(with_graph), arg, arglen, NULL, NULL, NULL) ||
540 argv_find_rev_flag(no_graph, ARRAY_SIZE(no_graph), arg, arglen, NULL, &graph, NULL) ||
541 argv_find_rev_flag(with_reflog, ARRAY_SIZE(with_reflog), arg, arglen, NULL, NULL, &reflog) ||
542 argv_find_rev_flag(search_no_graph, ARRAY_SIZE(search_no_graph), arg, arglen, &search, &graph, NULL)) {
543 if (rev_flags) {
544 rev_flags->search_offset = search ? search : arglen;
545 rev_flags->with_graph = graph;
546 rev_flags->with_reflog = reflog;
548 return TRUE;
551 return FALSE;
554 char *
555 argv_format_arg(struct argv_env *argv_env, const char *src_arg)
557 const char *src_argv[] = { src_arg, NULL };
558 const char **dst_argv = NULL;
559 char *dst_arg = NULL;
561 if (argv_format(argv_env, &dst_argv, src_argv, FALSE, TRUE))
562 dst_arg = (char *) dst_argv[0];
564 free(dst_argv);
565 return dst_arg;
568 /* vim: set ts=8 sw=8 noexpandtab: */