1 /* vifm
2  * Copyright (C) 2011 xaizek.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include "cmds.h"
20 
21 #include <assert.h> /* assert() */
22 #include <ctype.h> /* isalpha() isdigit() isspace() */
23 #include <stddef.h> /* NULL size_t */
24 #include <stdio.h>
25 #include <stdlib.h> /* calloc() malloc() free() realloc() */
26 #include <string.h> /* strdup() */
27 
28 #include "../compat/reallocarray.h"
29 #include "../utils/darray.h"
30 #include "../utils/macros.h"
31 #include "../utils/str.h"
32 #include "../utils/string_array.h"
33 #include "../utils/test_helpers.h"
34 #include "../utils/utils.h"
35 #include "completion.h"
36 
37 #define MAX_CMD_RECURSION 16
38 #define MAX_CMD_NAME_LEN 256
39 #define INVALID_MARK -4096
40 
41 typedef struct
42 {
43 	cmd_t head;
44 	cmd_add_t user_cmd_handler;
45 	cmd_handler command_handler;
46 	int udf_count;
47 }
48 inner_t;
49 
50 /* List of characters, which are treated as range separators. */
51 static const char *RANGE_SEPARATORS = ",;";
52 
53 static inner_t *inner;
54 static cmds_conf_t *cmds_conf;
55 
56 static const char * correct_limit(const char cmd[], cmd_info_t *cmd_info);
57 static int udf_is_ambiguous(const char name[]);
58 static const char * parse_tail(cmd_t *cur, const char cmd[],
59 		cmd_info_t *cmd_info);
60 static const char * get_cmd_name(const char cmd[], char buf[], size_t buf_len);
61 static void init_cmd_info(cmd_info_t *cmd_info);
62 static const char * skip_prefix_commands(const char cmd[]);
63 static cmd_t * find_cmd(const char name[]);
64 static const char * parse_range(const char cmd[], cmd_info_t *cmd_info);
65 static const char * parse_range_elem(const char cmd[], cmd_info_t *cmd_info,
66 		char last_sep);
67 static int complete_cmd_args(cmd_t *cur, const char args[],
68 		cmd_info_t *cmd_info, void *arg);
69 static void complete_cmd_name(const char cmd_name[], int user_only);
70 TSTATIC int add_builtin_cmd(const char name[], int abbr, const cmd_add_t *conf);
71 static int comclear_cmd(const cmd_info_t *cmd_info);
72 static int command_cmd(const cmd_info_t *cmd_info);
73 static void init_command_flags(cmd_t *cmd, int flags);
74 static const char * get_user_cmd_name(const char cmd[], char buf[],
75 		size_t buf_len);
76 static int is_valid_udc_name(const char name[]);
77 static cmd_t * insert_cmd(cmd_t *after);
78 static int delcommand_cmd(const cmd_info_t *cmd_info);
79 TSTATIC char ** dispatch_line(const char args[], int *count, char sep,
80 		int regexp, int quotes, int noescaping, int comments, int *last_arg,
81 		int (**positions)[2]);
82 static int is_separator(char c, char sep);
83 
84 void
vle_cmds_init(int udf,cmds_conf_t * conf)85 vle_cmds_init(int udf, cmds_conf_t *conf)
86 {
87 	static cmd_add_t commands[] = {
88 		{
89 			.name = "comclear",        .abbr = "comc", .handler = comclear_cmd,
90 			.id = COMCLEAR_CMD_ID,
91 			.descr = "remove all user-defined :commands",
92 			.flags = 0,
93 			.min_args = 0,             .max_args = 0,
94 		}, {
95 			.name = "command",         .abbr = "com",  .handler = command_cmd,
96 			.id = COMMAND_CMD_ID,
97 			.descr = "display/define user-defined :command",
98 			.flags = HAS_EMARK,
99 			.min_args = 0,             .max_args = NOT_DEF,
100 		}, {
101 			.name = "delcommand", .abbr = "delc", .handler = delcommand_cmd,
102 			.id = DELCOMMAND_CMD_ID,
103 			.descr = "undefine user-defined :command",
104 			.flags = HAS_EMARK,
105 			.min_args = 1,             .max_args = 1,
106 		}
107 	};
108 
109 	cmds_conf = conf;
110 	inner = conf->inner;
111 
112 	if(inner == NULL)
113 	{
114 		assert(conf->complete_args != NULL);
115 		assert(conf->swap_range != NULL);
116 		assert(conf->resolve_mark != NULL);
117 		assert(conf->expand_macros != NULL);
118 		assert(conf->expand_envvars != NULL);
119 		assert(conf->post != NULL);
120 		assert(conf->select_range != NULL);
121 		assert(conf->skip_at_beginning != NULL);
122 		conf->inner = calloc(1, sizeof(inner_t));
123 		assert(conf->inner != NULL);
124 		inner = conf->inner;
125 
126 		if(udf)
127 			vle_cmds_add(commands, ARRAY_LEN(commands));
128 	}
129 }
130 
131 void
vle_cmds_reset(void)132 vle_cmds_reset(void)
133 {
134 	cmd_t *cur = inner->head.next;
135 
136 	while(cur != NULL)
137 	{
138 		cmd_t *next = cur->next;
139 		free(cur->cmd);
140 		free(cur->name);
141 		free(cur);
142 		cur = next;
143 	}
144 
145 	inner->head.next = NULL;
146 	inner->user_cmd_handler.handler = NULL;
147 
148 	free(inner);
149 	cmds_conf->inner = NULL;
150 }
151 
152 int
vle_cmds_run(const char cmd[])153 vle_cmds_run(const char cmd[])
154 {
155 	cmd_info_t cmd_info;
156 	char cmd_name[MAX_CMD_NAME_LEN];
157 	cmd_t *cur;
158 	const char *args;
159 	int execution_code;
160 	size_t last_arg_len;
161 	char *last_arg;
162 	int last_end = 0;
163 	cmds_conf_t *cc = cmds_conf;
164 
165 	init_cmd_info(&cmd_info);
166 	cmd = parse_range(cmd, &cmd_info);
167 	if(cmd == NULL)
168 	{
169 		if(cmd_info.end == INVALID_MARK)
170 			return CMDS_ERR_INVALID_RANGE;
171 		else
172 			return CMDS_ERR_INVALID_CMD;
173 	}
174 
175 	if(*cmd != '\0' && cmd_info.end < cmd_info.begin)
176 	{
177 		int t;
178 
179 		if(!cc->swap_range())
180 			return CMDS_ERR_INVALID_RANGE;
181 
182 		t = cmd_info.end;
183 		cmd_info.end = cmd_info.begin;
184 		cmd_info.begin = t;
185 	}
186 
187 	cmd = get_cmd_name(cmd, cmd_name, sizeof(cmd_name));
188 	if(udf_is_ambiguous(cmd_name))
189 		return CMDS_ERR_UDF_IS_AMBIGUOUS;
190 
191 	cur = find_cmd(cmd_name);
192 	if(cur == NULL)
193 		return CMDS_ERR_INVALID_CMD;
194 
195 	args = parse_tail(cur, cmd, &cmd_info);
196 
197 	cmd_info.raw_args = strdup(args);
198 
199 	/* Set background flag and remove background mark from raw arguments, when
200 	 * command supports backgrounding. */
201 	last_arg = vle_cmds_last_arg(cmd_info.raw_args, cur->quote, &last_arg_len);
202 	if(cur->bg && *last_arg == '&' && *vle_cmds_at_arg(last_arg + 1) == '\0')
203 	{
204 		cmd_info.bg = 1;
205 		*last_arg = '\0';
206 	}
207 
208 	if(cur->select)
209 	{
210 		cc->select_range(cur->id, &cmd_info);
211 	}
212 
213 	if(cur->macros_for_cmd || cur->macros_for_shell)
214 	{
215 		cmd_info.args = cc->expand_macros(cmd_info.raw_args, cur->macros_for_shell,
216 				&cmd_info.usr1, &cmd_info.usr2);
217 	}
218 	if(cur->envvars)
219 	{
220 		char *const p = cmd_info.args;
221 		cmd_info.args = cc->expand_envvars(p ? p : cmd_info.raw_args);
222 		free(p);
223 	}
224 	if(cmd_info.args == NULL)
225 	{
226 		cmd_info.args = strdup(cmd_info.raw_args);
227 	}
228 
229 	cmd_info.argv = dispatch_line(cmd_info.args, &cmd_info.argc, cmd_info.sep,
230 			cur->regexp, cur->quote, cur->noescaping, cur->comment, NULL,
231 			&cmd_info.argvp);
232 	if(cmd_info.argc > 0)
233 	{
234 		last_end = cmd_info.argvp[cmd_info.argc - 1][1];
235 	}
236 	cmd_info.args[last_end] = '\0';
237 
238 	/* TODO: extract a method that would check all these error conditions. */
239 	if((cmd_info.begin != NOT_DEF || cmd_info.end != NOT_DEF) && !cur->range)
240 	{
241 		execution_code = CMDS_ERR_NO_RANGE_ALLOWED;
242 	}
243 	else if(cmd_info.argc < 0)
244 	{
245 		execution_code = CMDS_ERR_INVALID_ARG;
246 	}
247 	else if(cmd_info.emark && !cur->emark)
248 	{
249 		execution_code = CMDS_ERR_NO_BANG_ALLOWED;
250 	}
251 	else if(cmd_info.qmark && !cur->qmark)
252 	{
253 		execution_code = CMDS_ERR_NO_QMARK_ALLOWED;
254 	}
255 	else if(cmd_info.qmark && !cur->args_after_qmark && *cmd_info.args != '\0')
256 	{
257 		execution_code = CMDS_ERR_TRAILING_CHARS;
258 	}
259 	else if(cmd_info.argc < cur->min_args)
260 	{
261 		execution_code = CMDS_ERR_TOO_FEW_ARGS;
262 	}
263 	else if(cmd_info.argc > cur->max_args && cur->max_args != NOT_DEF)
264 	{
265 		execution_code = CMDS_ERR_TRAILING_CHARS;
266 	}
267 	else if(cur->passed > MAX_CMD_RECURSION)
268 	{
269 		execution_code = CMDS_ERR_LOOP;
270 	}
271 	else
272 	{
273 		++cur->passed;
274 
275 		if(cur->type == USER_CMD)
276 		{
277 			cmd_info.user_cmd = cur->name;
278 			cmd_info.user_action = cur->cmd;
279 			execution_code = inner->user_cmd_handler.handler(&cmd_info);
280 		}
281 		else
282 		{
283 			execution_code = cur->handler(&cmd_info);
284 		}
285 
286 		cc->post(cur->id);
287 		if(--cur->passed == 0 && cur->deleted)
288 		{
289 			free(cur);
290 		}
291 	}
292 
293 	free(cmd_info.raw_args);
294 	free(cmd_info.args);
295 	free_string_array(cmd_info.argv, cmd_info.argc);
296 	free(cmd_info.argvp);
297 
298 	return execution_code;
299 }
300 
301 /* Applies limit shifts and ensures that its value is not out of bounds.
302  * Returns pointer to part of string after parsed piece. */
303 static const char *
correct_limit(const char cmd[],cmd_info_t * cmd_info)304 correct_limit(const char cmd[], cmd_info_t *cmd_info)
305 {
306 	cmd_info->count = (cmd_info->end == NOT_DEF) ? 1 : (cmd_info->end + 1);
307 
308 	while(*cmd == '+' || *cmd == '-')
309 	{
310 		int n = 1;
311 		int plus = *cmd == '+';
312 		cmd++;
313 		if(isdigit(*cmd))
314 		{
315 			char *p;
316 			n = strtol(cmd, &p, 10);
317 			cmd = p;
318 		}
319 
320 		if(plus)
321 		{
322 			cmd_info->end += n;
323 			cmd_info->count += n;
324 		}
325 		else
326 		{
327 			cmd_info->end -= n;
328 			cmd_info->count -= n;
329 		}
330 	}
331 
332 	if(cmd_info->end < 0)
333 		cmd_info->end = 0;
334 	if(cmd_info->end > cmds_conf->end)
335 		cmd_info->end = cmds_conf->end;
336 
337 	return cmd;
338 }
339 
340 static int
udf_is_ambiguous(const char name[])341 udf_is_ambiguous(const char name[])
342 {
343 	size_t len;
344 	int count;
345 	cmd_t *cur;
346 
347 	len = strlen(name);
348 	count = 0;
349 	cur = inner->head.next;
350 	while(cur != NULL)
351 	{
352 		int cmp;
353 
354 		cmp = strncmp(cur->name, name, len);
355 		if(cmp == 0)
356 		{
357 			if(cur->name[len] == '\0')
358 				return 0;
359 			if(cur->type == USER_CMD)
360 			{
361 				char c = cur->name[strlen(cur->name) - 1];
362 				if(c != '!' && c != '?')
363 					count++;
364 			}
365 		}
366 		else if(cmp > 0)
367 		{
368 			break;
369 		}
370 
371 		cur = cur->next;
372 	}
373 	return (count > 1);
374 }
375 
376 static const char *
parse_tail(cmd_t * cur,const char cmd[],cmd_info_t * cmd_info)377 parse_tail(cmd_t *cur, const char cmd[], cmd_info_t *cmd_info)
378 {
379 	if(*cmd == '!' && (!cur->cust_sep || cur->emark))
380 	{
381 		cmd_info->emark = 1;
382 		cmd++;
383 	}
384 	else if(*cmd == '?' && (!cur->cust_sep || cur->qmark))
385 	{
386 		cmd_info->qmark = 1;
387 		cmd++;
388 	}
389 
390 	if(*cmd != '\0' && !isspace(*cmd))
391 	{
392 		if(cur->cust_sep)
393 		{
394 			cmd_info->sep = isspace(*cmd) ? ' ' : *cmd;
395 		}
396 		return cmd;
397 	}
398 
399 	while(is_separator(*cmd, cmd_info->sep))
400 	{
401 		++cmd;
402 	}
403 
404 	return cmd;
405 }
406 
407 int
vle_cmds_identify(const char cmd[])408 vle_cmds_identify(const char cmd[])
409 {
410 	cmd_info_t info;
411 	const cmd_t *const c = vle_cmds_parse(cmd, &info);
412 	return (c == NULL ? -1 : c->id);
413 }
414 
415 const char *
vle_cmds_args(const char cmd[])416 vle_cmds_args(const char cmd[])
417 {
418 	cmd_info_t info = {};
419 	(void)vle_cmds_parse(cmd, &info);
420 	return info.raw_args;
421 }
422 
423 /* Initializes command info structure with reasonable defaults. */
424 static void
init_cmd_info(cmd_info_t * cmd_info)425 init_cmd_info(cmd_info_t *cmd_info)
426 {
427 	cmd_info->begin = NOT_DEF;
428 	cmd_info->end = NOT_DEF;
429 	cmd_info->count = NOT_DEF;
430 	cmd_info->emark = 0;
431 	cmd_info->qmark = 0;
432 	cmd_info->raw_args = NULL;
433 	cmd_info->args = NULL;
434 	cmd_info->argc = 0;
435 	cmd_info->argv = NULL;
436 	cmd_info->user_cmd = NULL;
437 	cmd_info->user_action = NULL;
438 	cmd_info->sep = ' ';
439 	cmd_info->bg = 0;
440 	cmd_info->usr1 = 0;
441 	cmd_info->usr2 = 0;
442 }
443 
444 const cmd_t *
vle_cmds_parse(const char cmd[],cmd_info_t * info)445 vle_cmds_parse(const char cmd[], cmd_info_t *info)
446 {
447 	cmd_info_t cmd_info;
448 	char cmd_name[MAX_CMD_NAME_LEN + 1];
449 	cmd_t *c;
450 
451 	init_cmd_info(&cmd_info);
452 
453 	cmd = parse_range(cmd, &cmd_info);
454 	if(cmd == NULL)
455 	{
456 		return NULL;
457 	}
458 
459 	cmd = get_cmd_name(cmd, cmd_name, sizeof(cmd_name));
460 	c = find_cmd(cmd_name);
461 	if(c == NULL)
462 	{
463 		return NULL;
464 	}
465 
466 	cmd_info.raw_args = (char *)parse_tail(c, cmd, &cmd_info);
467 
468 	*info = cmd_info;
469 	return c;
470 }
471 
472 int
vle_cmds_complete(const char cmd[],void * arg)473 vle_cmds_complete(const char cmd[], void *arg)
474 {
475 	cmd_info_t cmd_info;
476 	const char *begin, *cmd_name_pos;
477 	size_t prefix_len;
478 
479 	begin = cmd;
480 	cmd = skip_prefix_commands(begin);
481 	prefix_len = cmd - begin;
482 
483 	init_cmd_info(&cmd_info);
484 
485 	cmd_name_pos = parse_range(cmd, &cmd_info);
486 	if(cmd_name_pos != NULL)
487 	{
488 		char cmd_name[MAX_CMD_NAME_LEN];
489 		const char *args;
490 		cmd_t *cur;
491 
492 		args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
493 		cur = find_cmd(cmd_name);
494 
495 		if(*args == '\0' && strcmp(cmd_name, "!") != 0)
496 		{
497 			complete_cmd_name(cmd_name, 0);
498 			prefix_len += cmd_name_pos - cmd;
499 		}
500 		else
501 		{
502 			prefix_len += args - cmd;
503 			prefix_len += complete_cmd_args(cur, args, &cmd_info, arg);
504 		}
505 	}
506 
507 	return prefix_len;
508 }
509 
510 /* Skips prefix commands (which can be followed by an arbitrary command) at the
511  * beginning of command-line. */
512 static const char *
skip_prefix_commands(const char cmd[])513 skip_prefix_commands(const char cmd[])
514 {
515 	cmd_info_t cmd_info;
516 	const char *cmd_name_pos;
517 
518 	init_cmd_info(&cmd_info);
519 
520 	cmd_name_pos = parse_range(cmd, &cmd_info);
521 	if(cmd_name_pos != NULL)
522 	{
523 		char cmd_name[MAX_CMD_NAME_LEN];
524 		const char *args;
525 		cmd_t *cur;
526 
527 		args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
528 		cur = find_cmd(cmd_name);
529 		while(cur != NULL && *args != '\0')
530 		{
531 			int offset = cmds_conf->skip_at_beginning(cur->id, args);
532 			if(offset >= 0)
533 			{
534 				int delta = (args - cmd) + offset;
535 				cmd += delta;
536 				init_cmd_info(&cmd_info);
537 				cmd_name_pos = parse_range(cmd, &cmd_info);
538 				if(cmd_name_pos == NULL)
539 				{
540 					break;
541 				}
542 				args = get_cmd_name(cmd_name_pos, cmd_name, sizeof(cmd_name));
543 			}
544 			else
545 			{
546 				break;
547 			}
548 			cur = find_cmd(cmd_name);
549 		}
550 	}
551 	return cmd;
552 }
553 
554 static cmd_t *
find_cmd(const char name[])555 find_cmd(const char name[])
556 {
557 	cmd_t *cmd;
558 
559 	cmd = inner->head.next;
560 	while(cmd != NULL && strcmp(cmd->name, name) < 0)
561 	{
562 		cmd = cmd->next;
563 	}
564 
565 	if(cmd != NULL && strncmp(name, cmd->name, strlen(name)) != 0)
566 	{
567 		cmd = NULL;
568 	}
569 
570 	return cmd;
571 }
572 
573 /* Parses whole command range (e.g. "<val>;+<val>,,-<val>").  Returns advanced
574  * value of cmd when parsing is successful, otherwise NULL is returned. */
575 static const char *
parse_range(const char cmd[],cmd_info_t * cmd_info)576 parse_range(const char cmd[], cmd_info_t *cmd_info)
577 {
578 	char last_sep;
579 
580 	cmd = vle_cmds_at_arg(cmd);
581 
582 	if(isalpha(*cmd) || *cmd == '!' || *cmd == '\0')
583 	{
584 		return cmd;
585 	}
586 
587 	last_sep = '\0';
588 	while(*cmd != '\0')
589 	{
590 		cmd_info->begin = cmd_info->end;
591 
592 		cmd = parse_range_elem(cmd, cmd_info, last_sep);
593 		if(cmd == NULL)
594 		{
595 			return NULL;
596 		}
597 
598 		cmd = correct_limit(cmd, cmd_info);
599 
600 		if(cmd_info->begin == NOT_DEF)
601 		{
602 			cmd_info->begin = cmd_info->end;
603 		}
604 
605 		cmd = vle_cmds_at_arg(cmd);
606 
607 		if(!char_is_one_of(RANGE_SEPARATORS, *cmd))
608 		{
609 			break;
610 		}
611 
612 		last_sep = *cmd;
613 		cmd++;
614 
615 		cmd = vle_cmds_at_arg(cmd);
616 	}
617 
618 	return cmd;
619 }
620 
621 /* Parses single element of a command range (e.g. any of <el> in
622  * "<el>;<el>,<el>").  Returns advanced value of cmd when parsing is successful,
623  * otherwise NULL is returned. */
624 static const char *
parse_range_elem(const char cmd[],cmd_info_t * cmd_info,char last_sep)625 parse_range_elem(const char cmd[], cmd_info_t *cmd_info, char last_sep)
626 {
627 	if(cmd[0] == '%')
628 	{
629 		cmd_info->begin = cmds_conf->begin;
630 		cmd_info->end = cmds_conf->end;
631 		cmd++;
632 	}
633 	else if(cmd[0] == '$')
634 	{
635 		cmd_info->end = cmds_conf->end;
636 		cmd++;
637 	}
638 	else if(cmd[0] == '.')
639 	{
640 		cmd_info->end = cmds_conf->current;
641 		cmd++;
642 	}
643 	else if(char_is_one_of(RANGE_SEPARATORS, *cmd))
644 	{
645 		cmd_info->end = cmds_conf->current;
646 	}
647 	else if(isalpha(*cmd))
648 	{
649 		cmd_info->end = cmds_conf->current;
650 	}
651 	else if(isdigit(*cmd))
652 	{
653 		char *p;
654 		cmd_info->end = strtol(cmd, &p, 10) - 1;
655 		if(cmd_info->end < cmds_conf->begin)
656 			cmd_info->end = cmds_conf->begin;
657 		cmd = p;
658 	}
659 	else if(*cmd == '\'')
660 	{
661 		char mark;
662 		cmd++;
663 		mark = *cmd++;
664 		cmd_info->end = cmds_conf->resolve_mark(mark);
665 		if(cmd_info->end < 0)
666 		{
667 			cmd_info->end = INVALID_MARK;
668 			return NULL;
669 		}
670 	}
671 	else if(*cmd == '+' || *cmd == '-')
672 	{
673 		/* Do nothing after semicolon, because in this case +/- are adjusting not
674 		 * base current cursor position, but base end of the range. */
675 		if(last_sep != ';')
676 		{
677 			cmd_info->end = cmds_conf->current;
678 		}
679 	}
680 	else
681 	{
682 		return NULL;
683 	}
684 
685 	return cmd;
686 }
687 
688 static const char *
get_cmd_name(const char cmd[],char buf[],size_t buf_len)689 get_cmd_name(const char cmd[], char buf[], size_t buf_len)
690 {
691 	const char *t;
692 	size_t len;
693 
694 	assert(buf_len != 0 && "The buffer is expected to be of size > 0.");
695 
696 	if(cmd[0] == '!')
697 	{
698 		strcpy(buf, "!");
699 		cmd = vle_cmds_at_arg(cmd + 1);
700 		return cmd;
701 	}
702 
703 	t = cmd;
704 	while(isalpha(*t))
705 		t++;
706 
707 	len = MIN((size_t)(t - cmd), buf_len - 1);
708 	strncpy(buf, cmd, len);
709 	buf[len] = '\0';
710 	if(*t == '?' || *t == '!')
711 	{
712 		int cmp;
713 		cmd_t *cur;
714 
715 		cur = inner->head.next;
716 		while(cur != NULL && (cmp = strncmp(cur->name, buf, len)) <= 0)
717 		{
718 			if(cmp == 0)
719 			{
720 				/* Complete match for a builtin with a custom separator. */
721 				if(cur->cust_sep && cur->name[len] == '\0')
722 				{
723 					strncpy(buf, cur->name, buf_len);
724 					break;
725 				}
726 				/* Check for user-defined command that ends with the char ('!' or
727 				 * '?', see above). */
728 				if(cur->type == USER_CMD && cur->name[strlen(cur->name) - 1] == *t)
729 				{
730 					strncpy(buf, cur->name, buf_len);
731 					break;
732 				}
733 				/* Or builtin abbreviation that supports the mark. */
734 				if(cur->type == BUILTIN_ABBR &&
735 						((*t == '!' && cur->emark) || (*t == '?' && cur->qmark)))
736 				{
737 					strncpy(buf, cur->name, buf_len);
738 					break;
739 				}
740 			}
741 			cur = cur->next;
742 		}
743 
744 		if(cur != NULL && cur->type == USER_CMD &&
745 				strncmp(cur->name, buf, len) == 0)
746 		{
747 			/* For user-defined commands, the char is part of the name. */
748 			++t;
749 		}
750 	}
751 
752 	return t;
753 }
754 
755 /* Returns offset at which completion was done. */
756 static int
complete_cmd_args(cmd_t * cur,const char args[],cmd_info_t * cmd_info,void * arg)757 complete_cmd_args(cmd_t *cur, const char args[], cmd_info_t *cmd_info,
758 		void *arg)
759 {
760 	const char *tmp_args = args;
761 	int result = 0;
762 
763 	if(cur == NULL || (cur->id >= NO_COMPLETION_BOUNDARY && cur->id < 0))
764 		return 0;
765 
766 	args = parse_tail(cur, tmp_args, cmd_info);
767 	args = vle_cmds_at_arg(args);
768 	result += args - tmp_args;
769 
770 	if(cur->id == COMMAND_CMD_ID || cur->id == DELCOMMAND_CMD_ID)
771 	{
772 		const char *arg;
773 
774 		arg = strrchr(args, ' ');
775 		arg = (arg == NULL) ? args : (arg + 1);
776 
777 		complete_cmd_name(arg, 1);
778 		result += arg - args;
779 	}
780 	else
781 	{
782 		int argc;
783 		char **argv;
784 		int (*argvp)[2];
785 		int last_arg = 0;
786 
787 		argv = dispatch_line(args, &argc, ' ', 0, 1, 0, 0, &last_arg, &argvp);
788 
789 		cmd_info->args = (char *)args;
790 		cmd_info->argc = argc;
791 		cmd_info->argv = argv;
792 		result += cmds_conf->complete_args(cur->id, cmd_info, last_arg, arg);
793 
794 		free_string_array(argv, argc);
795 		free(argvp);
796 	}
797 	return result;
798 }
799 
800 static void
complete_cmd_name(const char cmd_name[],int user_only)801 complete_cmd_name(const char cmd_name[], int user_only)
802 {
803 	cmd_t *cur;
804 	size_t len;
805 
806 	cur = inner->head.next;
807 	while(cur != NULL && strcmp(cur->name, cmd_name) < 0)
808 		cur = cur->next;
809 
810 	len = strlen(cmd_name);
811 	while(cur != NULL && strncmp(cur->name, cmd_name, len) == 0)
812 	{
813 		if(cur->type == BUILTIN_ABBR)
814 			;
815 		else if(cur->type != USER_CMD && user_only)
816 			;
817 		else if(cur->name[0] == '\0')
818 			;
819 		else if(cur->type == USER_CMD)
820 			vle_compl_add_match(cur->name, cur->cmd);
821 		else
822 			vle_compl_add_match(cur->name, cur->descr);
823 		cur = cur->next;
824 	}
825 
826 	vle_compl_add_last_match(cmd_name);
827 }
828 
829 void
vle_cmds_add(const cmd_add_t cmds[],int count)830 vle_cmds_add(const cmd_add_t cmds[], int count)
831 {
832 	int i;
833 	for(i = 0; i < count; ++i)
834 	{
835 		int ret_code;
836 		assert(cmds[i].min_args >= 0);
837 		assert(cmds[i].max_args == NOT_DEF ||
838 				cmds[i].min_args <= cmds[i].max_args);
839 		ret_code = add_builtin_cmd(cmds[i].name, 0, &cmds[i]);
840 		assert(ret_code == 0);
841 		if(cmds[i].abbr != NULL)
842 		{
843 			size_t full_len, short_len;
844 			char buf[strlen(cmds[i].name) + 1];
845 			assert(starts_with(cmds[i].name, cmds[i].abbr) &&
846 					"Abbreviation is consistent with full command");
847 			strcpy(buf, cmds[i].name);
848 			full_len = strlen(buf);
849 			short_len = strlen(cmds[i].abbr);
850 			while(full_len > short_len)
851 			{
852 				buf[--full_len] = '\0';
853 				ret_code = add_builtin_cmd(buf, 1, &cmds[i]);
854 				assert(ret_code == 0);
855 			}
856 		}
857 		(void)ret_code;
858 	}
859 }
860 
861 /* Returns non-zero on error */
862 TSTATIC int
add_builtin_cmd(const char name[],int abbr,const cmd_add_t * conf)863 add_builtin_cmd(const char name[], int abbr, const cmd_add_t *conf)
864 {
865 	int cmp;
866 	cmd_t *new;
867 	cmd_t *cur = &inner->head;
868 
869 	if(strcmp(name, "<USERCMD>") == 0)
870 	{
871 		if(inner->user_cmd_handler.handler != NULL)
872 			return -1;
873 		inner->user_cmd_handler = *conf;
874 		return 0;
875 	}
876 
877 	if(strcmp(name, "!") != 0)
878 	{
879 		unsigned int i;
880 		for(i = 0U; name[i] != '\0'; ++i)
881 		{
882 			if(!isalpha(name[i]))
883 			{
884 				return -1;
885 			}
886 		}
887 	}
888 
889 	cmp = -1;
890 	while(cur->next != NULL && (cmp = strcmp(cur->next->name, name)) < 0)
891 	{
892 		cur = cur->next;
893 	}
894 
895 	/* Command with the same name already exists. */
896 	if(cmp == 0)
897 	{
898 		if(strncmp(name, "command", strlen(name)) == 0)
899 		{
900 			inner->command_handler = conf->handler;
901 			return 0;
902 		}
903 		return -1;
904 	}
905 
906 	new = insert_cmd(cur);
907 	if(new == NULL)
908 	{
909 		return -1;
910 	}
911 
912 	new->name = strdup(name);
913 	new->descr = conf->descr;
914 	new->id = conf->id;
915 	new->handler = conf->handler;
916 	new->type = abbr ? BUILTIN_ABBR : BUILTIN_CMD;
917 	new->passed = 0;
918 	new->cmd = NULL;
919 	new->min_args = conf->min_args;
920 	new->max_args = conf->max_args;
921 	new->deleted = 0;
922 	init_command_flags(new, conf->flags);
923 
924 	return 0;
925 }
926 
927 static int
comclear_cmd(const cmd_info_t * cmd_info)928 comclear_cmd(const cmd_info_t *cmd_info)
929 {
930 	cmd_t *cur = &inner->head;
931 
932 	while(cur->next != NULL)
933 	{
934 		if(cur->next->type == USER_CMD)
935 		{
936 			cmd_t *this = cur->next;
937 			cur->next = this->next;
938 
939 			free(this->cmd);
940 			free(this->name);
941 			if(this->passed == 0)
942 			{
943 				free(this);
944 			}
945 			else
946 			{
947 				this->deleted = 1;
948 			}
949 		}
950 		else
951 		{
952 			cur = cur->next;
953 		}
954 	}
955 	inner->udf_count = 0;
956 	return 0;
957 }
958 
959 static int
command_cmd(const cmd_info_t * cmd_info)960 command_cmd(const cmd_info_t *cmd_info)
961 {
962 	int cmp;
963 	char cmd_name[MAX_CMD_NAME_LEN];
964 	const char *args;
965 	cmd_t *new, *cur;
966 	size_t len;
967 	int has_emark, has_qmark;
968 
969 	if(cmd_info->argc < 2)
970 	{
971 		if(inner->command_handler != NULL)
972 			return inner->command_handler(cmd_info);
973 		else
974 			return CMDS_ERR_TOO_FEW_ARGS;
975 	}
976 
977 	args = get_user_cmd_name(cmd_info->args, cmd_name, sizeof(cmd_name));
978 	args = vle_cmds_at_arg(args);
979 	if(args[0] == '\0')
980 		return CMDS_ERR_TOO_FEW_ARGS;
981 	else if(!is_valid_udc_name(cmd_name))
982 		return CMDS_ERR_INCORRECT_NAME;
983 
984 	len = strlen(cmd_name);
985 	has_emark = (len > 0 && cmd_name[len - 1] == '!');
986 	has_qmark = (len > 0 && cmd_name[len - 1] == '?');
987 
988 	cmp = -1;
989 	cur = &inner->head;
990 	while(cur->next != NULL && (cmp = strcmp(cur->next->name, cmd_name)) < 0)
991 	{
992 		if(has_emark && cur->next->type == BUILTIN_CMD && cur->next->emark &&
993 				strncmp(cmd_name, cur->next->name, len - 1) == 0)
994 		{
995 			cmp = 0;
996 			break;
997 		}
998 		if(has_qmark && cur->next->type == BUILTIN_CMD && cur->next->qmark &&
999 				strncmp(cmd_name, cur->next->name, len - 1) == 0)
1000 		{
1001 			cmp = 0;
1002 			break;
1003 		}
1004 		cur = cur->next;
1005 	}
1006 
1007 	if(cmp == 0)
1008 	{
1009 		cur = cur->next;
1010 		if(cur->type == BUILTIN_CMD)
1011 			return CMDS_ERR_NO_BUILTIN_REDEFINE;
1012 		if(!cmd_info->emark)
1013 			return CMDS_ERR_NEED_BANG;
1014 		free(cur->name);
1015 		free(cur->cmd);
1016 		new = cur;
1017 	}
1018 	else
1019 	{
1020 		if((new = insert_cmd(cur)) == NULL)
1021 		{
1022 			return CMDS_ERR_NO_MEM;
1023 		}
1024 	}
1025 
1026 	new->name = strdup(cmd_name);
1027 	new->descr = NULL;
1028 	new->id = USER_CMD_ID;
1029 	new->type = USER_CMD;
1030 	new->passed = 0;
1031 	new->cmd = strdup(args);
1032 	new->min_args = inner->user_cmd_handler.min_args;
1033 	new->max_args = inner->user_cmd_handler.max_args;
1034 	new->deleted = 0;
1035 	init_command_flags(new, inner->user_cmd_handler.flags);
1036 
1037 	++inner->udf_count;
1038 	return 0;
1039 }
1040 
1041 /* Initializes flag fields of *cmd from set of HAS_* flags. */
1042 static void
init_command_flags(cmd_t * cmd,int flags)1043 init_command_flags(cmd_t *cmd, int flags)
1044 {
1045 	assert((flags & (HAS_RAW_ARGS | HAS_REGEXP_ARGS)) !=
1046 			(HAS_RAW_ARGS | HAS_REGEXP_ARGS) && "Wrong flags combination.");
1047 	assert((flags & (HAS_RAW_ARGS | HAS_QUOTED_ARGS)) !=
1048 			(HAS_RAW_ARGS | HAS_QUOTED_ARGS) && "Wrong flags combination.");
1049 	assert((flags & (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS)) !=
1050 			(HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS) && "Wrong flags combination.");
1051 	assert((flags & (HAS_MACROS_FOR_CMD | HAS_MACROS_FOR_SHELL)) !=
1052 			(HAS_MACROS_FOR_CMD | HAS_MACROS_FOR_SHELL) &&
1053 			"Wrong flags combination.");
1054 
1055 	cmd->range = ((flags & HAS_RANGE) != 0);
1056 	cmd->cust_sep = ((flags & HAS_CUST_SEP) != 0);
1057 	cmd->emark = ((flags & HAS_EMARK) != 0);
1058 	cmd->envvars = ((flags & HAS_ENVVARS) != 0);
1059 	cmd->select = ((flags & HAS_SELECTION_SCOPE) != 0);
1060 	cmd->bg = ((flags & HAS_BG_FLAG) != 0);
1061 	cmd->regexp = ((flags & HAS_REGEXP_ARGS) != 0);
1062 	cmd->quote = ((flags & HAS_QUOTED_ARGS) != 0);
1063 	cmd->noescaping = ((flags & HAS_RAW_ARGS) != 0);
1064 	cmd->comment = ((flags & HAS_COMMENT) != 0);
1065 	cmd->qmark = ((flags & (HAS_QMARK_NO_ARGS | HAS_QMARK_WITH_ARGS)) != 0);
1066 	cmd->args_after_qmark = ((flags & HAS_QMARK_WITH_ARGS) != 0);
1067 	cmd->macros_for_cmd = ((flags & HAS_MACROS_FOR_CMD) != 0);
1068 	cmd->macros_for_shell = ((flags & HAS_MACROS_FOR_SHELL) != 0);
1069 }
1070 
1071 static const char *
get_user_cmd_name(const char cmd[],char buf[],size_t buf_len)1072 get_user_cmd_name(const char cmd[], char buf[], size_t buf_len)
1073 {
1074 	const char *t;
1075 	size_t len;
1076 
1077 	t = vle_cmds_past_arg(cmd);
1078 
1079 	len = MIN((size_t)(t - cmd), buf_len);
1080 	strncpy(buf, cmd, len);
1081 	buf[len] = '\0';
1082 	return t;
1083 }
1084 
1085 /* Checks that the name is a valid name for a user-defined command. */
1086 static int
is_valid_udc_name(const char name[])1087 is_valid_udc_name(const char name[])
1088 {
1089 	assert(name[0] != '\0' && "Command name can't be empty");
1090 
1091 	if(strcmp(name, "!") == 0)
1092 		return 0;
1093 	if(strcmp(name, "?") == 0)
1094 		return 0;
1095 
1096 	char cmd_name[MAX_CMD_NAME_LEN + 1];
1097 	copy_str(cmd_name, sizeof(cmd_name), name);
1098 
1099 	while(name[0] != '\0')
1100 	{
1101 		if(!isalpha(name[0]))
1102 		{
1103 			if(name[1] != '\0')
1104 				return 0;
1105 			else if(name[0] != '!' && name[0] != '?')
1106 				return 0;
1107 		}
1108 		name++;
1109 	}
1110 
1111 	/* Builtins with custom separator have higher priority.  Disallow registering
1112 	 * user-defined commands which will never be called. */
1113 	if(name[-1] == '!' || name[-1] == '?')
1114 	{
1115 		cmd_name[strlen(cmd_name) - 1] = '\0';
1116 	}
1117 	const cmd_t *const c = find_cmd(cmd_name);
1118 	if(c != NULL && c->cust_sep && strcmp(c->name, cmd_name) == 0)
1119 		return 0;
1120 
1121 	return 1;
1122 }
1123 
1124 /* Allocates new command node and inserts it after the specified one.  Returns
1125  * new uninitialized node or NULL on error. */
1126 static cmd_t *
insert_cmd(cmd_t * after)1127 insert_cmd(cmd_t *after)
1128 {
1129 	cmd_t *const new = malloc(sizeof(*new));
1130 	if(new == NULL)
1131 	{
1132 		return NULL;
1133 	}
1134 
1135 	new->next = after->next;
1136 	after->next = new;
1137 	return new;
1138 }
1139 
1140 static int
delcommand_cmd(const cmd_info_t * cmd_info)1141 delcommand_cmd(const cmd_info_t *cmd_info)
1142 {
1143 	int cmp;
1144 	cmd_t *cur;
1145 	cmd_t *cmd;
1146 
1147 	cmp = -1;
1148 	cur = &inner->head;
1149 	while(cur->next != NULL &&
1150 			(cmp = strcmp(cur->next->name, cmd_info->argv[0])) < 0)
1151 		cur = cur->next;
1152 
1153 	if(cur->next == NULL || cmp != 0)
1154 		return CMDS_ERR_NO_SUCH_UDF;
1155 
1156 	cmd = cur->next;
1157 	cur->next = cmd->next;
1158 	free(cmd->name);
1159 	free(cmd->cmd);
1160 	free(cmd);
1161 
1162 	inner->udf_count--;
1163 	return 0;
1164 }
1165 
1166 char *
vle_cmds_last_arg(const char cmd[],int quotes,size_t * len)1167 vle_cmds_last_arg(const char cmd[], int quotes, size_t *len)
1168 {
1169 	int argc;
1170 	char **argv;
1171 	int (*argvp)[2];
1172 	int last_start = 0;
1173 	int last_end = 0;
1174 
1175 	argv = dispatch_line(cmd, &argc, ' ', 0, quotes, 0, 0, NULL, &argvp);
1176 
1177 	if(argc > 0)
1178 	{
1179 		last_start = argvp[argc - 1][0];
1180 		last_end = argvp[argc - 1][1];
1181 	}
1182 
1183 	*len = last_end - last_start;
1184 	free_string_array(argv, argc);
1185 	free(argvp);
1186 	return (char *)cmd + last_start;
1187 }
1188 
1189 /* Splits argument string into array of strings.  Non-zero noescaping means that
1190  * unquoted arguments don't have escaping.  Returns NULL if no arguments are
1191  * found or an error occurred.  Always sets *count (to negative value on
1192  * unmatched quotes and to zero on all other errors). */
1193 TSTATIC char **
dispatch_line(const char args[],int * count,char sep,int regexp,int quotes,int noescaping,int comments,int * last_pos,int (** positions)[2])1194 dispatch_line(const char args[], int *count, char sep, int regexp, int quotes,
1195 		int noescaping, int comments, int *last_pos, int (**positions)[2])
1196 {
1197 	char *cmdstr;
1198 	int len;
1199 	int i;
1200 	int st;
1201 	const char *args_beg;
1202 	char **params;
1203 	int (*argvp)[2] = NULL;
1204 	DA_INSTANCE(argvp);
1205 
1206 	enum { BEGIN, NO_QUOTING, S_QUOTING, D_QUOTING, R_QUOTING, ARG, QARG } state;
1207 
1208 	if(last_pos != NULL)
1209 		*last_pos = 0;
1210 	*positions = NULL;
1211 
1212 	*count = 0;
1213 	params = NULL;
1214 
1215 	args_beg = args;
1216 	if(sep == ' ')
1217 	{
1218 		args = vle_cmds_at_arg(args);
1219 	}
1220 	cmdstr = strdup(args);
1221 	len = strlen(cmdstr);
1222 	for(i = 0, st = 0, state = BEGIN; i <= len; ++i)
1223 	{
1224 		const int prev_state = state;
1225 		switch(state)
1226 		{
1227 			case BEGIN:
1228 				if(sep == ' ' && cmdstr[i] == '\'' && quotes)
1229 				{
1230 					st = i + 1;
1231 					state = S_QUOTING;
1232 				}
1233 				else if(cmdstr[i] == '"' && ((sep == ' ' && quotes) ||
1234 							(comments && strchr(&cmdstr[i + 1], '"') == NULL)))
1235 				{
1236 					st = i + 1;
1237 					state = D_QUOTING;
1238 				}
1239 				else if(sep == ' ' && cmdstr[i] == '/' && regexp)
1240 				{
1241 					st = i + 1;
1242 					state = R_QUOTING;
1243 				}
1244 				else if(!is_separator(cmdstr[i], sep))
1245 				{
1246 					st = i;
1247 					state = NO_QUOTING;
1248 
1249 					/* Omit escaped character from processign to account for possible case
1250 					 * when it's a separator, which would lead to breaking argument into
1251 					 * pieces. */
1252 					if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1253 					{
1254 						++i;
1255 					}
1256 				}
1257 				else if(sep != ' ' && i > 0 && is_separator(cmdstr[i - 1], sep))
1258 				{
1259 					st = i--;
1260 					state = NO_QUOTING;
1261 				}
1262 				if(state != BEGIN && cmdstr[i] != '\0')
1263 				{
1264 					if(DA_EXTEND(argvp) != NULL)
1265 					{
1266 						DA_COMMIT(argvp);
1267 						argvp[DA_SIZE(argvp) - 1U][0] = i;
1268 					}
1269 				}
1270 				break;
1271 			case NO_QUOTING:
1272 				if(cmdstr[i] == '\0' || is_separator(cmdstr[i], sep))
1273 				{
1274 					state = ARG;
1275 				}
1276 				else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1277 				{
1278 					++i;
1279 				}
1280 				break;
1281 			case S_QUOTING:
1282 				if(cmdstr[i] == '\'')
1283 				{
1284 					if(cmdstr[i + 1] == '\'')
1285 					{
1286 						++i;
1287 					}
1288 					else
1289 					{
1290 						state = QARG;
1291 					}
1292 				}
1293 				break;
1294 			case D_QUOTING:
1295 				if(cmdstr[i] == '"')
1296 				{
1297 					state = QARG;
1298 				}
1299 				else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] != '\0')
1300 				{
1301 					++i;
1302 				}
1303 				break;
1304 			case R_QUOTING:
1305 				if(cmdstr[i] == '/')
1306 				{
1307 					state = QARG;
1308 				}
1309 				else if(!noescaping && cmdstr[i] == '\\' && cmdstr[i + 1] == '/')
1310 				{
1311 					++i;
1312 				}
1313 				break;
1314 
1315 			case ARG:
1316 			case QARG:
1317 				assert(0 && "Dispatch line state machine is broken");
1318 				break;
1319 		}
1320 		if(state == ARG || state == QARG)
1321 		{
1322 			/* Found another argument. */
1323 
1324 			const int end = (args - args_beg) + ((state == ARG) ? i : (i + 1));
1325 			char *last_arg;
1326 			const char c = cmdstr[i];
1327 			cmdstr[i] = '\0';
1328 
1329 			if(DA_SIZE(argvp) != 0U)
1330 			{
1331 				argvp[DA_SIZE(argvp) - 1U][1] = end;
1332 			}
1333 
1334 			*count = add_to_string_array(&params, *count, &cmdstr[st]);
1335 			if(*count == 0)
1336 			{
1337 				break;
1338 			}
1339 			last_arg = params[*count - 1];
1340 
1341 			cmdstr[i] = c;
1342 			switch(prev_state)
1343 			{
1344 				case NO_QUOTING:
1345 					if(!noescaping)
1346 					{
1347 						unescape(last_arg, sep != ' ');
1348 					}
1349 					break;
1350 				case  S_QUOTING: expand_squotes_escaping(last_arg); break;
1351 				case  D_QUOTING: expand_dquotes_escaping(last_arg); break;
1352 				case  R_QUOTING: unescape(last_arg, 1);             break;
1353 			}
1354 			state = BEGIN;
1355 		}
1356 	}
1357 
1358 	if(comments && state == D_QUOTING && strchr(&cmdstr[st], '"') == NULL)
1359 	{
1360 		state = BEGIN;
1361 		--st;
1362 		if(DA_SIZE(argvp) != 0U)
1363 		{
1364 			DA_REMOVE(argvp, &argvp[DA_SIZE(argvp) - 1U]);
1365 		}
1366 	}
1367 
1368 	free(cmdstr);
1369 
1370 	if(*count == 0 || (size_t)*count != DA_SIZE(argvp) ||
1371 			(state != BEGIN && state != NO_QUOTING) ||
1372 			put_into_string_array(&params, *count, NULL) != *count + 1)
1373 	{
1374 		free(argvp);
1375 		free_string_array(params, *count);
1376 		*count = (state == S_QUOTING || state == D_QUOTING || state == R_QUOTING)
1377 		       ? -1 : 0;
1378 		return NULL;
1379 	}
1380 
1381 	if(last_pos != NULL)
1382 	{
1383 		*last_pos = (args - args_beg) + st;
1384 	}
1385 
1386 	*positions = argvp;
1387 	return params;
1388 }
1389 
1390 char **
vle_cmds_list_udcs(void)1391 vle_cmds_list_udcs(void)
1392 {
1393 	char **p;
1394 	cmd_t *cur;
1395 
1396 	char **const list = reallocarray(NULL, inner->udf_count*2 + 1, sizeof(*list));
1397 	if(list == NULL)
1398 	{
1399 		return NULL;
1400 	}
1401 
1402 	p = list;
1403 
1404 	cur = inner->head.next;
1405 	while(cur != NULL)
1406 	{
1407 		if(cur->type == USER_CMD)
1408 		{
1409 			*p++ = strdup(cur->name);
1410 			*p++ = strdup(cur->cmd);
1411 		}
1412 		cur = cur->next;
1413 	}
1414 
1415 	*p = NULL;
1416 
1417 	return list;
1418 }
1419 
1420 char *
vle_cmds_print_udcs(const char beginning[])1421 vle_cmds_print_udcs(const char beginning[])
1422 {
1423 	size_t len;
1424 	cmd_t *cur;
1425 	char *content = NULL;
1426 	size_t content_len = 0;
1427 
1428 	cur = inner->head.next;
1429 	len = strlen(beginning);
1430 	while(cur != NULL)
1431 	{
1432 		void *ptr;
1433 		size_t new_size;
1434 
1435 		if(strncmp(cur->name, beginning, len) != 0 || cur->type != USER_CMD)
1436 		{
1437 			cur = cur->next;
1438 			continue;
1439 		}
1440 
1441 		if(content == NULL)
1442 		{
1443 			content = strdup("Command -- Action");
1444 			content_len = strlen(content);
1445 		}
1446 		new_size = content_len + 1 + strlen(cur->name) + 10 + strlen(cur->cmd) + 1;
1447 		ptr = realloc(content, new_size);
1448 		if(ptr != NULL)
1449 		{
1450 			content = ptr;
1451 			content_len += sprintf(content + content_len, "\n%-*s %s", 10, cur->name,
1452 					cur->cmd);
1453 		}
1454 		cur = cur->next;
1455 	}
1456 
1457 	return content;
1458 }
1459 
1460 char *
vle_cmds_past_arg(const char args[])1461 vle_cmds_past_arg(const char args[])
1462 {
1463 	while(!is_separator(*args, ' ') && *args != '\0')
1464 	{
1465 		++args;
1466 	}
1467 	return (char *)args;
1468 }
1469 
1470 char *
vle_cmds_at_arg(const char args[])1471 vle_cmds_at_arg(const char args[])
1472 {
1473 	while(is_separator(*args, ' '))
1474 	{
1475 		++args;
1476 	}
1477 	return (char *)args;
1478 }
1479 
1480 char *
vle_cmds_next_arg(const char args[])1481 vle_cmds_next_arg(const char args[])
1482 {
1483 	args = vle_cmds_past_arg(args);
1484 	return vle_cmds_at_arg(args);
1485 }
1486 
1487 /* Checks whether character is command separator.  Special case is space as a
1488  * separator, which means that any reasonable whitespace can be used as
1489  * separators (we don't won't to treat line feeds or similar characters as
1490  * separators to allow their appearance as part of arguments).  Returns non-zero
1491  * if c is counted to be a separator, otherwise zero is returned. */
1492 static int
is_separator(char c,char sep)1493 is_separator(char c, char sep)
1494 {
1495 	return (sep == ' ') ? (c == ' ' || c == '\t') : (c == sep);
1496 }
1497 
1498 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1499 /* vim: set cinoptions+=t0 filetype=c : */
1500