blob: 87f60bca2ee03b0e129453551594ae872c9edbc8 [file] [log] [blame]
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -08001/*
2 *
Craig Tiller06059952015-02-18 08:34:56 -08003 * Copyright 2015, Google Inc.
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -08004 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
nnoble9f312b42015-01-07 01:19:35 -080034#include <grpc/support/cmdline.h>
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080035
36#include <limits.h>
37#include <stdio.h>
38#include <string.h>
39
Craig Tiller485d7762015-01-23 12:54:05 -080040#include "src/core/support/string.h"
nnoble9f312b42015-01-07 01:19:35 -080041#include <grpc/support/alloc.h>
42#include <grpc/support/log.h>
Masood Malekghassemi701af602015-06-03 15:01:17 -070043#include <grpc/support/string_util.h>
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -080044
45typedef enum { ARGTYPE_INT, ARGTYPE_BOOL, ARGTYPE_STRING } argtype;
46
47typedef struct arg {
48 const char *name;
49 const char *help;
50 argtype type;
51 void *value;
52 struct arg *next;
53} arg;
54
55struct gpr_cmdline {
56 const char *description;
57 arg *args;
58 const char *argv0;
59
60 const char *extra_arg_name;
61 const char *extra_arg_help;
62 void (*extra_arg)(void *user_data, const char *arg);
63 void *extra_arg_user_data;
64
65 void (*state)(gpr_cmdline *cl, char *arg);
66 arg *cur_arg;
67};
68
69static void normal_state(gpr_cmdline *cl, char *arg);
70
71gpr_cmdline *gpr_cmdline_create(const char *description) {
72 gpr_cmdline *cl = gpr_malloc(sizeof(gpr_cmdline));
73 memset(cl, 0, sizeof(gpr_cmdline));
74
75 cl->description = description;
76 cl->state = normal_state;
77
78 return cl;
79}
80
81void gpr_cmdline_destroy(gpr_cmdline *cl) {
82 while (cl->args) {
83 arg *a = cl->args;
84 cl->args = a->next;
85 gpr_free(a);
86 }
87 gpr_free(cl);
88}
89
90static void add_arg(gpr_cmdline *cl, const char *name, const char *help,
91 argtype type, void *value) {
92 arg *a;
93
94 for (a = cl->args; a; a = a->next) {
95 GPR_ASSERT(0 != strcmp(a->name, name));
96 }
97
98 a = gpr_malloc(sizeof(arg));
99 memset(a, 0, sizeof(arg));
100 a->name = name;
101 a->help = help;
102 a->type = type;
103 a->value = value;
104 a->next = cl->args;
105 cl->args = a;
106}
107
108void gpr_cmdline_add_int(gpr_cmdline *cl, const char *name, const char *help,
109 int *value) {
110 add_arg(cl, name, help, ARGTYPE_INT, value);
111}
112
113void gpr_cmdline_add_flag(gpr_cmdline *cl, const char *name, const char *help,
114 int *value) {
115 add_arg(cl, name, help, ARGTYPE_BOOL, value);
116}
117
118void gpr_cmdline_add_string(gpr_cmdline *cl, const char *name, const char *help,
119 char **value) {
120 add_arg(cl, name, help, ARGTYPE_STRING, value);
121}
122
123void gpr_cmdline_on_extra_arg(
124 gpr_cmdline *cl, const char *name, const char *help,
125 void (*on_extra_arg)(void *user_data, const char *arg), void *user_data) {
126 GPR_ASSERT(!cl->extra_arg);
127 GPR_ASSERT(on_extra_arg);
128
129 cl->extra_arg = on_extra_arg;
130 cl->extra_arg_user_data = user_data;
131 cl->extra_arg_name = name;
132 cl->extra_arg_help = help;
133}
134
Craig Tillere7023612015-05-28 08:00:14 -0700135/* recursively descend argument list, adding the last element
136 to s first - so that arguments are added in the order they were
137 added to the list by api calls */
138static void add_args_to_usage(gpr_strvec *s, arg *a) {
139 char *tmp;
140
141 if (!a) return;
142 add_args_to_usage(s, a->next);
143
144 switch (a->type) {
145 case ARGTYPE_BOOL:
146 gpr_asprintf(&tmp, " [--%s|--no-%s]", a->name, a->name);
147 gpr_strvec_add(s, tmp);
148 break;
149 case ARGTYPE_STRING:
150 gpr_asprintf(&tmp, " [--%s=string]", a->name);
151 gpr_strvec_add(s, tmp);
152 break;
153 case ARGTYPE_INT:
154 gpr_asprintf(&tmp, " [--%s=int]", a->name);
155 gpr_strvec_add(s, tmp);
156 break;
157 }
158}
159
160char *gpr_cmdline_usage_string(gpr_cmdline *cl, const char *argv0) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800161 /* TODO(ctiller): make this prettier */
Craig Tillere7023612015-05-28 08:00:14 -0700162 gpr_strvec s;
163 char *tmp;
164 const char *name = strrchr(argv0, '/');
165
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800166 if (name) {
167 name++;
168 } else {
Craig Tillere7023612015-05-28 08:00:14 -0700169 name = argv0;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800170 }
Craig Tillere7023612015-05-28 08:00:14 -0700171
172 gpr_strvec_init(&s);
173
174 gpr_asprintf(&tmp, "Usage: %s", name);
175 gpr_strvec_add(&s, tmp);
176 add_args_to_usage(&s, cl->args);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800177 if (cl->extra_arg) {
Craig Tillere7023612015-05-28 08:00:14 -0700178 gpr_asprintf(&tmp, " [%s...]", cl->extra_arg_name);
179 gpr_strvec_add(&s, tmp);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800180 }
Craig Tillere7023612015-05-28 08:00:14 -0700181 gpr_strvec_add(&s, gpr_strdup("\n"));
182
183 tmp = gpr_strvec_flatten(&s, NULL);
184 gpr_strvec_destroy(&s);
185 return tmp;
186}
187
188static void print_usage_and_die(gpr_cmdline *cl) {
189 char *usage = gpr_cmdline_usage_string(cl, cl->argv0);
190 fprintf(stderr, "%s", usage);
191 gpr_free(usage);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800192 exit(1);
193}
194
Craig Tillerb9d35962015-09-11 13:31:16 -0700195static void extra_state(gpr_cmdline *cl, char *str) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800196 if (!cl->extra_arg) print_usage_and_die(cl);
Craig Tillerb9d35962015-09-11 13:31:16 -0700197 cl->extra_arg(cl->extra_arg_user_data, str);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800198}
199
200static arg *find_arg(gpr_cmdline *cl, char *name) {
201 arg *a;
202
203 for (a = cl->args; a; a = a->next) {
204 if (0 == strcmp(a->name, name)) {
205 break;
206 }
207 }
208
209 if (!a) {
210 fprintf(stderr, "Unknown argument: %s\n", name);
211 print_usage_and_die(cl);
212 }
213
214 return a;
215}
216
Craig Tillerb9d35962015-09-11 13:31:16 -0700217static void value_state(gpr_cmdline *cl, char *str) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800218 long intval;
219 char *end;
220
221 GPR_ASSERT(cl->cur_arg);
222
223 switch (cl->cur_arg->type) {
224 case ARGTYPE_INT:
Craig Tillerb9d35962015-09-11 13:31:16 -0700225 intval = strtol(str, &end, 0);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800226 if (*end || intval < INT_MIN || intval > INT_MAX) {
Craig Tillerb9d35962015-09-11 13:31:16 -0700227 fprintf(stderr, "expected integer, got '%s' for %s\n", str,
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800228 cl->cur_arg->name);
229 print_usage_and_die(cl);
230 }
murgatroid995e71d7a2015-06-19 12:24:44 -0700231 *(int *)cl->cur_arg->value = (int)intval;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800232 break;
233 case ARGTYPE_BOOL:
Craig Tillerb9d35962015-09-11 13:31:16 -0700234 if (0 == strcmp(str, "1") || 0 == strcmp(str, "true")) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800235 *(int *)cl->cur_arg->value = 1;
Craig Tillerb9d35962015-09-11 13:31:16 -0700236 } else if (0 == strcmp(str, "0") || 0 == strcmp(str, "false")) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800237 *(int *)cl->cur_arg->value = 0;
238 } else {
Craig Tillerb9d35962015-09-11 13:31:16 -0700239 fprintf(stderr, "expected boolean, got '%s' for %s\n", str,
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800240 cl->cur_arg->name);
241 print_usage_and_die(cl);
242 }
243 break;
244 case ARGTYPE_STRING:
Craig Tillerb9d35962015-09-11 13:31:16 -0700245 *(char **)cl->cur_arg->value = str;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800246 break;
247 }
248
249 cl->state = normal_state;
250}
251
Craig Tillerb9d35962015-09-11 13:31:16 -0700252static void normal_state(gpr_cmdline *cl, char *str) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800253 char *eq = NULL;
254 char *tmp = NULL;
255 char *arg_name = NULL;
256
Craig Tillerb9d35962015-09-11 13:31:16 -0700257 if (0 == strcmp(str, "-help") || 0 == strcmp(str, "--help") ||
258 0 == strcmp(str, "-h")) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800259 print_usage_and_die(cl);
260 }
261
262 cl->cur_arg = NULL;
263
Craig Tillerb9d35962015-09-11 13:31:16 -0700264 if (str[0] == '-') {
265 if (str[1] == '-') {
266 if (str[2] == 0) {
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800267 /* handle '--' to move to just extra args */
268 cl->state = extra_state;
269 return;
270 }
Craig Tillerb9d35962015-09-11 13:31:16 -0700271 str += 2;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800272 } else {
Craig Tillerb9d35962015-09-11 13:31:16 -0700273 str += 1;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800274 }
Craig Tillerb9d35962015-09-11 13:31:16 -0700275 /* first byte of str is now past the leading '-' or '--' */
276 if (str[0] == 'n' && str[1] == 'o' && str[2] == '-') {
277 /* str is of the form '--no-foo' - it's a flag disable */
278 str += 3;
279 cl->cur_arg = find_arg(cl, str);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800280 if (cl->cur_arg->type != ARGTYPE_BOOL) {
Craig Tillerb9d35962015-09-11 13:31:16 -0700281 fprintf(stderr, "%s is not a flag argument\n", str);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800282 print_usage_and_die(cl);
283 }
284 *(int *)cl->cur_arg->value = 0;
285 return; /* early out */
286 }
Craig Tillerb9d35962015-09-11 13:31:16 -0700287 eq = strchr(str, '=');
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800288 if (eq != NULL) {
289 /* copy the string into a temp buffer and extract the name */
Craig Tillerb9d35962015-09-11 13:31:16 -0700290 tmp = arg_name = gpr_malloc((size_t)(eq - str + 1));
291 memcpy(arg_name, str, (size_t)(eq - str));
292 arg_name[eq - str] = 0;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800293 } else {
Craig Tillerb9d35962015-09-11 13:31:16 -0700294 arg_name = str;
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800295 }
296 cl->cur_arg = find_arg(cl, arg_name);
297 if (eq != NULL) {
Craig Tillerb9d35962015-09-11 13:31:16 -0700298 /* str was of the type --foo=value, parse the value */
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800299 value_state(cl, eq + 1);
300 } else if (cl->cur_arg->type != ARGTYPE_BOOL) {
301 /* flag types don't have a '--foo value' variant, other types do */
302 cl->state = value_state;
303 } else {
304 /* flag parameter: just set the value */
305 *(int *)cl->cur_arg->value = 1;
306 }
307 } else {
Craig Tillerb9d35962015-09-11 13:31:16 -0700308 extra_state(cl, str);
Nicolas Nobleb7ebd3b2014-11-26 16:33:03 -0800309 }
310
311 gpr_free(tmp);
312}
313
314void gpr_cmdline_parse(gpr_cmdline *cl, int argc, char **argv) {
315 int i;
316
317 GPR_ASSERT(argc >= 1);
318 cl->argv0 = argv[0];
319
320 for (i = 1; i < argc; i++) {
321 cl->state(cl, argv[i]);
322 }
Craig Tiller190d3602015-02-18 09:23:38 -0800323}