Hawaii Hybrid
Loading...
Searching...
No Matches
flag.h
Go to the documentation of this file.
1// flag.h -- v1.3.0 -- command-line flag parsing
2//
3// Inspired by Go's flag module: https://pkg.go.dev/flag
4//
5// Macros API:
6// - FLAG_LIST_INIT_CAP - initial capacity of the Flag_List Dynamic Array.
7// - FLAGS_CAP - how many flags you can define.
8// - FLAG_PUSH_DASH_DASH_BACK - make flag_parse() retain "--" in the rest args
9// (available via flag_rest_argc() and flag_rest_argv()). Useful when you need
10// to know whether flag_parse() has stopped due to encountering "--" or due to
11// encountering a non-flag. Ideally this should've been a default behavior,
12// but it breaks backward compatibility. Hence it's a feature macro.
13// TODO: make FLAG_PUSH_DASH_DASH_BACK a default behavior on a major version upgrade.
14#ifndef FLAG_H_
15#define FLAG_H_
16
17#include <assert.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <stdbool.h>
21#include <stdint.h>
22#include <stddef.h>
23#include <inttypes.h>
24#include <limits.h>
25#include <string.h>
26#include <errno.h>
27
28// TODO: *_var function variants
29// void flag_bool_var(bool *var, const char *name, bool def, const char *desc);
30// void flag_bool_uint64(uint64_t *var, const char *name, bool def, const char *desc);
31// etc.
32// WARNING! *_var functions may break the flag_name() functionality
33
34#ifndef FLAG_LIST_INIT_CAP
35#define FLAG_LIST_INIT_CAP 8
36#endif // FLAG_LIST_INIT_CAP
37
38typedef struct {
39 const char **items;
40 size_t count;
41 size_t capacity;
42} Flag_List;
43
44char *flag_name(void *val);
45bool *flag_bool(const char *name, bool def, const char *desc);
46uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc);
47size_t *flag_size(const char *name, uint64_t def, const char *desc);
48char **flag_str(const char *name, const char *def, const char *desc);
49Flag_List *flag_list(const char *name, const char *desc);
50bool flag_parse(int argc, char **argv);
52char **flag_rest_argv(void);
53void flag_print_error(FILE *stream);
54void flag_print_options(FILE *stream);
55
56#endif // FLAG_H_
57
59
60#ifdef FLAG_IMPLEMENTATION
61
62typedef enum {
63 FLAG_BOOL = 0,
64 FLAG_UINT64,
65 FLAG_SIZE,
66 FLAG_STR,
67 FLAG_LIST,
68 COUNT_FLAG_TYPES,
69} Flag_Type;
70
71static_assert(COUNT_FLAG_TYPES == 5, "Exhaustive Flag_Value definition");
72typedef union {
73 char *as_str;
74 uint64_t as_uint64;
75 bool as_bool;
76 size_t as_size;
77 Flag_List as_list;
78} Flag_Value;
79
80typedef enum {
81 FLAG_NO_ERROR = 0,
82 FLAG_ERROR_UNKNOWN,
83 FLAG_ERROR_NO_VALUE,
84 FLAG_ERROR_INVALID_NUMBER,
85 FLAG_ERROR_INTEGER_OVERFLOW,
86 FLAG_ERROR_INVALID_SIZE_SUFFIX,
87 COUNT_FLAG_ERRORS,
88} Flag_Error;
89
90typedef struct {
91 Flag_Type type;
92 char *name;
93 char *desc;
94 Flag_Value val;
95 Flag_Value def;
96} Flag;
97
98#ifndef FLAGS_CAP
99#define FLAGS_CAP 256
100#endif
101
102typedef struct {
103 Flag flags[FLAGS_CAP];
104 size_t flags_count;
105
106 Flag_Error flag_error;
107 char *flag_error_name;
108
109 const char *program_name;
110
111 int rest_argc;
112 char **rest_argv;
113} Flag_Context;
114
115static Flag_Context flag_global_context;
116
117Flag *flag_new(Flag_Type type, const char *name, const char *desc)
118{
119 Flag_Context *c = &flag_global_context;
120
121 assert(c->flags_count < FLAGS_CAP);
122 Flag *flag = &c->flags[c->flags_count++];
123 memset(flag, 0, sizeof(*flag));
124 flag->type = type;
125 // NOTE: I won't touch them I promise Kappa
126 flag->name = (char*) name;
127 flag->desc = (char*) desc;
128 return flag;
129}
130
131char *flag_name(void *val)
132{
133 Flag *flag = (Flag*) ((char*) val - offsetof(Flag, val));
134 return flag->name;
135}
136
137bool *flag_bool(const char *name, bool def, const char *desc)
138{
139 Flag *flag = flag_new(FLAG_BOOL, name, desc);
140 flag->def.as_bool = def;
141 flag->val.as_bool = def;
142 return &flag->val.as_bool;
143}
144
145uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc)
146{
147 Flag *flag = flag_new(FLAG_UINT64, name, desc);
148 flag->val.as_uint64 = def;
149 flag->def.as_uint64 = def;
150 return &flag->val.as_uint64;
151}
152
153size_t *flag_size(const char *name, uint64_t def, const char *desc)
154{
155 Flag *flag = flag_new(FLAG_SIZE, name, desc);
156 flag->val.as_size = def;
157 flag->def.as_size = def;
158 return &flag->val.as_size;
159}
160
161char **flag_str(const char *name, const char *def, const char *desc)
162{
163 Flag *flag = flag_new(FLAG_STR, name, desc);
164 flag->val.as_str = (char*) def;
165 flag->def.as_str = (char*) def;
166 return &flag->val.as_str;
167}
168
169Flag_List *flag_list(const char *name, const char *desc)
170{
171 Flag *flag = flag_new(FLAG_LIST, name, desc);
172 return &flag->val.as_list;
173}
174
175static void flag_list_append(Flag_List *list, char *item)
176{
177 if (list->count >= list->capacity) {
178 size_t new_capacity = list->capacity == 0 ? FLAG_LIST_INIT_CAP : list->capacity*2;
179 list->items = (const char**)realloc(list->items, new_capacity*sizeof(*list->items));
180 list->capacity = new_capacity;
181 }
182
183 list->items[list->count++] = item;
184}
185
186static char *flag_shift_args(int *argc, char ***argv)
187{
188 assert(*argc > 0);
189 char *result = **argv;
190 *argv += 1;
191 *argc -= 1;
192 return result;
193}
194
195int flag_rest_argc(void)
196{
197 return flag_global_context.rest_argc;
198}
199
200char **flag_rest_argv(void)
201{
202 return flag_global_context.rest_argv;
203}
204
205const char *flag_program_name(void)
206{
207 return flag_global_context.program_name;
208}
209
210bool flag_parse(int argc, char **argv)
211{
212 Flag_Context *c = &flag_global_context;
213
214 if (c->program_name == NULL) {
215 c->program_name = flag_shift_args(&argc, &argv);
216 }
217
218 while (argc > 0) {
219 char *flag = flag_shift_args(&argc, &argv);
220
221 if (*flag != '-') {
222 // NOTE: pushing flag back into args
223 c->rest_argc = argc + 1;
224 c->rest_argv = argv - 1;
225 return true;
226 }
227
228 if (strcmp(flag, "--") == 0) {
229#ifdef FLAG_PUSH_DASH_DASH_BACK
230 // NOTE: pushing dash dash back into args as requested by the user
231 c->rest_argc = argc + 1;
232 c->rest_argv = argv - 1;
233#else
234 // NOTE: not pushing dash dash back into args for backward compatibility
235 c->rest_argc = argc;
236 c->rest_argv = argv;
237#endif // FLAG_PUSH_DASH_DASH_BACK
238 return true;
239 }
240
241 // NOTE: remove the dash
242 flag += 1;
243
244 char *equals = strchr(flag, '=');
245 if (equals != NULL) {
246 // trim off the '=' and the value from `flag`,
247 *equals = '\0';
248 // and make `equals` be a pointer to just the value
249 equals += 1;
250 }
251
252 bool found = false;
253 for (size_t i = 0; i < c->flags_count; ++i) {
254 if (strcmp(c->flags[i].name, flag) == 0) {
255 static_assert(COUNT_FLAG_TYPES == 5, "Exhaustive flag type parsing");
256 switch (c->flags[i].type) {
257 case FLAG_LIST: {
258 if (argc == 0) {
259 c->flag_error = FLAG_ERROR_NO_VALUE;
260 c->flag_error_name = flag;
261 return false;
262 }
263
264 char *arg = flag_shift_args(&argc, &argv);
265 flag_list_append(&c->flags[i].val.as_list, arg);
266 }
267 break;
268
269 case FLAG_BOOL: {
270 c->flags[i].val.as_bool = true;
271 }
272 break;
273
274 case FLAG_STR: {
275 char *arg;
276 if (equals == NULL) {
277 if (argc == 0) {
278 c->flag_error = FLAG_ERROR_NO_VALUE;
279 c->flag_error_name = flag;
280 return false;
281 }
282 arg = flag_shift_args(&argc, &argv);
283 } else {
284 arg = equals;
285 }
286
287 c->flags[i].val.as_str = arg;
288 }
289 break;
290
291 case FLAG_UINT64: {
292 char *arg;
293 if (equals == NULL) {
294 if (argc == 0) {
295 c->flag_error = FLAG_ERROR_NO_VALUE;
296 c->flag_error_name = flag;
297 return false;
298 }
299 arg = flag_shift_args(&argc, &argv);
300 } else {
301 arg = equals;
302 }
303
304 static_assert(sizeof(unsigned long long int) == sizeof(uint64_t), "The original author designed this for x86_64 machine with the compiler that expects unsigned long long int and uint64_t to be the same thing, so they could use strtoull() function to parse it. Please adjust this code for your case and maybe even send the patch to upstream to make it work on a wider range of environments.");
305 char *endptr;
306 // TODO: replace strtoull with a custom solution
307 // That way we can get rid of the dependency on errno and static_assert
308 unsigned long long int result = strtoull(arg, &endptr, 10);
309
310 if (*endptr != '\0') {
311 c->flag_error = FLAG_ERROR_INVALID_NUMBER;
312 c->flag_error_name = flag;
313 return false;
314 }
315
316 if (result == ULLONG_MAX && errno == ERANGE) {
317 c->flag_error = FLAG_ERROR_INTEGER_OVERFLOW;
318 c->flag_error_name = flag;
319 return false;
320 }
321
322 c->flags[i].val.as_uint64 = result;
323 }
324 break;
325
326 case FLAG_SIZE: {
327 char *arg;
328 if (equals == NULL) {
329 if (argc == 0) {
330 c->flag_error = FLAG_ERROR_NO_VALUE;
331 c->flag_error_name = flag;
332 return false;
333 }
334 arg = flag_shift_args(&argc, &argv);
335 } else {
336 arg = equals;
337 }
338
339 static_assert(sizeof(unsigned long long int) == sizeof(size_t), "The original author designed this for x86_64 machine with the compiler that expects unsigned long long int and size_t to be the same thing, so they could use strtoull() function to parse it. Please adjust this code for your case and maybe even send the patch to upstream to make it work on a wider range of environments.");
340 char *endptr;
341 // TODO: replace strtoull with a custom solution
342 // That way we can get rid of the dependency on errno and static_assert
343 unsigned long long int result = strtoull(arg, &endptr, 10);
344
345 // TODO: handle more multiplicative suffixes like in dd(1). From the dd(1) man page:
346 // > N and BYTES may be followed by the following
347 // > multiplicative suffixes: c =1, w =2, b =512, kB =1000, K
348 // > =1024, MB =1000*1000, M =1024*1024, xM =M, GB
349 // > =1000*1000*1000, G =1024*1024*1024, and so on for T, P,
350 // > E, Z, Y.
351 if (strcmp(endptr, "K") == 0) {
352 result *= 1024;
353 } else if (strcmp(endptr, "M") == 0) {
354 result *= 1024*1024;
355 } else if (strcmp(endptr, "G") == 0) {
356 result *= 1024*1024*1024;
357 } else if (strcmp(endptr, "") != 0) {
358 c->flag_error = FLAG_ERROR_INVALID_SIZE_SUFFIX;
359 c->flag_error_name = flag;
360 // TODO: capability to report what exactly is the wrong suffix
361 return false;
362 }
363
364 if (result == ULLONG_MAX && errno == ERANGE) {
365 c->flag_error = FLAG_ERROR_INTEGER_OVERFLOW;
366 c->flag_error_name = flag;
367 return false;
368 }
369
370 c->flags[i].val.as_size = result;
371 }
372 break;
373
374 case COUNT_FLAG_TYPES:
375 default: {
376 assert(0 && "unreachable");
377 exit(69);
378 }
379 }
380
381 found = true;
382 }
383 }
384
385 if (!found) {
386 c->flag_error = FLAG_ERROR_UNKNOWN;
387 c->flag_error_name = flag;
388 return false;
389 }
390 }
391
392 c->rest_argc = argc;
393 c->rest_argv = argv;
394 return true;
395}
396
397void flag_print_options(FILE *stream)
398{
399 Flag_Context *c = &flag_global_context;
400 for (size_t i = 0; i < c->flags_count; ++i) {
401 Flag *flag = &c->flags[i];
402
403 static_assert(COUNT_FLAG_TYPES == 5, "Exhaustive flag type defaults printing");
404 switch (c->flags[i].type) {
405 case FLAG_LIST:
406 fprintf(stream, " -%s <str> ... -%s <str> ...\n", flag->name, flag->name);
407 fprintf(stream, " %s\n", flag->desc);
408 break;
409 case FLAG_BOOL:
410 fprintf(stream, " -%s\n", flag->name);
411 fprintf(stream, " %s\n", flag->desc);
412 if (flag->def.as_bool) {
413 fprintf(stream, " Default: %s\n", flag->def.as_bool ? "true" : "false");
414 }
415 break;
416 case FLAG_UINT64:
417 fprintf(stream, " -%s <int>\n", flag->name);
418 fprintf(stream, " %s\n", flag->desc);
419 fprintf(stream, " Default: %" PRIu64 "\n", flag->def.as_uint64);
420 break;
421 case FLAG_SIZE:
422 fprintf(stream, " -%s <int>\n", flag->name);
423 fprintf(stream, " %s\n", flag->desc);
424 fprintf(stream, " Default: %zu\n", flag->def.as_size);
425 break;
426 case FLAG_STR:
427 fprintf(stream, " -%s <str>\n", flag->name);
428 fprintf(stream, " %s\n", flag->desc);
429 if (flag->def.as_str) {
430 fprintf(stream, " Default: %s\n", flag->def.as_str);
431 }
432 break;
433 default:
434 assert(0 && "unreachable");
435 exit(69);
436 }
437 }
438}
439
440void flag_print_error(FILE *stream)
441{
442 Flag_Context *c = &flag_global_context;
443 static_assert(COUNT_FLAG_ERRORS == 6, "Exhaustive flag error printing");
444 switch (c->flag_error) {
445 case FLAG_NO_ERROR:
446 // NOTE: don't call flag_print_error() if flag_parse() didn't return false, okay? ._.
447 fprintf(stream, "Operation Failed Successfully! Please tell the developer of this software that they don't know what they are doing! :)");
448 break;
449 case FLAG_ERROR_UNKNOWN:
450 fprintf(stream, "ERROR: -%s: unknown flag\n", c->flag_error_name);
451 break;
452 case FLAG_ERROR_NO_VALUE:
453 fprintf(stream, "ERROR: -%s: no value provided\n", c->flag_error_name);
454 break;
455 case FLAG_ERROR_INVALID_NUMBER:
456 fprintf(stream, "ERROR: -%s: invalid number\n", c->flag_error_name);
457 break;
458 case FLAG_ERROR_INTEGER_OVERFLOW:
459 fprintf(stream, "ERROR: -%s: integer overflow\n", c->flag_error_name);
460 break;
461 case FLAG_ERROR_INVALID_SIZE_SUFFIX:
462 fprintf(stream, "ERROR: -%s: invalid size suffix\n", c->flag_error_name);
463 break;
464 case COUNT_FLAG_ERRORS:
465 default:
466 assert(0 && "unreachable");
467 exit(69);
468 }
469}
470
471#endif // FLAG_IMPLEMENTATION
472
473/*
474 Revision history:
475
476 1.3.0 (2025-07-21) Add support for `-key=value` syntax (by @ej-shafran)
477 1.2.1 (2025-07-04) flag_print_options: denote expected argument types
478 flag_print_options: indicate flag list usage more clearly
479 1.2.0 (2025-05-31) Introduce FLAG_PUSH_DASH_DASH_BACK (by @nullnominal)
480 1.1.0 (2025-05-09) Introduce flag list
481 1.0.0 (2025-03-03) Initial release
482 Save program_name in the context
483
484*/
485
486// Copyright 2021 Alexey Kutepov <reximkut@gmail.com>
487//
488// Permission is hereby granted, free of charge, to any person obtaining a copy
489// of this software and associated documentation files (the "Software"), to
490// deal in the Software without restriction, including without limitation the
491// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
492// sell copies of the Software, and to permit persons to whom the Software is
493// furnished to do so, subject to the following conditions:
494//
495// The above copyright notice and this permission notice shall be included in
496// all copies or substantial portions of the Software.
497//
498// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
499// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
500// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
501// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
502// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
503// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
504// IN THE SOFTWARE.
void flag_print_error(FILE *stream)
uint64_t * flag_uint64(const char *name, uint64_t def, const char *desc)
int flag_rest_argc(void)
char * flag_name(void *val)
char ** flag_str(const char *name, const char *def, const char *desc)
#define FLAG_LIST_INIT_CAP
Definition flag.h:35
char ** flag_rest_argv(void)
bool flag_parse(int argc, char **argv)
size_t * flag_size(const char *name, uint64_t def, const char *desc)
void flag_print_options(FILE *stream)
Flag_List * flag_list(const char *name, const char *desc)
bool * flag_bool(const char *name, bool def, const char *desc)
Definition flag.h:38
size_t capacity
Definition flag.h:41
const char ** items
Definition flag.h:39
size_t count
Definition flag.h:40