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
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
43 #define n_FILE cmd_tab
45 #ifndef HAVE_AMALGAMATION
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
);
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 */
64 static struct n_cmd_desc
const a_ctab_ctable
[] = {
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
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")) }
84 #ifdef HAVE_DOCSTRINGS
86 a_ctab_cmdinfo(struct n_cmd_desc
const *cdp
){
87 struct n_string rvb
, *rv
;
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");
98 case n_CMD_ARG_TYPE_NDMLIST
:
99 cp
= N_("message-list (without default)");
101 case n_CMD_ARG_TYPE_STRING
:
102 case n_CMD_ARG_TYPE_RAWDAT
:
103 cp
= N_("string data");
105 case n_CMD_ARG_TYPE_RAWLIST
:
106 cp
= N_("old-style quoting");
108 case n_CMD_ARG_TYPE_WYRA
:
109 cp
= N_("`wysh' for sh(1)ell-style quoting");
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");
117 case n_CMD_ARG_TYPE_ARG
:{
118 ui32_t flags
, xflags
;
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];
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
){
136 case n_CMD_ARG_DESC_SHEXP
:
137 rv
= n_string_push_cp(rv
, _("(shell-)token"));
139 case n_CMD_ARG_DESC_MSGLIST
:
140 rv
= n_string_push_cp(rv
, _("(shell-)msglist"));
142 case n_CMD_ARG_DESC_NDMSGLIST
:
143 rv
= n_string_push_cp(rv
, _("(shell-)msglist (no default)"));
145 case n_CMD_ARG_DESC_MSGLIST_AND_TARGET
:
146 rv
= n_string_push_cp(rv
, _("(shell-)msglist"));
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
, ']');
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
);
202 #endif /* HAVE_DOCSTRINGS */
205 a_ctab_c_list(void *vp
){
207 struct n_cmd_desc
const **cdpa
, *cdp
, **cdpa_curr
;
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
];
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
)
226 scrwid
= n_SCRNWIDTH_FOR_LISTS
;
228 fprintf(fp
, _("Commands are:\n"));
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
= "]";
238 #ifdef HAVE_DOCSTRINGS
239 if(n_poption
& n_PO_D_V
){
240 fprintf(fp
, "%s%s%s\n", pre
, cdp
->cd_name
, suf
);
242 fprintf(fp
, " : %s\n", V_(cdp
->cd_doc
));
244 fprintf(fp
, " : %s\n", a_ctab_cmdinfo(cdp
));
251 j
= strlen(cdp
->cd_name
);
255 if((i
+= j
+ 2) > scrwid
){
260 fprintf(fp
, (*cdpa_curr
!= NULL
? "%s%s%s, " : "%s%s%s\n"),
261 pre
, cdp
->cd_name
, suf
);
266 page_or_print(fp
, l
);
274 a_ctab__pcmd_cmp(void const *s1
, void const *s2
){
275 struct n_cmd_desc
const * const *cdpa1
, * const *cdpa2
;
281 rv
= strcmp((*cdpa1
)->cd_name
, (*cdpa2
)->cd_name
);
287 a_ctab_c_help(void *vp
){
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" */
303 while((aepx
= n_commandalias_exists(arg
, &alias_exp
)) != NULL
&&
304 (alias_name
== NULL
|| strcmp(alias_name
, aepx
))){
306 fprintf(n_stdout
, "%s -> ", arg
);
310 cdp_max
= &(cdp
= a_ctab_ctable
)[n_NELEM(a_ctab_ctable
)];
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
);
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
));
325 putc('\n', n_stdout
);
330 if(cdp_max
== &a_ctab_ctable
[n_NELEM(a_ctab_ctable
)]){
332 a_ctab_ctable_plus
)[n_NELEM(a_ctab_ctable_plus
)];
336 if(alias_name
!= NULL
){
337 fprintf(n_stdout
, "%s\n", n_shexp_quote_cp(arg
, TRU1
));
340 n_err(_("Unknown command: `%s'\n"), arg
);
344 /* Very ugly, but take care for compiler supported string lengths :( */
345 #ifdef HAVE_UISTRINGS
346 fputs(n_progname
, n_stdout
);
348 " commands -- <msglist> denotes message specification tokens,\n"
349 "e.g., 1-5, :n or . (current, the \"dot\"), separated by *ifs*:\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"),
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"),
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"),
381 #endif /* HAVE_UISTRINGS */
383 rv
= (ferror(n_stdout
) != 0);
391 n_cmd_isolate(char const *cmd
){
393 while(*cmd
!= '\0' &&
394 strchr("\\!~|? \t0123456789&%@$^.:/-+*'\",;(`", *cmd
) == NULL
)
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
;
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
))
415 FL
struct n_cmd_desc
const *
417 struct n_cmd_desc
const *cdp
;
420 cdp
= &a_ctab_ctable
[0];
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
;
431 size_t cad_idx
, parsed_args
;
432 struct n_cmd_arg_desc
const *cadp
;
435 assert(cacp
->cac_inlen
== 0 || cacp
->cac_indat
!= NULL
);
436 assert(cacp
->cac_desc
->cad_no
> 0);
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
);
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
)
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
);
489 cacp
->cac_arg
= lcap
= NULL
;
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
){
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
);
509 switch(ncap
.ca_ent_flags
[0] & n__CMD_ARG_DESC_TYPE_MASK
){
511 case n_CMD_ARG_DESC_SHEXP
:{
512 struct n_string shou
, *shoup
;
513 enum n_shexp_state shs
;
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
;
522 addflags
= n_SHEXP_PARSE_NONE
;
524 shoup
= n_string_creat_auto(&shou
);
526 shs
= n_shexp_parse_token((ncap
.ca_ent_flags
[1] | addflags
|
527 n_SHEXP_PARSE_TRIM_SPACE
| n_SHEXP_PARSE_LOG
),
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)
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
);
541 if(shs
& n_SHEXP_STATE_ERR_MASK
)
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
))
548 }else if(!(shs
& n_SHEXP_STATE_OUTPUT
)) /* XXX Is this right? */
551 case n_CMD_ARG_DESC_MSGLIST_AND_TARGET
:
552 target_argpp
= &target_argp
;
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*/
566 if(ncap
.ca_arg
.ca_msglist
[0] == 0){
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]);
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
);
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
;
606 stoploop
= TRU1
; /* XXX Asserted to be last above! */
611 if(greedyjoin
== TRU1
){ /* TODO speed this up! */
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
;
625 memcpy(&cp
[i
], ncap
.ca_arg
.ca_str
.s
, ncap
.ca_arg
.ca_str
.l
+1);
627 cap
= n_autorec_alloc(sizeof *cap
);
628 memcpy(cap
, &ncap
, sizeof ncap
);
636 if(target_argpp
!= NULL
){
637 lcap
->ca_next
= cap
= *target_argpp
;
648 if((shin
.l
> 0 || cookie
!= NULL
) &&
649 (ncap
.ca_ent_flags
[0] & n_CMD_ARG_DESC_GREEDY
)){
651 greedyjoin
= ((ncap
.ca_ent_flags
[0] & n_CMD_ARG_DESC_GREEDY_JOIN
) &&
652 (ncap
.ca_ent_flags
[0] & n_CMD_ARG_DESC_SHEXP
))
659 if(cad_idx
< cadp
->cad_no
&&
660 !(cadp
->cad_ent_flags
[cad_idx
][0] & n_CMD_ARG_DESC_OPTION
))
663 lcap
= (struct n_cmd_arg
*)-1;
666 return (lcap
!= NULL
);
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
)){
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"
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
);
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
;
699 struct n_cmd_arg
const *cap
;
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 */
706 for(cap
= cacp
->cac_arg
; cap
!= NULL
; cap
= cap
->ca_next
){
707 i
= cap
->ca_arg
.ca_str
.l
+1;
709 len
+= sizeof(*cap
) + i
;
711 if(cacp
->cac_vput
!= NULL
)
712 len
+= strlen(cacp
->cac_vput
) +1;
714 ncacp
= n_alloc(len
);
716 buf
= (char*)&ncacp
[1];
718 for(ncap
= NULL
, cap
= cacp
->cac_arg
; cap
!= NULL
; cap
= cap
->ca_next
){
722 DBG( memset(vp
, 0, sizeof *ncap
); )
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);
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);
744 ncacp
->cac_vput
= NULL
;
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
;
755 rv
= n_autorec_alloc(sizeof *rv
);
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
); )
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
);
783 getrawlist(bool_t wysh
, char **res_dat
, size_t res_size
,
784 char const *line
, size_t linesize
){
788 n_pstate
&= ~n_PS_ARGLIST_MASK
;
793 }else if(UICMP(z
, res_size
, >, INT_MAX
))
800 /* And assuming result won't grow input */
801 char c2
, c
, quotec
, *cp2
, *linebuf
;
803 linebuf
= n_lofi_alloc(linesize
);
806 for(; blankchar(*line
); ++line
)
811 if(UICMP(z
, res_no
, >=, res_size
)){
812 n_err(_("Too many input tokens for result storage\n"));
820 /* TODO v15: complete switch in order mirror known behaviour */
821 while((c
= *line
++) != '\0'){
827 if((c2
= *line
++) == quotec
)
832 }else if(c
== '"' || c
== '\''){
836 if((c2
= *line
++) != '\0')
840 }else if(blankchar(c
))
845 res_dat
[res_no
++] = savestrbuf(linebuf
, PTR2SIZE(cp2
- linebuf
));
850 n_lofi_free(linebuf
);
852 /* sh(1) compat mode. Prepare shell token-wise */
853 struct n_string store
;
857 n_string_creat_auto(&store
);
858 input
.s
= n_UNCONST(line
);
863 if(UICMP(z
, res_no
, >=, res_size
)){
864 n_err(_("Too many input tokens for result storage\n"));
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
){
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
)
895 n_string_gut(&store
);
899 res_dat
[(size_t)res_no
] = NULL
;