first version
This commit is contained in:
24
LICENSE
Normal file
24
LICENSE
Normal 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
207
README.md
Normal 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
581
args.h
Normal 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/>
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user