nail.1: tweak text of -a
[s-mailx.git] / cmd_arg.c
blobbcaf506cc74ff3e76ee0e41230c3374ee99cc42d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Command argument list parser(s). TODO partial: no msglist in here, etc.
4 * Copyright (c) 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 /* getrawlist(): */
20 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
21 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
24 * Copyright (c) 1980, 1993
25 * The Regents of the University of California. All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 * notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 * notice, this list of conditions and the following disclaimer in the
34 * documentation and/or other materials provided with the distribution.
35 * 3. Neither the name of the University nor the names of its contributors
36 * may be used to endorse or promote products derived from this software
37 * without specific prior written permission.
39 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
40 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
41 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
42 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
43 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
44 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
45 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
46 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
47 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
48 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
49 * SUCH DAMAGE.
51 #undef n_FILE
52 #define n_FILE cmd_arg
54 #ifndef HAVE_AMALGAMATION
55 # include "nail.h"
56 #endif
58 FL int
59 getrawlist(bool_t wysh, char **res_dat, size_t res_size,
60 char const *line, size_t linesize){
61 int res_no;
62 NYD_ENTER;
64 pstate &= ~PS_ARGLIST_MASK;
66 if(res_size == 0){
67 res_no = -1;
68 goto jleave;
69 }else if(UICMP(z, res_size, >, INT_MAX))
70 res_size = INT_MAX;
71 else
72 --res_size;
73 res_no = 0;
75 if(!wysh){
76 /* And assuming result won't grow input */
77 char c2, c, quotec, *cp2, *linebuf;
79 linebuf = n_lofi_alloc(linesize);
81 for(;;){
82 for(; blankchar(*line); ++line)
84 if(*line == '\0')
85 break;
87 if(UICMP(z, res_no, >=, res_size)){
88 n_err(_("Too many input tokens for result storage\n"));
89 res_no = -1;
90 break;
93 cp2 = linebuf;
94 quotec = '\0';
96 /* TODO v15: complete switch in order mirror known behaviour */
97 while((c = *line++) != '\0'){
98 if(quotec != '\0'){
99 if(c == quotec){
100 quotec = '\0';
101 continue;
102 }else if(c == '\\'){
103 if((c2 = *line++) == quotec)
104 c = c2;
105 else
106 --line;
108 }else if(c == '"' || c == '\''){
109 quotec = c;
110 continue;
111 }else if(c == '\\'){
112 if((c2 = *line++) != '\0')
113 c = c2;
114 else
115 --line;
116 }else if(blankchar(c))
117 break;
118 *cp2++ = c;
121 res_dat[res_no++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
122 if(c == '\0')
123 break;
126 n_lofi_free(linebuf);
127 }else{
128 /* sh(1) compat mode. Prepare shell token-wise */
129 struct n_string store;
130 struct str input;
132 n_string_creat_auto(&store);
133 input.s = n_UNCONST(line);
134 input.l = linesize;
136 for(;;){
137 for(; blankchar(*line); ++line)
139 if(*line == '\0')
140 break;
142 if(UICMP(z, res_no, >=, res_size)){
143 n_err(_("Too many input tokens for result storage\n"));
144 res_no = -1;
145 break;
148 input.l -= PTR2SIZE(line - input.s);
149 input.s = n_UNCONST(line);
150 /* C99 */{
151 enum n_shexp_state shs;
153 if((shs = n_shexp_parse_token(&store, &input, n_SHEXP_PARSE_LOG)) &
154 n_SHEXP_STATE_ERR_MASK){
155 /* Simply ignore Unicode error, just keep the normalized \[Uu] */
156 if((shs & n_SHEXP_STATE_ERR_MASK) != n_SHEXP_STATE_ERR_UNICODE){
157 res_no = -1;
158 break;
162 if(shs & n_SHEXP_STATE_OUTPUT){
163 if(shs & n_SHEXP_STATE_CONTROL)
164 pstate |= PS_WYSHLIST_SAW_CONTROL;
166 res_dat[res_no++] = n_string_cp(&store);
167 n_string_drop_ownership(&store);
170 if(shs & n_SHEXP_STATE_STOP)
171 break;
173 line = input.s;
176 n_string_gut(&store);
179 if(res_no >= 0)
180 res_dat[(size_t)res_no] = NULL;
181 jleave:
182 NYD_LEAVE;
183 return res_no;
186 FL bool_t
187 n_cmd_arg_parse(struct n_cmd_arg_ctx *cacp){
188 struct n_cmd_arg ncap, *lcap;
189 struct str shin_orig, shin;
190 bool_t addca;
191 size_t cad_no, parsed_args;
192 struct n_cmd_arg_desc const *cadp;
193 NYD_ENTER;
195 assert(cacp->cac_inlen == 0 || cacp->cac_indat != NULL);
196 assert(cacp->cac_desc->cad_no > 0);
197 #ifdef HAVE_DEBUG
198 /* C99 */{
199 bool_t opt_seen = FAL0;
201 for(cadp = cacp->cac_desc, cad_no = 0; cad_no < cadp->cad_no; ++cad_no){
202 assert(cadp->cad_ent_flags[cad_no][0] & n__CMD_ARG_DESC_TYPE_MASK);
203 assert(!opt_seen ||
204 (cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_OPTION));
205 if(cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_OPTION)
206 opt_seen = TRU1;
207 assert(!(cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_GREEDY) ||
208 cad_no + 1 == cadp->cad_no);
211 #endif
213 shin.s = n_UNCONST(cacp->cac_indat); /* "logical" only */
214 shin.l = (cacp->cac_inlen == UIZ_MAX ? strlen(shin.s) : cacp->cac_inlen);
215 shin_orig = shin;
216 cacp->cac_no = 0;
217 cacp->cac_arg = lcap = NULL;
219 parsed_args = 0;
220 for(cadp = cacp->cac_desc, cad_no = 0; shin.l > 0 && cad_no < cadp->cad_no;
221 ++cad_no){
222 jredo:
223 memset(&ncap, 0, sizeof ncap);
224 ncap.ca_indat = shin.s;
225 /* >ca_inline once we know */
226 memcpy(&ncap.ca_ent_flags[0], &cadp->cad_ent_flags[cad_no][0],
227 sizeof ncap.ca_ent_flags);
228 addca = FAL0;
230 switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){
231 case n_CMD_ARG_DESC_STRING:{
232 char /*const*/ *cp = shin.s;
233 size_t i = shin.l;
235 while(i > 0 && blankspacechar(*cp))
236 ++cp, --i;
238 ncap.ca_arg.ca_str.s = cp;
239 while(i > 0 && !blankspacechar(*cp))
240 ++cp, --i;
241 ncap.ca_arg.ca_str.s = savestrbuf(ncap.ca_arg.ca_str.s,
242 ncap.ca_arg.ca_str.l = PTR2SIZE(cp - ncap.ca_arg.ca_str.s));
244 while(i > 0 && blankspacechar(*cp))
245 ++cp, --i;
246 ncap.ca_inlen = PTR2SIZE(cp - ncap.ca_indat);
247 shin.s = cp;
248 shin.l = i;
249 addca = TRU1;
250 } break;
251 default:
252 case n_CMD_ARG_DESC_WYSH:{
253 struct n_string shou, *shoup;
254 enum n_shexp_state shs;
256 shoup = n_string_creat_auto(&shou);
257 ncap.ca_arg_flags =
258 shs = n_shexp_parse_token(shoup, &shin, ncap.ca_ent_flags[1] |
259 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG);
260 ncap.ca_inlen = PTR2SIZE(shin.s - ncap.ca_indat);
261 if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) ==
262 n_SHEXP_STATE_OUTPUT){
263 ncap.ca_arg.ca_str.s = n_string_cp(shoup);
264 ncap.ca_arg.ca_str.l = shou.s_len;
265 shoup = n_string_drop_ownership(shoup);
267 n_string_gut(shoup);
269 if(shs & n_SHEXP_STATE_ERR_MASK)
270 goto jerr;
271 if((shs & n_SHEXP_STATE_STOP) &&
272 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_HONOUR_STOP)){
273 if(!(shs & n_SHEXP_STATE_OUTPUT))
274 goto jleave;
275 addca = TRUM1;
276 }else
277 addca = TRU1;
278 } break;
280 ++parsed_args;
282 if(addca){
283 struct n_cmd_arg *cap;
285 cap = salloc(sizeof *cap);
286 memcpy(cap, &ncap, sizeof ncap);
287 if(lcap == NULL)
288 cacp->cac_arg = cap;
289 else
290 lcap->ca_next = cap;
291 lcap = cap;
292 ++cacp->cac_no;
294 if(addca == TRUM1)
295 goto jleave;
298 if(shin.l > 0 && (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY))
299 goto jredo;
302 if(cad_no < cadp->cad_no &&
303 !(cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_OPTION))
304 goto jerr;
306 jleave:
307 NYD_LEAVE;
308 return (lcap != NULL);
310 jerr:{
311 size_t i;
313 for(i = 0; (i < cadp->cad_no &&
314 !(cadp->cad_ent_flags[i][0] & n_CMD_ARG_DESC_OPTION)); ++i)
317 n_err(_("`%s': parsing stopped after %" PRIuZ " arguments "
318 "(need %" PRIuZ "%s)\n"
319 " Input: %.*s\n"
320 " Stopped: %.*s\n"),
321 cadp->cad_name, parsed_args, i, (i == cadp->cad_no ? n_empty : "+"),
322 (int)shin_orig.l, shin_orig.s,
323 (int)shin.l, shin.s);
325 lcap = NULL;
326 goto jleave;
329 FL struct n_string *
330 n_cmd_arg_join_greedy(struct n_cmd_arg_ctx const *cacp, struct n_string *store){
331 struct n_cmd_arg *cap;
332 NYD_ENTER;
334 for(cap = cacp->cac_arg;
335 (cap != NULL && !(cap->ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY));
336 cap = cap->ca_next)
338 /* Can only join strings */
339 assert(cap == NULL ||
340 (cap->ca_ent_flags[0] & (n_CMD_ARG_DESC_STRING | n_CMD_ARG_DESC_WYSH)));
342 while(cap != NULL){
343 store = n_string_push_buf(store,
344 cap->ca_arg.ca_str.s, cap->ca_arg.ca_str.l);
345 if((cap = cap->ca_next) != NULL)
346 store = n_string_push_c(store, ' ');
348 NYD_LEAVE;
349 return store;
352 /* s-it-mode */