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