first version

This commit is contained in:
Krzosa Karol
2025-10-20 11:34:59 +02:00
commit f54ed04e27
3 changed files with 812 additions and 0 deletions

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>

207
README.md Normal file
View File

@@ -0,0 +1,207 @@
## args.h
Single header command line argument parser for C and C++
loosely based on python's argparse module.
## Usage:
Define implementation macro before you include this file in *one* C or C++ file:
```c
#define ARGS_IMPLEMENTATION
```
Should look like this:
```c
#include ...
#include ...
#define ARGS_IMPLEMENTATION
#include "args.h"
```
This will expand the implementation into that file, in other files you can just
include the header without the macro.
```c
#include "args.h"
```
## Example:
```c
void example(int argc, char **argv) {
args_t args = {
.program = argv[0],
.description = "this program does very cool things on many files and outputs it somewhere",
.epilogue = "it also shows cool epilogue text in it's help",
};
args_add(&args, (args_desc_t){"filenames", .nargs = "+" });
args_add(&args, (args_desc_t){"--output", "-o" });
args_add(&args, (args_desc_t){"--force", "-f", .store_value = "1", .default_value = "0"});
args_add(&args, (args_desc_t){"--recursive", "-r", .store_value = "1", .default_value = "0"});
args_add(&args, (args_desc_t){"--help", "-h", .store_value = "1", .default_value = "0"});
args_parse(&args, argc, argv);
const char **filenames = args_get(&args, "filenames");
const char **output = args_get(&args, "--output");
int force = atoi(args_get(&args, "-f")[0]);
int recursive = atoi(args_get(&args, "--recursive")[0]);
int help = atoi(args_get(&args, "--help")[0]);
if (help) {
args_print_help(&args);
return;
}
// ..
for (int i = 0; filenames[i]; i += 1) {
// ..
}
}
```
Execute like this:
> app.exe file1 file2 file3 --force --output /folder/.
Help menu:
```
usage: ./a.out [options] filenames ...
this program does very cool things on many files and outputs it somewhere
options:
filenames <arg> ... list of filenames to process
--output -o <arg> path to the folder where output will be gathered
--force -f force the processing
--recursive -r apply recursively to files in current directory
--help -h show this help window
it also shows cool epilogue text in it's help
```
## How To's
1. Single positional argument
> program.exe argument
Specifying `.nargs = "<digit>"` enforces a strict number
of arguments, so supplying less or more will result in
an error.
```c
args_t args = {0};
args_add(&args, (args_desc_t){"argument", .nargs = "1"});
args_parse(&args, argc, argv);
const char *argument = args_get(&args, "argument")[0];
```
2. One or more positional arguments
> program.exe a b c
You need to set `.nargs = "+"` this will make it so that one
or more arguments are expected. Alternatively `.nargs = "*"`,
which makes it so that having even one argumenti is optional.
```c
args_t args = {0};
args_add(&args, (args_desc_t){"arguments", .nargs = "+"});
args_parse(&args, argc, argv);
const char **arguments = args_get(&args, "arguments");
```
3. Help menu, help flag
> program.exe --help
```
args_t args = {0};
args_add(&args, (args_desc_t){"--help", "-h", .store_value = "1", .default_value = "0"});
args_parse(&args, argc, argv);
int help = atoi(args_get(&args, "help")[0]);
if (help) {
args_print_help(&args);
exit(0);
}
```
4. Typechecked optional list of integers
> program.exe --list 1 2 3
> program.exe
```c
args_t args = {0};
args_add(&args, (args_desc_t){"--list", "-l", .type = ARGS_TYPE_INT, .nargs = "*"});
args_parse(&args, argc, argv);
const char **list = args_get(&args, "--list");
```
5. Custom logging
You can override the ARGS_PRINTF macro, at compile time
the library will use that. By default it uses fprintf(stderr)
```c
#define ARGS_PRINTF(...) fprintf(stdout, __VA_ARGS__)
#define ARGS_IMPLEMENTATION
#include "args.h"
```
6. User defined memory allocator
```c
#define ARGS_MALLOC(x) my_malloc(x)
#define ARGS_FREE(x) my_free(x)
#define ARGS_IMPLEMENTATION
#include "args.h"
```
7. C++
The library works in C++ but leverages special C syntax for aggregate types,
in later C++ the syntax might be simiar so no issues there but for older
versions you will either need to write some small wrapper functions or just
bare with initializing structures inline. For example:
```c
void args_add_bool(args_t *args, const char *name, const char *alt_name, const char *help) {
args_desc_t desc = {name, alt_name};
desc.help = help;
desc.store_value = "1";
desc.default_value = "0";
args_add(args, desc);
}
void args_add_nargs(args_t *args, const char *name, const char *alt_name, const char *nargs, const char *help) {
args_desc_t desc = {name, alt_name};
desc.nargs = nargs;
desc.help = help;
args_add(args, desc);
}
int main(int argc, char **argv) {
args_t args = {
.program = argv[0],
.description = "this program does very cool things on many files and outputs it somewhere",
.epilogue = "it also shows cool epilogue text in it's help",
};
args_add_nargs(&args, "filenames", NULL, "+", "list of filenames to process");
args_add_nargs(&args, "--output", "-o", "?", "path to the folder where output will be gathered");
args_add_bool(&args, "--force", "-f", "force the processing");
args_add_bool(&args, "--recursive", "-r", "apply recursively to files in current directory");
args_add_bool(&args, "--help", "-h", "show this help window");
args_parse(&args, argc, argv);
}
```
## License
Library is in public domain. See end of code file for license.

581
args.h Normal file
View File

@@ -0,0 +1,581 @@
#ifndef ARGS_HEADER
/*
args.h - v1
Krzosa Karol - 2025
## args.h
Single header command line argument parser for C and C++
loosely based on python's argparse module.
## Usage:
Define implementation macro before you include this file in *one* C or C++ file:
```c
#define ARGS_IMPLEMENTATION
```
Should look like this:
```c
#include ...
#include ...
#define ARGS_IMPLEMENTATION
#include "args.h"
```
This will expand the implementation into that file, in other files you can just
include the header without the macro.
```c
#include "args.h"
```
## Example:
```c
void example(int argc, char **argv) {
args_t args = {
.program = argv[0],
.description = "this program does very cool things on many files and outputs it somewhere",
.epilogue = "it also shows cool epilogue text in it's help",
};
args_add(&args, (args_desc_t){"filenames", .nargs = "+" });
args_add(&args, (args_desc_t){"--output", "-o" });
args_add(&args, (args_desc_t){"--force", "-f", .store_value = "1", .default_value = "0"});
args_add(&args, (args_desc_t){"--recursive", "-r", .store_value = "1", .default_value = "0"});
args_add(&args, (args_desc_t){"--help", "-h", .store_value = "1", .default_value = "0"});
args_parse(&args, argc, argv);
const char **filenames = args_get(&args, "filenames");
const char **output = args_get(&args, "--output");
int force = atoi(args_get(&args, "-f")[0]);
int recursive = atoi(args_get(&args, "--recursive")[0]);
int help = atoi(args_get(&args, "--help")[0]);
if (help) {
args_print_help(&args);
return;
}
// ..
for (int i = 0; filenames[i]; i += 1) {
// ..
}
}
```
Execute like this:
> app.exe file1 file2 file3 --force --output /folder/.
Help menu:
```
usage: ./a.out [options] filenames ...
this program does very cool things on many files and outputs it somewhere
options:
filenames <arg> ... list of filenames to process
--output -o <arg> path to the folder where output will be gathered
--force -f force the processing
--recursive -r apply recursively to files in current directory
--help -h show this help window
it also shows cool epilogue text in it's help
```
## How To's
1. Single positional argument
> program.exe argument
Specifying `.nargs = "<digit>"` enforces a strict number
of arguments, so supplying less or more will result in
an error.
```c
args_t args = {0};
args_add(&args, (args_desc_t){"argument", .nargs = "1"});
args_parse(&args, argc, argv);
const char *argument = args_get(&args, "argument")[0];
```
2. One or more positional arguments
> program.exe a b c
You need to set `.nargs = "+"` this will make it so that one
or more arguments are expected. Alternatively `.nargs = "*"`,
which makes it so that having even one argumenti is optional.
```c
args_t args = {0};
args_add(&args, (args_desc_t){"arguments", .nargs = "+"});
args_parse(&args, argc, argv);
const char **arguments = args_get(&args, "arguments");
```
3. Help menu, help flag
> program.exe --help
```
args_t args = {0};
args_add(&args, (args_desc_t){"--help", "-h", .store_value = "1", .default_value = "0"});
args_parse(&args, argc, argv);
int help = atoi(args_get(&args, "help")[0]);
if (help) {
args_print_help(&args);
exit(0);
}
```
4. Typechecked optional list of integers
> program.exe --list 1 2 3
> program.exe
```c
args_t args = {0};
args_add(&args, (args_desc_t){"--list", "-l", .type = ARGS_TYPE_INT, .nargs = "*"});
args_parse(&args, argc, argv);
const char **list = args_get(&args, "--list");
```
5. Custom logging
You can override the ARGS_PRINTF macro, at compile time
the library will use that. By default it uses fprintf(stderr)
```c
#define ARGS_PRINTF(...) fprintf(stdout, __VA_ARGS__)
#define ARGS_IMPLEMENTATION
#include "args.h"
```
6. User defined memory allocator
```c
#define ARGS_MALLOC(x) my_malloc(x)
#define ARGS_FREE(x) my_free(x)
#define ARGS_IMPLEMENTATION
#include "args.h"
```
7. C++
The library works in C++ but leverages special C syntax for aggregate types,
in later C++ the syntax might be simiar so no issues there but for older
versions you will either need to write some small wrapper functions or just
bare with initializing structures inline. For example:
```c
void args_add_bool(args_t *args, const char *name, const char *alt_name, const char *help) {
args_desc_t desc = {name, alt_name};
desc.help = help;
desc.store_value = "1";
desc.default_value = "0";
args_add(args, desc);
}
void args_add_nargs(args_t *args, const char *name, const char *alt_name, const char *nargs, const char *help) {
args_desc_t desc = {name, alt_name};
desc.nargs = nargs;
desc.help = help;
args_add(args, desc);
}
int main(int argc, char **argv) {
args_t args = {
.program = argv[0],
.description = "this program does very cool things on many files and outputs it somewhere",
.epilogue = "it also shows cool epilogue text in it's help",
};
args_add_nargs(&args, "filenames", NULL, "+", "list of filenames to process");
args_add_nargs(&args, "--output", "-o", "?", "path to the folder where output will be gathered");
args_add_bool(&args, "--force", "-f", "force the processing");
args_add_bool(&args, "--recursive", "-r", "apply recursively to files in current directory");
args_add_bool(&args, "--help", "-h", "show this help window");
args_parse(&args, argc, argv);
}
```
## License
Library is in public domain. See end of code file for license.
*/
#define ARGS_HEADER
#include <stddef.h>
#include <stdbool.h>
#include <ctype.h>
typedef enum {
ARGS_TYPE_STRING,
ARGS_TYPE_INT,
} args_type_t;
typedef struct args_desc_t args_desc_t;
struct args_desc_t {
const char *name; // for example: '--filename', skipping '-' sets the argument as positional
const char *alt_name; // usually the short name: '-f', invalid for positional
const char *default_value; // default value parameter will contain if no value was provided on cmd
const char *store_value; // turns processing into special flag syntax, no argument following flag is expected
// ? - 1 or 0
// * - 0 or more
// + - 1 or more
// 1 - exactly one
// 2 - exactly two
// 3 - ...
const char *nargs; // how many arguments this command uses
const char *help; // the text describing what the command does in the help string
args_type_t type; // you can use this to add some additional checking for arguments
// internally used data, user shouldn't modify directly
int match_count;
const char **values;
int nargs_parsed;
bool nargs_required;
};
typedef struct args_t args_t;
struct args_t {
const char *program;
const char *description;
const char *epilogue;
// internall used data, user shouldn't modify directly
args_desc_t *desc;
int errors;
};
int args_parse(args_t *args, int argc, char **argv);
void args_add(args_t *args, args_desc_t desc);
const char **args_get(args_t *args, const char *name);
void args_print_help(args_t *args);
void args_free(args_t *args);
#ifdef ARGS_IMPLEMENTATION
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef ARGS_MALLOC
#define ARGS_MALLOC(s) malloc(s)
#endif
#ifndef ARGS_FREE
#define ARGS_FREE(p) free(p)
#endif
#ifndef ARGS_PRINTF
#define ARGS_PRINTF(...) fprintf(stderr, __VA_ARGS__)
#endif
#define ARGS_REPORT(args, ...) (args->errors++, ARGS_PRINTF(__VA_ARGS__))
// excerpt from Sean Barett's stb_ds.h, simple dynamic array
typedef struct {
size_t length;
size_t capacity;
} args__array_header;
#define args__header(t) ((args__array_header *)(t) - 1)
#define args__arrgrow(a, b, c) ((a) = args__arrgrowf_wrapper((a), sizeof *(a), (b), (c)))
#define args__arrcap(a) ((a) ? args__header(a)->capacity : 0)
#define args__arrmaybegrow(a, n) ((!(a) || args__header(a)->length + (n) > args__header(a)->capacity) ? (args__arrgrow(a, n, 0), 0) : 0)
#define args_alen(a) ((a) ? (int)args__header(a)->length : 0)
#define args_slen(x) (int)(sizeof((x)) / sizeof((x)[0]))
#define args_aput(a, v) (args__arrmaybegrow(a, 1), (a)[args__header(a)->length++] = (v))
static void *args__arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) {
args__array_header temp = {0}; // force debugging
size_t min_len = args_alen(a) + addlen;
(void)sizeof(temp);
// compute the minimum capacity needed
if (min_len > min_cap)
min_cap = min_len;
if (min_cap <= args__arrcap(a))
return a;
// increase needed capacity to guarantee O(1) amortized
if (min_cap < 2 * args__arrcap(a)) {
min_cap = 2 * args__arrcap(a);
} else if (min_cap < 4) {
min_cap = 4;
}
void *b = ARGS_MALLOC(elemsize * min_cap + sizeof(args__array_header));
b = (char *)b + sizeof(args__array_header);
if (a) {
memcpy(b, a, args_alen(a) * elemsize);
args__header(b)->length = args_alen(a);
ARGS_FREE(args__header(a));
} else {
args__header(b)->length = 0;
}
args__header(b)->capacity = min_cap;
return b;
}
#ifdef __cplusplus
template<class T> static T * args__arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) {
return (T*)args__arrgrowf((void *)a, elemsize, addlen, min_cap);
}
#else
#define args__arrgrowf_wrapper args__arrgrowf
#endif
static bool args_streq(const char *a, const char *b) {
return strcmp(a, b) == 0;
}
static args_desc_t *args_get_desc(args_desc_t *desc, const char *name) {
for (args_desc_t *it = desc; it < desc + args_alen(desc); it += 1) {
if (args_streq(it->name, name) || (it->alt_name &&args_streq(it->alt_name, name))) {
return it;
}
}
return NULL;
}
static args_desc_t *args_get_positional(args_desc_t *desc) {
for (args_desc_t *it = desc; it < desc + args_alen(desc); it += 1) {
if (it->name[0] != '-') return it;
}
return NULL;
}
const char **args_get(args_t *args, const char *name) {
args_desc_t *i = args_get_desc(args->desc, name);
if (i) return i->values;
return NULL;
}
void args_add(args_t *args, args_desc_t desc) {
args_aput(args->desc, desc);
}
#define ARGS__MAX(x,y) ((x)>(y)?(x):(y))
void args_print_help(args_t *args) {
ARGS_PRINTF("usage: %s [options]", args->program);
args_desc_t *a = args_get_positional(args->desc);
if (a) {
ARGS_PRINTF(" %s", a->name);
}
ARGS_PRINTF(" ... \n");
if (args->description) {
ARGS_PRINTF("\n%s\n", args->description);
}
ARGS_PRINTF("\noptions:\n");
int name_len = 0;
int alt_name_len = 0;
for (args_desc_t *it = args->desc; it < args->desc + args_alen(args->desc); it += 1) {
name_len = ARGS__MAX(strlen(it->name), name_len);
if (it->alt_name) {
alt_name_len = ARGS__MAX(strlen(it->alt_name), alt_name_len);
}
}
for (args_desc_t *it = args->desc; it < args->desc + args_alen(args->desc); it += 1) {
ARGS_PRINTF(" %-*s", name_len + 1, it->name);
if (it->alt_name) {
ARGS_PRINTF(" %-*s", alt_name_len + 1, it->alt_name);
} else {
ARGS_PRINTF(" %-*s", alt_name_len +1, " ");
}
bool print_arg = false;
bool print_tbc = false;
if (args_streq(it->nargs, "+") || args_streq(it->nargs, "*") || it->nargs_parsed > 1) {
print_arg = true;
print_tbc = true;
}
if (args_streq(it->nargs, "?") || it->nargs_parsed == 1) {
print_arg = true;
}
if (it->store_value) {
print_arg = false;
print_tbc = false;
}
if (print_arg) {
ARGS_PRINTF(" <arg>");
} else {
ARGS_PRINTF(" ");
}
if (print_tbc) {
ARGS_PRINTF(" ...");
} else {
ARGS_PRINTF(" ");
}
if (it->help) {
ARGS_PRINTF(" %s", it->help);
}
ARGS_PRINTF("\n");
}
if (args->epilogue) {
ARGS_PRINTF("\n%s\n", args->epilogue);
}
}
void args_free(args_t *args) {
for (int i = 0; i < args_alen(args->desc); i += 1) {
ARGS_FREE(args__header(args->desc[i].values));
}
ARGS_FREE(args__header(args->desc));
}
int args_parse(args_t *args, int argc, char **argv) {
int pos_count = 0;
for (args_desc_t *it = args->desc; it < args->desc + args_alen(args->desc); it += 1) {
if (it->alt_name && it->alt_name[0] != '-') {
ARGS_REPORT(args, "invalid alt_name for flag: '%s', always should begin with '-'\n", it->alt_name);
}
if (it->nargs == NULL) {
it->nargs = "?";
} else {
bool special = args_streq(it->nargs, "*") || args_streq(it->nargs, "+") || args_streq(it->nargs, "?");
if (!special) {
bool digit = true;
for (int i = 0; it->nargs[i]; i += 1) {
if (!isdigit(it->nargs[i])) {
digit = false;
break;
}
}
if (!digit) {
ARGS_REPORT(args, "invalid nargs: '%s', expected digit or '*', '+', '?'\n", it->nargs);
} else {
it->nargs_parsed = atoi(it->nargs);
it->nargs_required = true;
}
}
}
if (it->name[0] != '-') {
pos_count += 1;
if (pos_count > 1) {
ARGS_REPORT(args, "more then one positional argument: '%s'\n", it->name);
}
}
}
if (args->errors) {
goto end;
}
for (int i = 1; i < argc;) {
const char *curr = argv[i++];
if (curr[0] != '-') {
args_desc_t *it = args_get_positional(args->desc);
if (!it) {
ARGS_REPORT(args, "unrecognized argument: '%s'\n", curr);
continue;
}
it->match_count += 1;
args_aput(it->values, curr);
continue;
}
args_desc_t *it = args_get_desc(args->desc, curr);
if (it == NULL) {
ARGS_REPORT(args, "unrecognized argument: '%s'\n", curr);
continue;
}
if (it->store_value) {
args_aput(it->values, it->store_value);
it->match_count += 1;
continue;
}
int count = 0;
int end = i;
if (it->nargs_required) end += it->nargs_parsed;
if (args_streq(it->nargs, "?")) end += 1;
if (args_streq(it->nargs, "+")) end += argc;
if (args_streq(it->nargs, "*")) end += argc;
for (;i < end && i < argc;) {
if (argv[i] && argv[i][0] == '-') {
break;
}
count += 1;
it->match_count += 1;
args_aput(it->values, argv[i]);
i += 1;
}
if (count == 0) {
ARGS_REPORT(args, "expected a value to follow after argument: '%s'\n", curr);
}
}
for (args_desc_t *it = args->desc; it < args->desc + args_alen(args->desc); it += 1) {
if (args_streq(it->nargs, "?") && it->match_count > 1) {
ARGS_REPORT(args, "expected argument: '%s', to appear 1 or 0 times instead of %d times\n", it->name, it->match_count);
}
if (args_streq(it->nargs, "+") && it->match_count == 0) {
ARGS_REPORT(args, "required argument: '%s'\n", it->name);
}
if (it->nargs_required && it->match_count != it->nargs_parsed) {
ARGS_REPORT(args, "expected %d arguments: '%s', got %d arguments\n", it->nargs_parsed, it->name, it->match_count);
}
if (it->default_value && it->match_count == 0) {
args_aput(it->values, it->default_value);
}
if (it->type == ARGS_TYPE_INT) {
for (int i = 0; i < args_alen(it->values); i += 1) {
const char *val = it->values[i];
for (int idx = 0; val[idx]; idx += 1) {
char c = val[idx];
if (!isdigit(c)) {
ARGS_REPORT(args, "failed to interpret: '%s' as integer, non integer value: '%s'\n", it->name, val);
break;
}
}
}
}
args_aput(it->values, NULL);
}
end:;
return args->errors;
}
#endif // ARGS_IMPLEMENTATION
#endif // ARGS_HEADER
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
*/