1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
13 #include <ecoli_malloc.h>
14 #include <ecoli_log.h>
15 #include <ecoli_string.h>
16 #include <ecoli_test.h>
17 #include <ecoli_strvec.h>
18 #include <ecoli_node.h>
19 #include <ecoli_parse.h>
20 #include <ecoli_complete.h>
21 #include <ecoli_node_seq.h>
22 #include <ecoli_node_str.h>
23 #include <ecoli_node_option.h>
24 #include <ecoli_node_sh_lex.h>
26 EC_LOG_TYPE_REGISTER(node_sh_lex);
28 struct ec_node_sh_lex {
29 struct ec_node *child;
32 static size_t eat_spaces(const char *str)
37 while (isblank(str[i]))
44 * Allocate a new string which is a copy of the input string with quotes
45 * removed. If quotes are not closed properly, set missing_quote to the
48 static char *unquote_str(const char *str, size_t n, int allow_missing_quote,
51 unsigned s = 1, d = 0;
62 /* copy string and remove quotes */
63 while (s < n && d < n && str[s] != '\0') {
64 if (str[s] == '\\' && str[s+1] == quote) {
69 if (str[s] == '\\' && str[s+1] == '\\') {
74 if (str[s] == quote) {
82 /* not enough room in dst buffer (should not happen) */
89 /* quote not closed */
91 if (missing_quote != NULL)
92 *missing_quote = str[0];
93 if (allow_missing_quote == 0) {
104 static size_t eat_quoted_str(const char *str)
109 while (str[i] != '\0') {
110 if (str[i] != '\\' && str[i+1] == quote)
115 /* unclosed quote, will be detected later */
119 static size_t eat_str(const char *str)
123 /* eat chars until we find a quote, space, or end of string */
124 while (!isblank(str[i]) && str[i] != '\0' &&
125 str[i] != '"' && str[i] != '\'')
131 static struct ec_strvec *tokenize(const char *str, int completion,
132 int allow_missing_quote, char *missing_quote)
134 struct ec_strvec *strvec = NULL;
135 size_t off = 0, len, suboff, sublen;
136 char *word = NULL, *concat = NULL, *tmp;
137 int last_is_space = 1;
139 strvec = ec_strvec();
143 while (str[off] != '\0') {
144 if (missing_quote != NULL)
145 *missing_quote = '\0';
146 len = eat_spaces(&str[off]);
153 while (str[suboff] != '\0') {
154 if (missing_quote != NULL)
155 *missing_quote = '\0';
157 if (str[suboff] == '"' || str[suboff] == '\'') {
158 sublen = eat_quoted_str(&str[suboff]);
159 word = unquote_str(&str[suboff], sublen,
160 allow_missing_quote, missing_quote);
162 sublen = eat_str(&str[suboff]);
165 word = ec_strndup(&str[suboff], sublen);
174 if (concat == NULL) {
178 tmp = ec_realloc(concat, len + 1);
182 strcat(concat, word);
188 if (concat != NULL) {
189 if (ec_strvec_add(strvec, concat) < 0)
198 /* in completion mode, append an empty string in the vector if
199 * the input string ends with space */
200 if (completion && last_is_space) {
201 if (ec_strvec_add(strvec, "") < 0)
210 ec_strvec_free(strvec);
215 ec_node_sh_lex_parse(const struct ec_node *node,
216 struct ec_pnode *state,
217 const struct ec_strvec *strvec)
219 struct ec_node_sh_lex *priv = ec_node_priv(node);
220 struct ec_strvec *new_vec = NULL;
221 struct ec_pnode *child_parse;
225 if (ec_strvec_len(strvec) == 0) {
226 new_vec = ec_strvec();
228 str = ec_strvec_val(strvec, 0);
229 new_vec = tokenize(str, 0, 0, NULL);
231 if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */
232 return EC_PARSE_NOMATCH;
236 ret = ec_parse_child(priv->child, state, new_vec);
240 if ((unsigned)ret == ec_strvec_len(new_vec)) {
242 } else if (ret != EC_PARSE_NOMATCH) {
243 child_parse = ec_pnode_get_last_child(state);
244 ec_pnode_unlink_child(state, child_parse);
245 ec_pnode_free(child_parse);
246 ret = EC_PARSE_NOMATCH;
249 ec_strvec_free(new_vec);
255 ec_strvec_free(new_vec);
260 ec_node_sh_lex_complete(const struct ec_node *node,
261 struct ec_comp *comp,
262 const struct ec_strvec *strvec)
264 struct ec_node_sh_lex *priv = ec_node_priv(node);
265 struct ec_comp *tmp_comp = NULL;
266 struct ec_strvec *new_vec = NULL;
267 struct ec_comp_iter *iter = NULL;
268 struct ec_comp_item *item = NULL;
269 char *new_str = NULL;
271 char missing_quote = '\0';
274 if (ec_strvec_len(strvec) != 1)
277 str = ec_strvec_val(strvec, 0);
278 new_vec = tokenize(str, 1, 1, &missing_quote);
282 /* we will store the completions in a temporary struct, because
283 * we want to update them (ex: add missing quotes) */
284 tmp_comp = ec_comp(ec_comp_get_state(comp));
285 if (tmp_comp == NULL)
288 ret = ec_complete_child(priv->child, tmp_comp, new_vec);
292 /* add missing quote for full completions */
293 if (missing_quote != '\0') {
294 iter = ec_comp_iter(tmp_comp, EC_COMP_FULL);
297 while ((item = ec_comp_iter_next(iter)) != NULL) {
298 str = ec_comp_item_get_str(item);
299 if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
300 missing_quote) < 0) {
304 if (ec_comp_item_set_str(item, new_str) < 0)
309 str = ec_comp_item_get_completion(item);
310 if (ec_asprintf(&new_str, "%s%c", str,
311 missing_quote) < 0) {
315 if (ec_comp_item_set_completion(item, new_str) < 0)
322 ec_comp_iter_free(iter);
323 ec_strvec_free(new_vec);
325 ec_comp_merge(comp, tmp_comp);
330 ec_comp_free(tmp_comp);
331 ec_comp_iter_free(iter);
332 ec_strvec_free(new_vec);
338 static void ec_node_sh_lex_free_priv(struct ec_node *node)
340 struct ec_node_sh_lex *priv = ec_node_priv(node);
342 ec_node_free(priv->child);
346 ec_node_sh_lex_get_children_count(const struct ec_node *node)
348 struct ec_node_sh_lex *priv = ec_node_priv(node);
356 ec_node_sh_lex_get_child(const struct ec_node *node, size_t i,
357 struct ec_node **child, unsigned int *refs)
359 struct ec_node_sh_lex *priv = ec_node_priv(node);
365 *child = priv->child;
369 static struct ec_node_type ec_node_sh_lex_type = {
371 .parse = ec_node_sh_lex_parse,
372 .complete = ec_node_sh_lex_complete,
373 .size = sizeof(struct ec_node_sh_lex),
374 .free_priv = ec_node_sh_lex_free_priv,
375 .get_children_count = ec_node_sh_lex_get_children_count,
376 .get_child = ec_node_sh_lex_get_child,
379 EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
381 struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
383 struct ec_node *node = NULL;
384 struct ec_node_sh_lex *priv;
389 node = ec_node_from_type(&ec_node_sh_lex_type, id);
395 priv = ec_node_priv(node);
401 /* LCOV_EXCL_START */
402 static int ec_node_sh_lex_testcase(void)
404 struct ec_node *node;
407 node = ec_node_sh_lex(EC_NO_ID,
408 EC_NODE_SEQ(EC_NO_ID,
409 ec_node_str(EC_NO_ID, "foo"),
410 ec_node_option(EC_NO_ID,
411 ec_node_str(EC_NO_ID, "toto")
413 ec_node_str(EC_NO_ID, "bar")
417 EC_LOG(EC_LOG_ERR, "cannot create node\n");
420 testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
421 testres |= EC_TEST_CHECK_PARSE(node, 1, " foo bar");
422 testres |= EC_TEST_CHECK_PARSE(node, 1, " 'foo' \"bar\"");
423 testres |= EC_TEST_CHECK_PARSE(node, 1, " 'f'oo 'toto' bar");
424 testres |= EC_TEST_CHECK_PARSE(node, -1, " foo toto bar'");
427 /* test completion */
428 node = ec_node_sh_lex(EC_NO_ID,
429 EC_NODE_SEQ(EC_NO_ID,
430 ec_node_str(EC_NO_ID, "foo"),
431 ec_node_option(EC_NO_ID,
432 ec_node_str(EC_NO_ID, "toto")
434 ec_node_str(EC_NO_ID, "bar"),
435 ec_node_str(EC_NO_ID, "titi")
439 EC_LOG(EC_LOG_ERR, "cannot create node\n");
442 testres |= EC_TEST_CHECK_COMPLETE(node,
445 testres |= EC_TEST_CHECK_COMPLETE(node,
448 testres |= EC_TEST_CHECK_COMPLETE(node,
451 testres |= EC_TEST_CHECK_COMPLETE(node,
454 testres |= EC_TEST_CHECK_COMPLETE(node,
456 "bar", "toto", EC_VA_END);
457 testres |= EC_TEST_CHECK_COMPLETE(node,
460 testres |= EC_TEST_CHECK_COMPLETE(node,
463 testres |= EC_TEST_CHECK_COMPLETE(node,
464 "foo bar", EC_VA_END,
466 testres |= EC_TEST_CHECK_COMPLETE(node,
467 "foo bar ", EC_VA_END,
469 testres |= EC_TEST_CHECK_COMPLETE(node,
470 "foo toto bar ", EC_VA_END,
472 testres |= EC_TEST_CHECK_COMPLETE(node,
475 testres |= EC_TEST_CHECK_COMPLETE(node,
476 "foo barx", EC_VA_END,
478 testres |= EC_TEST_CHECK_COMPLETE(node,
487 static struct ec_test ec_node_sh_lex_test = {
488 .name = "node_sh_lex",
489 .test = ec_node_sh_lex_testcase,
492 EC_TEST_REGISTER(ec_node_sh_lex_test);