make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / cmd-tab.c
blob9f5ea7bf1876518bd7f397f855e1c170c5683b42
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ n_cmd_firstfit(): the table of commands + `help' and `list'.
3 *@ And n_cmd_arg_parse(), the (new) argument list parser. TODO this is
4 *@ TODO too stupid yet, however: it should fully support subcommands, too, so
5 *@ TODO that, e.g., "vexpr regex" arguments can be fully prepared by the
6 *@ TODO generic parser. But at least a bit.
7 *@ TODO See cmd-tab.h for sort and speedup TODOs.
9 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
12 /* Command table and getrawlist() also:
13 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
15 * Copyright (c) 1980, 1993
16 * The Regents of the University of California. All rights reserved.
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions
20 * are met:
21 * 1. Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 * 2. Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimer in the
25 * documentation and/or other materials provided with the distribution.
26 * 3. Neither the name of the University nor the names of its contributors
27 * may be used to endorse or promote products derived from this software
28 * without specific prior written permission.
30 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
42 #undef n_FILE
43 #define n_FILE cmd_tab
45 #ifndef HAVE_AMALGAMATION
46 # include "nail.h"
47 #endif
49 /* Create a multiline info string about all known additional infos for lcp */
50 #ifdef HAVE_DOCSTRINGS
51 static char const *a_ctab_cmdinfo(struct n_cmd_desc const *cdp);
52 #endif
54 /* Print a list of all commands */
55 static int a_ctab_c_list(void *vp);
57 static int a_ctab__pcmd_cmp(void const *s1, void const *s2);
59 /* `help' / `?' command */
60 static int a_ctab_c_help(void *vp);
62 /* List of all commands; but first their n_cmd_arg_desc instances */
63 #include "cmd-tab.h"
64 static struct n_cmd_desc const a_ctab_ctable[] = {
65 #include "cmd-tab.h"
68 /* And a list of things which are special to the lexer in go.c, so that we can
69 * provide help and list them.
70 * This cross-file relationship is a bit unfortunate.. */
71 #ifdef HAVE_DOCSTRINGS
72 # define DS(S) , S
73 #else
74 # define DS(S)
75 #endif
76 static struct n_cmd_desc const a_ctab_ctable_plus[] = {
77 { n_ns, (int(*)(void*))-1, n_CMD_ARG_TYPE_STRING, 0, 0, NULL
78 DS(N_("Comment command: ignore remaining (continuable) line")) },
79 { n_hy, (int(*)(void*))-1, n_CMD_ARG_TYPE_WYSH, 0, 0, NULL
80 DS(N_("Print out the preceding message")) }
82 #undef DS
84 #ifdef HAVE_DOCSTRINGS
85 static char const *
86 a_ctab_cmdinfo(struct n_cmd_desc const *cdp){
87 struct n_string rvb, *rv;
88 char const *cp;
89 NYD2_ENTER;
91 rv = n_string_creat_auto(&rvb);
92 rv = n_string_reserve(rv, 80);
94 switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){
95 case n_CMD_ARG_TYPE_MSGLIST:
96 cp = N_("message-list");
97 break;
98 case n_CMD_ARG_TYPE_NDMLIST:
99 cp = N_("message-list (without default)");
100 break;
101 case n_CMD_ARG_TYPE_STRING:
102 case n_CMD_ARG_TYPE_RAWDAT:
103 cp = N_("string data");
104 break;
105 case n_CMD_ARG_TYPE_RAWLIST:
106 cp = N_("old-style quoting");
107 break;
108 case n_CMD_ARG_TYPE_WYRA:
109 cp = N_("`wysh' for sh(1)ell-style quoting");
110 break;
111 case n_CMD_ARG_TYPE_WYSH:
112 cp = (cdp->cd_minargs == 0 && cdp->cd_maxargs == 0)
113 ? N_("sh(1)ell-style quoting (takes no arguments)")
114 : N_("sh(1)ell-style quoting");
115 break;
116 default:
117 case n_CMD_ARG_TYPE_ARG:{
118 ui32_t flags, xflags;
119 size_t i;
120 struct n_cmd_arg_desc const *cadp;
122 rv = n_string_push_cp(rv, _("argument tokens: "));
124 for(cadp = cdp->cd_cadp, i = 0; i < cadp->cad_no; ++i){
125 xflags = flags = cadp->cad_ent_flags[i][0];
126 jfakeent:
127 if(i != 0)
128 rv = n_string_push_c(rv, ',');
130 if(flags & n_CMD_ARG_DESC_OPTION)
131 rv = n_string_push_c(rv, '[');
132 if(flags & n_CMD_ARG_DESC_GREEDY)
133 rv = n_string_push_c(rv, ':');
134 switch(flags & n__CMD_ARG_DESC_TYPE_MASK){
135 default:
136 case n_CMD_ARG_DESC_SHEXP:
137 rv = n_string_push_cp(rv, _("(shell-)token"));
138 break;
139 case n_CMD_ARG_DESC_MSGLIST:
140 rv = n_string_push_cp(rv, _("(shell-)msglist"));
141 break;
142 case n_CMD_ARG_DESC_NDMSGLIST:
143 rv = n_string_push_cp(rv, _("(shell-)msglist (no default)"));
144 break;
145 case n_CMD_ARG_DESC_MSGLIST_AND_TARGET:
146 rv = n_string_push_cp(rv, _("(shell-)msglist"));
147 ++i;
148 xflags = n_CMD_ARG_DESC_SHEXP;
150 if(flags & n_CMD_ARG_DESC_GREEDY)
151 rv = n_string_push_c(rv, ':');
152 if(flags & n_CMD_ARG_DESC_OPTION)
153 rv = n_string_push_c(rv, ']');
155 if(xflags != flags){
156 flags = xflags;
157 goto jfakeent;
160 cp = NULL;
161 }break;
163 if(cp != NULL)
164 rv = n_string_push_cp(rv, V_(cp));
166 /* Note: on updates, change the manual! */
167 if(cdp->cd_caflags & n_CMD_ARG_L)
168 rv = n_string_push_cp(rv, _(" | `local'"));
169 if(cdp->cd_caflags & n_CMD_ARG_V)
170 rv = n_string_push_cp(rv, _(" | `vput'"));
171 if(cdp->cd_caflags & n_CMD_ARG_EM)
172 rv = n_string_push_cp(rv, _(" | *!*"));
174 if(cdp->cd_caflags & n_CMD_ARG_A)
175 rv = n_string_push_cp(rv, _(" | needs-box"));
177 if(cdp->cd_caflags & (n_CMD_ARG_I | n_CMD_ARG_M | n_CMD_ARG_X)){
178 rv = n_string_push_cp(rv, _(" | ok:"));
179 if(cdp->cd_caflags & n_CMD_ARG_I)
180 rv = n_string_push_cp(rv, _(" batch/interactive"));
181 if(cdp->cd_caflags & n_CMD_ARG_M)
182 rv = n_string_push_cp(rv, _(" send-mode"));
183 if(cdp->cd_caflags & n_CMD_ARG_X)
184 rv = n_string_push_cp(rv, _(" subprocess"));
187 if(cdp->cd_caflags & (n_CMD_ARG_R | n_CMD_ARG_S)){
188 rv = n_string_push_cp(rv, _(" | not ok:"));
189 if(cdp->cd_caflags & n_CMD_ARG_R)
190 rv = n_string_push_cp(rv, _(" compose-mode"));
191 if(cdp->cd_caflags & n_CMD_ARG_S)
192 rv = n_string_push_cp(rv, _(" startup"));
195 if(cdp->cd_caflags & n_CMD_ARG_G)
196 rv = n_string_push_cp(rv, _(" | gabby"));
198 cp = n_string_cp(rv);
199 NYD2_LEAVE;
200 return cp;
202 #endif /* HAVE_DOCSTRINGS */
204 static int
205 a_ctab_c_list(void *vp){
206 FILE *fp;
207 struct n_cmd_desc const **cdpa, *cdp, **cdpa_curr;
208 size_t i, l, scrwid;
209 NYD_ENTER;
211 i = n_NELEM(a_ctab_ctable) + n_NELEM(a_ctab_ctable_plus) +1;
212 cdpa = n_autorec_alloc(sizeof(cdp) * i);
214 for(i = 0; i < n_NELEM(a_ctab_ctable); ++i)
215 cdpa[i] = &a_ctab_ctable[i];
216 for(l = 0; l < n_NELEM(a_ctab_ctable_plus); ++i, ++l)
217 cdpa[i] = &a_ctab_ctable_plus[l];
218 cdpa[i] = NULL;
220 if(*(void**)vp == NULL)
221 qsort(cdpa, i, sizeof(*cdpa), &a_ctab__pcmd_cmp);
223 if((fp = Ftmp(NULL, "list", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
224 fp = n_stdout;
226 scrwid = n_SCRNWIDTH_FOR_LISTS;
228 fprintf(fp, _("Commands are:\n"));
229 l = 1;
230 for(i = 0, cdpa_curr = cdpa; (cdp = *cdpa_curr++) != NULL;){
231 char const *pre, *suf;
233 if(cdp->cd_func == NULL)
234 pre = "[", suf = "]";
235 else
236 pre = suf = n_empty;
238 #ifdef HAVE_DOCSTRINGS
239 if(n_poption & n_PO_D_V){
240 fprintf(fp, "%s%s%s\n", pre, cdp->cd_name, suf);
241 ++l;
242 fprintf(fp, " : %s\n", V_(cdp->cd_doc));
243 ++l;
244 fprintf(fp, " : %s\n", a_ctab_cmdinfo(cdp));
245 ++l;
246 }else
247 #endif
249 size_t j;
251 j = strlen(cdp->cd_name);
252 if(*pre != '\0')
253 j += 2;
255 if((i += j + 2) > scrwid){
256 i = j;
257 fprintf(fp, "\n");
258 ++l;
260 fprintf(fp, (*cdpa_curr != NULL ? "%s%s%s, " : "%s%s%s\n"),
261 pre, cdp->cd_name, suf);
265 if(fp != n_stdout){
266 page_or_print(fp, l);
267 Fclose(fp);
269 NYD_LEAVE;
270 return 0;
273 static int
274 a_ctab__pcmd_cmp(void const *s1, void const *s2){
275 struct n_cmd_desc const * const *cdpa1, * const *cdpa2;
276 int rv;
277 NYD2_ENTER;
279 cdpa1 = s1;
280 cdpa2 = s2;
281 rv = strcmp((*cdpa1)->cd_name, (*cdpa2)->cd_name);
282 NYD2_LEAVE;
283 return rv;
286 static int
287 a_ctab_c_help(void *vp){
288 int rv;
289 char const *arg;
290 NYD_ENTER;
292 /* Help for a single command? */
293 if((arg = *(char const**)vp) != NULL){
294 struct n_cmd_desc const *cdp, *cdp_max;
295 struct str const *alias_exp;
296 char const *alias_name, *aepx;
298 /* Aliases take precedence.
299 * Avoid self-recursion; since a commandalias can shadow a command of
300 * equal name allow one level of expansion to return an equal result:
301 * "commandalias q q;commandalias x q;x" should be "x->q->q->quit" */
302 alias_name = NULL;
303 while((aepx = n_commandalias_exists(arg, &alias_exp)) != NULL &&
304 (alias_name == NULL || strcmp(alias_name, aepx))){
305 alias_name = aepx;
306 fprintf(n_stdout, "%s -> ", arg);
307 arg = alias_exp->s;
310 cdp_max = &(cdp = a_ctab_ctable)[n_NELEM(a_ctab_ctable)];
311 jredo:
312 for(; cdp < cdp_max; ++cdp){
313 if(is_prefix(arg, cdp->cd_name)){
314 fputs(arg, n_stdout);
315 if(strcmp(arg, cdp->cd_name))
316 fprintf(n_stdout, " (%s)", cdp->cd_name);
317 }else
318 continue;
320 #ifdef HAVE_DOCSTRINGS
321 fprintf(n_stdout, ": %s", V_(cdp->cd_doc));
322 if(n_poption & n_PO_D_V)
323 fprintf(n_stdout, "\n : %s", a_ctab_cmdinfo(cdp));
324 #endif
325 putc('\n', n_stdout);
326 rv = 0;
327 goto jleave;
330 if(cdp_max == &a_ctab_ctable[n_NELEM(a_ctab_ctable)]){
331 cdp_max = &(cdp =
332 a_ctab_ctable_plus)[n_NELEM(a_ctab_ctable_plus)];
333 goto jredo;
336 if(alias_name != NULL){
337 fprintf(n_stdout, "%s\n", n_shexp_quote_cp(arg, TRU1));
338 rv = 0;
339 }else{
340 n_err(_("Unknown command: `%s'\n"), arg);
341 rv = 1;
343 }else{
344 /* Very ugly, but take care for compiler supported string lengths :( */
345 #ifdef HAVE_UISTRINGS
346 fputs(n_progname, n_stdout);
347 fputs(_(
348 " commands -- <msglist> denotes message specification tokens,\n"
349 "e.g., 1-5, :n or . (current, the \"dot\"), separated by *ifs*:\n"),
350 n_stdout);
351 fputs(_(
352 "\n"
353 "type <msglist> type (`print') messages (honour `headerpick' etc.)\n"
354 "Type <msglist> like `type' but always show all headers\n"
355 "next goto and type next message\n"
356 "headers header summary ... for messages surrounding \"dot\"\n"
357 "search <msglist> ... for the given list (alias for `from')\n"
358 "delete <msglist> delete messages (can be `undelete'd)\n"),
359 n_stdout);
361 fputs(_(
362 "\n"
363 "save <msglist> folder append messages to folder and mark as saved\n"
364 "copy <msglist> folder like `save', but do not mark them (`move' moves)\n"
365 "write <msglist> file write message contents to file (prompts for parts)\n"
366 "Reply <msglist> reply to message sender(s) only\n"
367 "reply <msglist> like `Reply', but address all recipients\n"
368 "Lreply <msglist> forced mailing list `reply' (see `mlist')\n"),
369 n_stdout);
371 fputs(_(
372 "\n"
373 "mail <recipients> compose a mail for the given recipients\n"
374 "file folder change to another mailbox\n"
375 "File folder like `file', but open readonly\n"
376 "quit quit and apply changes to the current mailbox\n"
377 "xit or exit like `quit', but discard changes\n"
378 "!shell command shell escape\n"
379 "list [<anything>] all available commands [in search order]\n"),
380 n_stdout);
381 #endif /* HAVE_UISTRINGS */
383 rv = (ferror(n_stdout) != 0);
385 jleave:
386 NYD_LEAVE;
387 return rv;
390 FL char const *
391 n_cmd_isolate(char const *cmd){
392 NYD2_ENTER;
393 while(*cmd != '\0' &&
394 strchr("\\!~|? \t0123456789&%@$^.:/-+*'\",;(`", *cmd) == NULL)
395 ++cmd;
396 NYD2_LEAVE;
397 return n_UNCONST(cmd);
400 FL struct n_cmd_desc const *
401 n_cmd_firstfit(char const *cmd){ /* TODO *hashtable*! linear list search!!! */
402 struct n_cmd_desc const *cdp;
403 NYD2_ENTER;
405 for(cdp = a_ctab_ctable; cdp < &a_ctab_ctable[n_NELEM(a_ctab_ctable)]; ++cdp)
406 if(*cmd == *cdp->cd_name && cdp->cd_func != NULL &&
407 is_prefix(cmd, cdp->cd_name))
408 goto jleave;
409 cdp = NULL;
410 jleave:
411 NYD2_LEAVE;
412 return cdp;
415 FL struct n_cmd_desc const *
416 n_cmd_default(void){
417 struct n_cmd_desc const *cdp;
418 NYD2_ENTER;
420 cdp = &a_ctab_ctable[0];
421 NYD2_LEAVE;
422 return cdp;
425 FL bool_t
426 n_cmd_arg_parse(struct n_cmd_arg_ctx *cacp){
427 struct n_cmd_arg ncap, *lcap, *target_argp, **target_argpp, *cap;
428 struct str shin_orig, shin;
429 bool_t stoploop, greedyjoin;
430 void const *cookie;
431 size_t cad_idx, parsed_args;
432 struct n_cmd_arg_desc const *cadp;
433 NYD_ENTER;
435 assert(cacp->cac_inlen == 0 || cacp->cac_indat != NULL);
436 assert(cacp->cac_desc->cad_no > 0);
437 #ifdef HAVE_DEBUG
438 /* C99 */{
439 bool_t opt_seen = FAL0;
441 for(cadp = cacp->cac_desc, cad_idx = 0;
442 cad_idx < cadp->cad_no; ++cad_idx){
443 assert(cadp->cad_ent_flags[cad_idx][0] & n__CMD_ARG_DESC_TYPE_MASK);
445 /* TODO n_CMD_ARG_DESC_MSGLIST+ may only be used as the last entry */
446 assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) ||
447 cad_idx + 1 == cadp->cad_no);
448 assert(!(cadp->cad_ent_flags[cad_idx][0] &
449 n_CMD_ARG_DESC_NDMSGLIST) || cad_idx + 1 == cadp->cad_no);
450 assert(!(cadp->cad_ent_flags[cad_idx][0] &
451 n_CMD_ARG_DESC_MSGLIST_AND_TARGET) ||
452 cad_idx + 1 == cadp->cad_no);
454 assert(!opt_seen ||
455 (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
456 if(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION)
457 opt_seen = TRU1;
458 assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY) ||
459 cad_idx + 1 == cadp->cad_no);
461 /* TODO n_CMD_ARG_DESC_MSGLIST+ can only be n_CMD_ARG_DESC_GREEDY.
462 * TODO And they may not be n_CMD_ARG_DESC_OPTION */
463 assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) ||
464 (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY));
465 assert(!(cadp->cad_ent_flags[cad_idx][0] &
466 n_CMD_ARG_DESC_NDMSGLIST) ||
467 (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY));
468 assert(!(cadp->cad_ent_flags[cad_idx][0] &
469 n_CMD_ARG_DESC_MSGLIST_AND_TARGET) ||
470 (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY));
472 assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) ||
473 !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
474 assert(!(cadp->cad_ent_flags[cad_idx][0] &
475 n_CMD_ARG_DESC_NDMSGLIST) ||
476 !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
477 assert(!(cadp->cad_ent_flags[cad_idx][0] &
478 n_CMD_ARG_DESC_MSGLIST_AND_TARGET) ||
479 !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
482 #endif /* HAVE_DEBUG */
484 n_pstate_err_no = n_ERR_NONE;
485 shin.s = n_UNCONST(cacp->cac_indat); /* "logical" only */
486 shin.l = (cacp->cac_inlen == UIZ_MAX ? strlen(shin.s) : cacp->cac_inlen);
487 shin_orig = shin;
488 cacp->cac_no = 0;
489 cacp->cac_arg = lcap = NULL;
491 cookie = NULL;
492 parsed_args = 0;
493 greedyjoin = FAL0;
495 /* TODO We need to test >= 0 in order to deal with MSGLIST arguments, as
496 * TODO those use getmsglist() and that needs to deal with that situation.
497 * TODO In the future that should change; see jmsglist_related TODO below */
498 for(cadp = cacp->cac_desc, cad_idx = 0;
499 /*shin.l >= 0 &&*/ cad_idx < cadp->cad_no; ++cad_idx){
500 jredo:
501 memset(&ncap, 0, sizeof ncap);
502 ncap.ca_indat = shin.s;
503 /* >ca_inline once we know */
504 memcpy(&ncap.ca_ent_flags[0], &cadp->cad_ent_flags[cad_idx][0],
505 sizeof ncap.ca_ent_flags);
506 target_argpp = NULL;
507 stoploop = FAL0;
509 switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){
510 default:
511 case n_CMD_ARG_DESC_SHEXP:{
512 struct n_string shou, *shoup;
513 enum n_shexp_state shs;
514 ui32_t addflags;
516 if(shin.l == 0) goto jmsglist_related; /* TODO */
518 if(cad_idx == cadp->cad_no - 1 ||
519 (cadp->cad_ent_flags[cad_idx + 1][0] & n_CMD_ARG_DESC_OPTION))
520 addflags = n_SHEXP_PARSE_META_SEMICOLON;
521 else
522 addflags = n_SHEXP_PARSE_NONE;
524 shoup = n_string_creat_auto(&shou);
525 ncap.ca_arg_flags =
526 shs = n_shexp_parse_token((ncap.ca_ent_flags[1] | addflags |
527 n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG),
528 shoup, &shin,
529 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY ? &cookie : NULL));
530 ncap.ca_inlen = PTR2SIZE(shin.s - ncap.ca_indat);
531 if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) ==
532 n_SHEXP_STATE_OUTPUT){
533 if((shs & n_SHEXP_STATE_META_SEMICOLON) && shou.s_len == 0)
534 break;
535 ncap.ca_arg.ca_str.s = n_string_cp(shoup);
536 ncap.ca_arg.ca_str.l = shou.s_len;
537 shoup = n_string_drop_ownership(shoup);
539 n_string_gut(shoup);
541 if(shs & n_SHEXP_STATE_ERR_MASK)
542 goto jerr;
543 if((shs & n_SHEXP_STATE_STOP) &&
544 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_HONOUR_STOP)){
545 if(!(shs & n_SHEXP_STATE_OUTPUT))
546 goto jleave;
547 stoploop = TRU1;
548 }else if(!(shs & n_SHEXP_STATE_OUTPUT)) /* XXX Is this right? */
549 goto jerr;
550 }break;
551 case n_CMD_ARG_DESC_MSGLIST_AND_TARGET:
552 target_argpp = &target_argp;
553 /* FALLTHRU */
554 case n_CMD_ARG_DESC_MSGLIST:
555 case n_CMD_ARG_DESC_NDMSGLIST:
556 /* TODO _MSGLIST yet at end and greedy only (fast hack).
557 * TODO And consumes too much memory */
558 assert(shin.s[shin.l] == '\0');
559 if(n_getmsglist(shin.s, (ncap.ca_arg.ca_msglist =
560 n_autorec_calloc(msgCount +1, sizeof *ncap.ca_arg.ca_msglist)
561 ), cacp->cac_msgflag, target_argpp) < 0){
562 n_pstate_err_no = n_ERR_INVAL; /* XXX should come from getmsglist*/
563 goto jerr;
566 if(ncap.ca_arg.ca_msglist[0] == 0){
567 ui32_t e;
569 switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){
570 case n_CMD_ARG_DESC_MSGLIST_AND_TARGET:
571 case n_CMD_ARG_DESC_MSGLIST:
572 if((ncap.ca_arg.ca_msglist[0] = first(cacp->cac_msgflag,
573 cacp->cac_msgmask)) == 0){
574 if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
575 (n_poption & n_PO_D_V))
576 n_err(_("No applicable messages\n"));
578 e = n_CMD_ARG_DESC_TO_ERRNO(ncap.ca_ent_flags[0]);
579 if(e == 0)
580 e = n_ERR_NODATA;
581 n_pstate_err_no = e;
582 goto jerr;
584 ncap.ca_arg.ca_msglist[1] = 0;
586 /* TODO For the MSGLIST_AND_TARGET case an entirely empty input
587 * TODO results in no _TARGET argument: ensure it is there! */
588 if(target_argpp != NULL && (cap = *target_argpp) == NULL){
589 cap = n_autorec_calloc(1, sizeof *cap);
590 cap->ca_arg.ca_str.s = n_UNCONST(n_empty);
591 *target_argpp = cap;
593 /* FALLTHRU */
594 default:
595 break;
597 }else if((ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_MSGLIST_NEEDS_SINGLE
598 ) && ncap.ca_arg.ca_msglist[1] != 0){
599 if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
600 (n_poption & n_PO_D_V))
601 n_err(_("Cannot specify multiple messages at once\n"));
602 n_pstate_err_no = n_ERR_NOTSUP;
603 goto jerr;
605 shin.l = 0;
606 stoploop = TRU1; /* XXX Asserted to be last above! */
607 break;
609 ++parsed_args;
611 if(greedyjoin == TRU1){ /* TODO speed this up! */
612 char *cp;
613 size_t i;
615 assert((ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK
616 ) != n_CMD_ARG_DESC_MSGLIST);
617 assert(lcap != NULL);
618 assert(target_argpp == NULL);
619 i = lcap->ca_arg.ca_str.l;
620 lcap->ca_arg.ca_str.l += 1 + ncap.ca_arg.ca_str.l;
621 cp = n_autorec_alloc(lcap->ca_arg.ca_str.l +1);
622 memcpy(cp, lcap->ca_arg.ca_str.s, i);
623 lcap->ca_arg.ca_str.s = cp;
624 cp[i++] = ' ';
625 memcpy(&cp[i], ncap.ca_arg.ca_str.s, ncap.ca_arg.ca_str.l +1);
626 }else{
627 cap = n_autorec_alloc(sizeof *cap);
628 memcpy(cap, &ncap, sizeof ncap);
629 if(lcap == NULL)
630 cacp->cac_arg = cap;
631 else
632 lcap->ca_next = cap;
633 lcap = cap;
634 ++cacp->cac_no;
636 if(target_argpp != NULL){
637 lcap->ca_next = cap = *target_argpp;
638 if(cap != NULL){
639 lcap = cap;
640 ++cacp->cac_no;
645 if(stoploop)
646 goto jleave;
648 if((shin.l > 0 || cookie != NULL) &&
649 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY)){
650 if(!greedyjoin)
651 greedyjoin = ((ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY_JOIN) &&
652 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_SHEXP))
653 ? TRU1 : TRUM1;
654 goto jredo;
658 jmsglist_related:
659 if(cad_idx < cadp->cad_no &&
660 !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION))
661 goto jerr;
663 lcap = (struct n_cmd_arg*)-1;
664 jleave:
665 NYD_LEAVE;
666 return (lcap != NULL);
668 jerr:
669 if(n_pstate_err_no == n_ERR_NONE){
670 n_pstate_err_no = n_ERR_INVAL;
672 if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
673 (n_poption & n_PO_D_V)){
674 size_t i;
676 for(i = 0; (i < cadp->cad_no &&
677 !(cadp->cad_ent_flags[i][0] & n_CMD_ARG_DESC_OPTION)); ++i)
680 n_err(_("`%s': parsing stopped after %" PRIuZ " arguments "
681 "(need %" PRIuZ "%s)\n"
682 " Input: %.*s\n"
683 " Stopped: %.*s\n"),
684 cadp->cad_name, parsed_args,
685 i, (i == cadp->cad_no ? n_empty : "+"),
686 (int)shin_orig.l, shin_orig.s,
687 (int)shin.l, shin.s);
690 lcap = NULL;
691 goto jleave;
694 FL void *
695 n_cmd_arg_save_to_heap(struct n_cmd_arg_ctx const *cacp){
696 struct n_cmd_arg *ncap;
697 struct n_cmd_arg_ctx *ncacp;
698 char *buf;
699 struct n_cmd_arg const *cap;
700 size_t len, i;
701 NYD2_ENTER;
703 /* For simplicity, save it all in once chunk, so that it can be thrown away
704 * with a simple n_free() from whoever is concerned */
705 len = sizeof *cacp;
706 for(cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){
707 i = cap->ca_arg.ca_str.l +1;
708 i = n_ALIGN(i);
709 len += sizeof(*cap) + i;
711 if(cacp->cac_vput != NULL)
712 len += strlen(cacp->cac_vput) +1;
714 ncacp = n_alloc(len);
715 *ncacp = *cacp;
716 buf = (char*)&ncacp[1];
718 for(ncap = NULL, cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){
719 void *vp;
721 vp = buf;
722 DBG( memset(vp, 0, sizeof *ncap); )
724 if(ncap == NULL)
725 ncacp->cac_arg = vp;
726 else
727 ncap->ca_next = vp;
728 ncap = vp;
729 ncap->ca_next = NULL;
730 ncap->ca_ent_flags[0] = cap->ca_ent_flags[0];
731 ncap->ca_ent_flags[1] = cap->ca_ent_flags[1];
732 ncap->ca_arg_flags = cap->ca_arg_flags;
733 memcpy(ncap->ca_arg.ca_str.s = (char*)&ncap[1], cap->ca_arg.ca_str.s,
734 (ncap->ca_arg.ca_str.l = i = cap->ca_arg.ca_str.l) +1);
736 i = n_ALIGN(i);
737 buf += sizeof(*ncap) + i;
740 if(cacp->cac_vput != NULL){
741 ncacp->cac_vput = buf;
742 memcpy(buf, cacp->cac_vput, strlen(cacp->cac_vput) +1);
743 }else
744 ncacp->cac_vput = NULL;
745 NYD2_LEAVE;
746 return ncacp;
749 FL struct n_cmd_arg_ctx *
750 n_cmd_arg_restore_from_heap(void *vp){
751 struct n_cmd_arg *cap, *ncap;
752 struct n_cmd_arg_ctx *cacp, *rv;
753 NYD2_ENTER;
755 rv = n_autorec_alloc(sizeof *rv);
756 cacp = vp;
757 *rv = *cacp;
759 for(ncap = NULL, cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){
760 vp = n_autorec_alloc(sizeof(*ncap) + cap->ca_arg.ca_str.l +1);
761 DBG( memset(vp, 0, sizeof *ncap); )
763 if(ncap == NULL)
764 rv->cac_arg = vp;
765 else
766 ncap->ca_next = vp;
767 ncap = vp;
768 ncap->ca_next = NULL;
769 ncap->ca_ent_flags[0] = cap->ca_ent_flags[0];
770 ncap->ca_ent_flags[1] = cap->ca_ent_flags[1];
771 ncap->ca_arg_flags = cap->ca_arg_flags;
772 memcpy(ncap->ca_arg.ca_str.s = (char*)&ncap[1], cap->ca_arg.ca_str.s,
773 (ncap->ca_arg.ca_str.l = cap->ca_arg.ca_str.l) +1);
776 if(cacp->cac_vput != NULL)
777 rv->cac_vput = savestr(cacp->cac_vput);
778 NYD2_LEAVE;
779 return rv;
782 FL int
783 getrawlist(bool_t wysh, char **res_dat, size_t res_size,
784 char const *line, size_t linesize){
785 int res_no;
786 NYD_ENTER;
788 n_pstate &= ~n_PS_ARGLIST_MASK;
790 if(res_size == 0){
791 res_no = -1;
792 goto jleave;
793 }else if(UICMP(z, res_size, >, INT_MAX))
794 res_size = INT_MAX;
795 else
796 --res_size;
797 res_no = 0;
799 if(!wysh){
800 /* And assuming result won't grow input */
801 char c2, c, quotec, *cp2, *linebuf;
803 linebuf = n_lofi_alloc(linesize);
805 for(;;){
806 for(; blankchar(*line); ++line)
808 if(*line == '\0')
809 break;
811 if(UICMP(z, res_no, >=, res_size)){
812 n_err(_("Too many input tokens for result storage\n"));
813 res_no = -1;
814 break;
817 cp2 = linebuf;
818 quotec = '\0';
820 /* TODO v15: complete switch in order mirror known behaviour */
821 while((c = *line++) != '\0'){
822 if(quotec != '\0'){
823 if(c == quotec){
824 quotec = '\0';
825 continue;
826 }else if(c == '\\'){
827 if((c2 = *line++) == quotec)
828 c = c2;
829 else
830 --line;
832 }else if(c == '"' || c == '\''){
833 quotec = c;
834 continue;
835 }else if(c == '\\'){
836 if((c2 = *line++) != '\0')
837 c = c2;
838 else
839 --line;
840 }else if(blankchar(c))
841 break;
842 *cp2++ = c;
845 res_dat[res_no++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
846 if(c == '\0')
847 break;
850 n_lofi_free(linebuf);
851 }else{
852 /* sh(1) compat mode. Prepare shell token-wise */
853 struct n_string store;
854 struct str input;
855 void const *cookie;
857 n_string_creat_auto(&store);
858 input.s = n_UNCONST(line);
859 input.l = linesize;
860 cookie = NULL;
862 for(;;){
863 if(UICMP(z, res_no, >=, res_size)){
864 n_err(_("Too many input tokens for result storage\n"));
865 res_no = -1;
866 break;
869 /* C99 */{
870 enum n_shexp_state shs;
872 if((shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
873 (cookie == NULL ? n_SHEXP_PARSE_TRIM_SPACE : 0) |
874 /* TODO not here in old style n_SHEXP_PARSE_IFS_VAR |*/
875 n_SHEXP_PARSE_META_SEMICOLON),
876 &store, &input, &cookie)
877 ) & n_SHEXP_STATE_ERR_MASK){
878 /* Simply ignore Unicode error, just keep the normalized \[Uu] */
879 if((shs & n_SHEXP_STATE_ERR_MASK) != n_SHEXP_STATE_ERR_UNICODE){
880 res_no = -1;
881 break;
885 if(shs & n_SHEXP_STATE_OUTPUT){
886 res_dat[res_no++] = n_string_cp(&store);
887 n_string_drop_ownership(&store);
890 if(shs & n_SHEXP_STATE_STOP)
891 break;
895 n_string_gut(&store);
898 if(res_no >= 0)
899 res_dat[(size_t)res_no] = NULL;
900 jleave:
901 NYD_LEAVE;
902 return res_no;
905 /* s-it-mode */