💾 Archived View for gmi.noulin.net › gitRepositories › md2html › file › cmdline.c.gmi captured on 2023-01-29 at 13:24:34. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
cmdline.c (6934B)
1 /* cmdline.c: a reentrant version of getopt(). Written 2006 by Brian 2 * Raiter. This code is in the public domain. 3 */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <ctype.h> 9 #include "cmdline.h" 10 11 #define docallback(opt, val) \ 12 do { if ((r = callback(opt, val, data)) != 0) return r; } while (0) 13 14 /* Parse the given cmdline arguments. 15 */ 16 int readoptions(option const* list, int argc, char **argv, 17 int (*callback)(int, char const*, void*), void *data) 18 { 19 char argstring[] = "--"; 20 option const *opt; 21 char const *val; 22 char const *p; 23 int stop = 0; 24 int argi, len, r; 25 26 if (!list || !callback) 27 return -1; 28 29 for (argi = 1 ; argi < argc ; ++argi) 30 { 31 /* First, check for "--", which forces all remaining arguments 32 * to be treated as non-options. 33 */ 34 if (!stop && argv[argi][0] == '-' && argv[argi][1] == '-' 35 && argv[argi][2] == '\0') { 36 stop = 1; 37 continue; 38 } 39 40 /* Arguments that do not begin with '-' (or are only "-") are 41 * not options. 42 */ 43 if (stop || argv[argi][0] != '-' || argv[argi][1] == '\0') { 44 docallback(0, argv[argi]); 45 continue; 46 } 47 48 if (argv[argi][1] == '-') 49 { 50 /* Arguments that begin with a double-dash are long 51 * options. 52 */ 53 p = argv[argi] + 2; 54 val = strchr(p, '='); 55 if (val) 56 len = val++ - p; 57 else 58 len = strlen(p); 59 60 /* Is it on the list of valid options? If so, does it 61 * expect a parameter? 62 */ 63 for (opt = list ; opt->optval ; ++opt) 64 if (opt->name && !strncmp(p, opt->name, len) 65 && !opt->name[len]) 66 break; 67 if (!opt->optval) { 68 docallback('?', argv[argi]); 69 } else if (!val && opt->arg == 1) { 70 docallback(':', argv[argi]); 71 } else if (val && opt->arg == 0) { 72 docallback('=', argv[argi]); 73 } else { 74 docallback(opt->optval, val); 75 } 76 } 77 else 78 { 79 /* Arguments that begin with a single dash contain one or 80 * more short options. Each character in the argument is 81 * examined in turn, unless a parameter consumes the rest 82 * of the argument (or possibly even the following 83 * argument). 84 */ 85 for (p = argv[argi] + 1 ; *p ; ++p) { 86 for (opt = list ; opt->optval ; ++opt) 87 if (opt->chname == *p) 88 break; 89 if (!opt->optval) { 90 argstring[1] = *p; 91 docallback('?', argstring); 92 continue; 93 } else if (opt->arg == 0) { 94 docallback(opt->optval, NULL); 95 continue; 96 } else if (p[1]) { 97 docallback(opt->optval, p + 1); 98 break; 99 } else if (argi + 1 < argc && strcmp(argv[argi + 1], "--")) { 100 ++argi; 101 docallback(opt->optval, argv[argi]); 102 break; 103 } else if (opt->arg == 2) { 104 docallback(opt->optval, NULL); 105 continue; 106 } else { 107 argstring[1] = *p; 108 docallback(':', argstring); 109 break; 110 } 111 } 112 } 113 } 114 return 0; 115 } 116 117 /* Verify that str points to an ASCII zero or one (optionally with 118 * whitespace) and return the value present, or -1 if str's contents 119 * are anything else. 120 */ 121 static int readboolvalue(char const *str) 122 { 123 char d; 124 125 while (isspace(*str)) 126 ++str; 127 if (!*str) 128 return -1; 129 d = *str++; 130 while (isspace(*str)) 131 ++str; 132 if (*str) 133 return -1; 134 if (d == '0') 135 return 0; 136 else if (d == '1') 137 return 1; 138 else 139 return -1; 140 } 141 142 /* Parse a configuration file. 143 */ 144 int readcfgfile(option const* list, FILE *fp, 145 int (*callback)(int, char const*, void*), void *data) 146 { 147 char buf[1024]; 148 option const *opt; 149 char *name, *val, *p; 150 int len, f, r; 151 152 while (fgets(buf, sizeof buf, fp) != NULL) 153 { 154 /* Strip off the trailing newline and any leading whitespace. 155 * If the line begins with a hash sign, skip it entirely. 156 */ 157 len = strlen(buf); 158 if (len && buf[len - 1] == '\n') 159 buf[--len] = '\0'; 160 for (p = buf ; isspace(*p) ; ++p) ; 161 if (!*p || *p == '#') 162 continue; 163 164 /* Find the end of the option's name and the beginning of the 165 * parameter, if any. 166 */ 167 for (name = p ; *p && *p != '=' && !isspace(*p) ; ++p) ; 168 len = p - name; 169 for ( ; *p == '=' || isspace(*p) ; ++p) ; 170 val = p; 171 172 /* Is it on the list of valid options? Does it take a 173 * full parameter, or just an optional boolean? 174 */ 175 for (opt = list ; opt->optval ; ++opt) 176 if (opt->name && !strncmp(name, opt->name, len) 177 && !opt->name[len]) 178 break; 179 if (!opt->optval) { 180 docallback('?', name); 181 } else if (!*val && opt->arg == 1) { 182 docallback(':', name); 183 } else if (*val && opt->arg == 0) { 184 f = readboolvalue(val); 185 if (f < 0) 186 docallback('=', name); 187 else if (f == 1) 188 docallback(opt->optval, NULL); 189 } else { 190 docallback(opt->optval, val); 191 } 192 } 193 return ferror(fp) ? -1 : 0; 194 } 195 196 /* Turn a string containing a cmdline into an argc-argv pair. 197 */ 198 int makecmdline(char const *cmdline, int *argcp, char ***argvp) 199 { 200 char **argv; 201 int argc; 202 char const *s; 203 int n, quoted; 204 205 if (!cmdline) 206 return 0; 207 208 /* Calcuate argc by counting the number of "clumps" of non-spaces. 209 */ 210 for (s = cmdline ; isspace(*s) ; ++s) ; 211 if (!*s) { 212 *argcp = 1; 213 if (argvp) { 214 *argvp = malloc(2 * sizeof(char*)); 215 if (!*argvp) 216 return 0; 217 (*argvp)[0] = NULL; 218 (*argvp)[1] = NULL; 219 } 220 return 1; 221 } 222 for (argc = 2, quoted = 0 ; *s ; ++s) { 223 if (quoted == '"') { 224 if (*s == '"') 225 quoted = 0; 226 else if (*s == '\\' && s[1]) 227 ++s; 228 } else if (quoted == '\'') { 229 if (*s == '\'') 230 quoted = 0; 231 } else { 232 if (isspace(*s)) { 233 for ( ; isspace(s[1]) ; ++s) ; 234 if (!s[1]) 235 break; 236 ++argc; 237 } else if (*s == '"' || *s == '\'') { 238 quoted = *s; 239 } 240 } 241 } 242 243 *argcp = argc; 244 if (!argvp) 245 return 1; 246 247 /* Allocate space for all the arguments and their pointers. 248 */ 249 argv = malloc((argc + 1) * sizeof(char*) + strlen(cmdline) + 1); 250 *argvp = argv; 251 if (!argv) 252 return 0; 253 argv[0] = NULL; 254 argv[1] = (char*)(argv + argc + 1); 255 256 /* Copy the string into the allocated memory immediately after the 257 * argv array. Where spaces immediately follows a nonspace, 258 * replace it with a \0. Where a nonspace immediately follows 259 * spaces, store a pointer to it. (Except, of course, when the 260 * space-nonspace transitions occur within quotes.) 261 */ 262 for (s = cmdline ; isspace(*s) ; ++s) ; 263 for (argc = 1, n = 0, quoted = 0 ; *s ; ++s) { 264 if (quoted == '"') { 265 if (*s == '"') { 266 quoted = 0; 267 } else { 268 if (*s == '\\' && s[1]) 269 ++s; 270 argv[argc][n++] = *s; 271 } 272 } else if (quoted == '\'') { 273 if (*s == '\'') 274 quoted = 0; 275 else 276 argv[argc][n++] = *s; 277 } else { 278 if (isspace(*s)) { 279 argv[argc][n] = '\0'; 280 for ( ; isspace(s[1]) ; ++s) ; 281 if (!s[1]) 282 break; 283 argv[argc + 1] = argv[argc] + n + 1; 284 ++argc; 285 n = 0; 286 } else { 287 if (*s == '"' || *s == '\'') 288 quoted = *s; 289 else 290 argv[argc][n++] = *s; 291 } 292 } 293 } 294 argv[argc + 1] = NULL; 295 return 1; 296 }