1
// flag.h -- command-line flag parsing
3
// Inspired by Go's flag module: https://pkg.go.dev/flag
19
// TODO: add support for -flag=x syntax
20
// TODO: *_var function variants
21
// void flag_bool_var(bool *var, const char *name, bool def, const char *desc);
22
// void flag_bool_uint64(uint64_t *var, const char *name, bool def, const char *desc);
24
// WARNING! *_var functions may break the flag_name() functionality
26
char *flag_name(void *val);
27
bool *flag_bool(const char *name, bool def, const char *desc);
28
uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc);
29
size_t *flag_size(const char *name, uint64_t def, const char *desc);
30
char **flag_str(const char *name, const char *def, const char *desc);
31
bool flag_parse(int argc, char **argv);
32
int flag_rest_argc(void);
33
char **flag_rest_argv(void);
34
void flag_print_error(FILE *stream);
35
void flag_print_options(FILE *stream);
39
//////////////////////////////
41
#ifdef FLAG_IMPLEMENTATION
51
static_assert(COUNT_FLAG_TYPES == 4, "Exhaustive Flag_Value definition");
63
FLAG_ERROR_INVALID_NUMBER,
64
FLAG_ERROR_INTEGER_OVERFLOW,
65
FLAG_ERROR_INVALID_SIZE_SUFFIX,
82
Flag flags[FLAGS_CAP];
85
Flag_Error flag_error;
86
char *flag_error_name;
92
static Flag_Context flag_global_context;
94
Flag *flag_new(Flag_Type type, const char *name, const char *desc)
96
Flag_Context *c = &flag_global_context;
98
assert(c->flags_count < FLAGS_CAP);
99
Flag *flag = &c->flags[c->flags_count++];
100
memset(flag, 0, sizeof(*flag));
102
// NOTE: I won't touch them I promise Kappa
103
flag->name = (char*) name;
104
flag->desc = (char*) desc;
108
char *flag_name(void *val)
110
Flag *flag = (Flag*) ((char*) val - offsetof(Flag, val));
114
bool *flag_bool(const char *name, bool def, const char *desc)
116
Flag *flag = flag_new(FLAG_BOOL, name, desc);
117
flag->def.as_bool = def;
118
flag->val.as_bool = def;
119
return &flag->val.as_bool;
122
uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc)
124
Flag *flag = flag_new(FLAG_UINT64, name, desc);
125
flag->val.as_uint64 = def;
126
flag->def.as_uint64 = def;
127
return &flag->val.as_uint64;
130
size_t *flag_size(const char *name, uint64_t def, const char *desc)
132
Flag *flag = flag_new(FLAG_SIZE, name, desc);
133
flag->val.as_size = def;
134
flag->def.as_size = def;
135
return &flag->val.as_size;
138
char **flag_str(const char *name, const char *def, const char *desc)
140
Flag *flag = flag_new(FLAG_STR, name, desc);
141
flag->val.as_str = (char*) def;
142
flag->def.as_str = (char*) def;
143
return &flag->val.as_str;
146
static char *flag_shift_args(int *argc, char ***argv)
149
char *result = **argv;
155
int flag_rest_argc(void)
157
return flag_global_context.rest_argc;
160
char **flag_rest_argv(void)
162
return flag_global_context.rest_argv;
165
bool flag_parse(int argc, char **argv)
167
Flag_Context *c = &flag_global_context;
169
flag_shift_args(&argc, &argv);
172
char *flag = flag_shift_args(&argc, &argv);
175
// NOTE: pushing flag back into args
176
c->rest_argc = argc + 1;
177
c->rest_argv = argv - 1;
181
if (strcmp(flag, "--") == 0) {
182
// NOTE: but if it's the terminator we don't need to push it back
188
// NOTE: remove the dash
192
for (size_t i = 0; i < c->flags_count; ++i) {
193
if (strcmp(c->flags[i].name, flag) == 0) {
194
static_assert(COUNT_FLAG_TYPES == 4, "Exhaustive flag type parsing");
195
switch (c->flags[i].type) {
197
c->flags[i].val.as_bool = true;
203
c->flag_error = FLAG_ERROR_NO_VALUE;
204
c->flag_error_name = flag;
207
char *arg = flag_shift_args(&argc, &argv);
208
c->flags[i].val.as_str = arg;
214
c->flag_error = FLAG_ERROR_NO_VALUE;
215
c->flag_error_name = flag;
218
char *arg = flag_shift_args(&argc, &argv);
220
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.");
222
// TODO: replace strtoull with a custom solution
223
// That way we can get rid of the dependency on errno and static_assert
224
unsigned long long int result = strtoull(arg, &endptr, 10);
226
if (*endptr != '\0') {
227
c->flag_error = FLAG_ERROR_INVALID_NUMBER;
228
c->flag_error_name = flag;
232
if (result == ULLONG_MAX && errno == ERANGE) {
233
c->flag_error = FLAG_ERROR_INTEGER_OVERFLOW;
234
c->flag_error_name = flag;
238
c->flags[i].val.as_uint64 = result;
244
c->flag_error = FLAG_ERROR_NO_VALUE;
245
c->flag_error_name = flag;
248
char *arg = flag_shift_args(&argc, &argv);
250
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.");
252
// TODO: replace strtoull with a custom solution
253
// That way we can get rid of the dependency on errno and static_assert
254
unsigned long long int result = strtoull(arg, &endptr, 10);
256
// TODO: handle more multiplicative suffixes like in dd(1). From the dd(1) man page:
257
// > N and BYTES may be followed by the following
258
// > multiplicative suffixes: c =1, w =2, b =512, kB =1000, K
259
// > =1024, MB =1000*1000, M =1024*1024, xM =M, GB
260
// > =1000*1000*1000, G =1024*1024*1024, and so on for T, P,
262
if (strcmp(endptr, "K") == 0) {
264
} else if (strcmp(endptr, "M") == 0) {
266
} else if (strcmp(endptr, "G") == 0) {
267
result *= 1024*1024*1024;
268
} else if (strcmp(endptr, "") != 0) {
269
c->flag_error = FLAG_ERROR_INVALID_SIZE_SUFFIX;
270
c->flag_error_name = flag;
271
// TODO: capability to report what exactly is the wrong suffix
275
if (result == ULLONG_MAX && errno == ERANGE) {
276
c->flag_error = FLAG_ERROR_INTEGER_OVERFLOW;
277
c->flag_error_name = flag;
281
c->flags[i].val.as_size = result;
285
case COUNT_FLAG_TYPES:
287
assert(0 && "unreachable");
297
c->flag_error = FLAG_ERROR_UNKNOWN;
298
c->flag_error_name = flag;
308
void flag_print_options(FILE *stream)
310
Flag_Context *c = &flag_global_context;
311
for (size_t i = 0; i < c->flags_count; ++i) {
312
Flag *flag = &c->flags[i];
314
fprintf(stream, " -%s\n", flag->name);
315
fprintf(stream, " %s\n", flag->desc);
316
static_assert(COUNT_FLAG_TYPES == 4, "Exhaustive flag type defaults printing");
317
switch (c->flags[i].type) {
319
if (flag->def.as_bool) {
320
fprintf(stream, " Default: %s\n", flag->def.as_bool ? "true" : "false");
324
fprintf(stream, " Default: %" PRIu64 "\n", flag->def.as_uint64);
327
fprintf(stream, " Default: %zu\n", flag->def.as_size);
330
if (flag->def.as_str) {
331
fprintf(stream, " Default: %s\n", flag->def.as_str);
335
assert(0 && "unreachable");
341
void flag_print_error(FILE *stream)
343
Flag_Context *c = &flag_global_context;
344
static_assert(COUNT_FLAG_ERRORS == 6, "Exhaustive flag error printing");
345
switch (c->flag_error) {
347
// NOTE: don't call flag_print_error() if flag_parse() didn't return false, okay? ._.
348
fprintf(stream, "Operation Failed Successfully! Please tell the developer of this software that they don't know what they are doing! :)");
350
case FLAG_ERROR_UNKNOWN:
351
fprintf(stream, "ERROR: -%s: unknown flag\n", c->flag_error_name);
353
case FLAG_ERROR_NO_VALUE:
354
fprintf(stream, "ERROR: -%s: no value provided\n", c->flag_error_name);
356
case FLAG_ERROR_INVALID_NUMBER:
357
fprintf(stream, "ERROR: -%s: invalid number\n", c->flag_error_name);
359
case FLAG_ERROR_INTEGER_OVERFLOW:
360
fprintf(stream, "ERROR: -%s: integer overflow\n", c->flag_error_name);
362
case FLAG_ERROR_INVALID_SIZE_SUFFIX:
363
fprintf(stream, "ERROR: -%s: invalid size suffix\n", c->flag_error_name);
365
case COUNT_FLAG_ERRORS:
367
assert(0 && "unreachable");
373
// Copyright 2021 Alexey Kutepov <reximkut@gmail.com>
375
// Permission is hereby granted, free of charge, to any person obtaining a copy
376
// of this software and associated documentation files (the "Software"), to
377
// deal in the Software without restriction, including without limitation the
378
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
379
// sell copies of the Software, and to permit persons to whom the Software is
380
// furnished to do so, subject to the following conditions:
382
// The above copyright notice and this permission notice shall be included in
383
// all copies or substantial portions of the Software.
385
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
386
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
387
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
388
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
389
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
390
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS