Merge branch 'io'
[ccan] / ccan / opt / parse.c
1 /* Licensed under GPLv3+ - see LICENSE file for details */
2 /* Actual code to parse commandline. */
3 #include <ccan/opt/opt.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <assert.h>
7 #include "private.h"
8
9 /* glibc does this as:
10 /tmp/opt-example: invalid option -- 'x'
11 /tmp/opt-example: unrecognized option '--long'
12 /tmp/opt-example: option '--someflag' doesn't allow an argument
13 /tmp/opt-example: option '--s' is ambiguous
14 /tmp/opt-example: option requires an argument -- 's'
15 */
16 static int parse_err(void (*errlog)(const char *fmt, ...),
17                      const char *argv0, const char *arg, unsigned len,
18                      const char *problem)
19 {
20         errlog("%s: %.*s: %s", argv0, len, arg, problem);
21         return -1;
22 }
23
24 static void consume_option(int *argc, char *argv[], unsigned optnum)
25 {
26         memmove(&argv[optnum], &argv[optnum+1],
27                 sizeof(argv[optnum]) * (*argc-optnum));
28         (*argc)--;
29 }
30
31 /* Returns 1 if argument consumed, 0 if all done, -1 on error. */
32 int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset,
33               void (*errlog)(const char *fmt, ...))
34 {
35         unsigned i, arg, len;
36         const char *o, *optarg = NULL;
37         char *problem = NULL;
38
39         if (getenv("POSIXLY_CORRECT")) {
40                 /* Don't find options after non-options. */
41                 arg = 1;
42         } else {
43                 for (arg = 1; argv[arg]; arg++) {
44                         if (argv[arg][0] == '-')
45                                 break;
46                 }
47         }
48
49         if (!argv[arg] || argv[arg][0] != '-')
50                 return 0;
51
52         /* Special arg terminator option. */
53         if (strcmp(argv[arg], "--") == 0) {
54                 consume_option(argc, argv, arg);
55                 return 0;
56         }
57
58         /* Long options start with -- */
59         if (argv[arg][1] == '-') {
60                 assert(*offset == 0);
61                 for (o = first_lopt(&i, &len); o; o = next_lopt(o, &i, &len)) {
62                         if (strncmp(argv[arg] + 2, o, len) != 0)
63                                 continue;
64                         if (argv[arg][2 + len] == '=')
65                                 optarg = argv[arg] + 2 + len + 1;
66                         else if (argv[arg][2 + len] != '\0')
67                                 continue;
68                         break;
69                 }
70                 if (!o)
71                         return parse_err(errlog, argv[0],
72                                          argv[arg], strlen(argv[arg]),
73                                          "unrecognized option");
74                 /* For error messages, we include the leading '--' */
75                 o -= 2;
76                 len += 2;
77         } else {
78                 /* offset allows us to handle -abc */
79                 for (o = first_sopt(&i); o; o = next_sopt(o, &i)) {
80                         if (argv[arg][*offset + 1] != *o)
81                                 continue;
82                         (*offset)++;
83                         break;
84                 }
85                 if (!o)
86                         return parse_err(errlog, argv[0],
87                                          argv[arg], strlen(argv[arg]),
88                                          "unrecognized option");
89                 /* For error messages, we include the leading '-' */
90                 o--;
91                 len = 2;
92         }
93
94         if ((opt_table[i].type & ~OPT_EARLY) == OPT_NOARG) {
95                 if (optarg)
96                         return parse_err(errlog, argv[0], o, len,
97                                          "doesn't allow an argument");
98                 if ((opt_table[i].type & OPT_EARLY) == is_early)
99                         problem = opt_table[i].cb(opt_table[i].u.arg);
100         } else {
101                 if (!optarg) {
102                         /* Swallow any short options as optarg, eg -afile */
103                         if (*offset && argv[arg][*offset + 1]) {
104                                 optarg = argv[arg] + *offset + 1;
105                                 *offset = 0;
106                         } else
107                                 optarg = argv[arg+1];
108                 }
109                 if (!optarg)
110                         return parse_err(errlog, argv[0], o, len,
111                                          "requires an argument");
112                 if ((opt_table[i].type & OPT_EARLY) == is_early)
113                         problem = opt_table[i].cb_arg(optarg,
114                                                       opt_table[i].u.arg);
115         }
116
117         if (problem) {
118                 parse_err(errlog, argv[0], o, len, problem);
119                 opt_alloc.free(problem);
120                 return -1;
121         }
122
123         /* If no more letters in that short opt, reset offset. */
124         if (*offset && !argv[arg][*offset + 1])
125                 *offset = 0;
126
127         /* All finished with that option? */
128         if (*offset == 0) {
129                 consume_option(argc, argv, arg);
130                 if (optarg && optarg == argv[arg])
131                         consume_option(argc, argv, arg);
132         }
133         return 1;
134 }