diff --git a/external/cflags/.gitignore b/external/cflags/.gitignore new file mode 100644 index 00000000..abef3588 --- /dev/null +++ b/external/cflags/.gitignore @@ -0,0 +1,4 @@ +*.vs +*.vscode +/[Bb]uild + diff --git a/external/cflags/CMakeLists.txt b/external/cflags/CMakeLists.txt new file mode 100644 index 00000000..bf7cc8cf --- /dev/null +++ b/external/cflags/CMakeLists.txt @@ -0,0 +1,121 @@ +cmake_minimum_required(VERSION 3.2 FATAL_ERROR) + +project( + cflags + LANGUAGES C CXX + DESCRIPTION "Command line flag parsing library in C/C++" + VERSION 3.0.3 +) + +### +### cflags +### + +add_library(cflags INTERFACE) +add_library(cflags::cflags ALIAS cflags) + +target_include_directories( + cflags INTERFACE + $ + $ +) + +### +### cppflags +### + +add_library(cppflags INTERFACE) +add_library(cflags::cppflags ALIAS cppflags) + +target_include_directories( + cppflags INTERFACE + $ + $ +) + +set_target_properties( + cppflags + PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) + +### +### Install +### + +install( + TARGETS cflags cppflags + EXPORT cflagsTargets + INCLUDES DESTINATION include +) + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/cflagsConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion +) + +install( + EXPORT cflagsTargets + NAMESPACE cflags:: + DESTINATION lib/cmake/cflags +) + +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cflagsConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cflagsConfig.cmake + INSTALL_DESTINATION lib/cmake/cflags +) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/cflagsConfigVersion.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cflagsConfig.cmake + DESTINATION lib/cmake/cflags +) + +include(FindPkgConfig) + +if(PKG_CONFIG_FOUND) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cflags.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/cflags.pc + @ONLY + ) + + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/cflags.pc + DESTINATION share/pkgconfig + ) +endif() + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ + DESTINATION include +) + +### +### Test executables +### + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + + add_executable(example examples/example.c) + + target_link_libraries(example cflags) + + add_executable(example-cpp examples/example.cpp) + + target_link_libraries(example-cpp cppflags) + + set_target_properties( + example-cpp + PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + ) + +endif() \ No newline at end of file diff --git a/external/cflags/LICENSE b/external/cflags/LICENSE new file mode 100644 index 00000000..9fa24d83 --- /dev/null +++ b/external/cflags/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Stephen Lane-Walsh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 OR COPYRIGHT HOLDERS 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. diff --git a/external/cflags/README.md b/external/cflags/README.md new file mode 100644 index 00000000..f547d5bf --- /dev/null +++ b/external/cflags/README.md @@ -0,0 +1,310 @@ + +# cflags + +Command line flag parsing library in C + +Heavily inspired by Go's `flag` package +https://golang.org/pkg/flag/ + +## Building + +cflags is a header only library. To use it, simply copy `cflags.h` or `cflags.hpp` to your project, or add it to your include path. + +You may also install it using cmake, like so: + +``` +cmake path/to/source +sudo make install +``` + +This will install both CMake and pkg-config configuration files. + +## Argument Parsing Logic + +* The first argument is stored in `program` +* The following arguments are parsed left to right +* If an argument does not start with `-`, it is placed in the additional arguments list stored in `args/argv` +* If the special `--` argument appears, all following arguments are treated as positional + e.g. `-c 4 -- --name hello` would parse the `-c`, but place `--name` and `hello` into `args/argv` +* Arguments starting with `--` are long name flags, e.g. `--example` + * The list of flags is searched for one with `long_name` equal to the argument name (after the `--`), e.g. `long_name == example` + * If a flag is not found with that name, an error is printed and `parse()` returns false +* Arguments starting with just `-` are short name flags, e.g. `-xvf` + * These can be grouped together, so they are searched one at a time from left to right, e.g. `x`, `v`, then `f` + * If any of these fail to match a flag, an error is printed and `parse()` returns false +* Once a flag is found, it attempts to find a value + * Arguments with long names can also come in the forms `--name`, `--name=value`, or `--name value` + * Arguemnts with short names can come in the forms `-n`, or `-n value` + * Note: Only the last short flag of a group can have a value, e.g. `-xvf file` will work, but `-xfv file` will fail + * If the flag is of type `[c]string`, `int`, or `float` then a value is required, and if one is not found an error is printed and `parse()` returns false + * Arguments of type `bool` can have a value, e.g. `--debug=false`, but one is not required + * Each time a flag is encountered, the `count` member is incremented + * The value for a flag is overwritten each time the flag is processed, the last argument parsed wins, e.g. `-c 4 -c 10` will result in `-c` being 10 + * If you want to capture each argument separately, use `add_*_callback` instead + +## Usage (C) + +```cpp +#include + +void process_string(const char * str) +{ + printf("processing %s\n", str); +} + +void process_bool(bool b) +{ + printf("processing %d\n", b); +} + +void process_int(int i) +{ + printf("processing %d\n", i); +} + +void process_float(float f) +{ + printf("processing %f\n", f); +} + +int main(int argc, char** argv) +{ + // Create a cflags object + cflags_t * flags = cflags_init(); + + // Add a bool flag, which will be callable with -d or --debug + // The value will be true if it exists, and can bet set to false + // by saying -d false or --debug=false + bool debug = false; + cflags_add_bool(flags, 'd', "debug", &debug, "enable debug mode"); + + // Add a similar help flag, which will be callable with just --help + bool help = false; + cflags_add_bool(flags, '\0', "help", &help, "print this text and exit"); + + // Add a string flag + const char * string = NULL; + cflags_add_string(flags, 's', "string", &string, "enter a string"); + + // Add an int flag + int count = 0; + cflags_add_int(flags, 'c', "count", &count, "enter a number"); + + // Add a float flag + float amount = 0.f; + cflags_add_float(flags, 'a', "amount", &amount, "enter a float"); + + // Add a string callback flag. This will call the supplied function with the value + // when it is parsed + cflags_add_string_callback(flags, 'f', "file", &process_string, "process a file"); + + cflags_add_bool_callback(flags, 'q', "bool-flag", &process_bool, "process a bool"); + + cflags_add_int_callback(flags, 'w', "int-flag", &process_int, "process a int"); + + cflags_add_float_callback(flags, 'e', "float-flag", &process_float, "process a float"); + + // Add a flag that can be called multiple times + cflags_flag_t * verbose = cflags_add_bool(flags, 'v', "verbose", NULL, "enables verbose output, repeat up to 4 times for more verbosity"); + + // Parse the command arguments + if (!cflags_parse(flags, argc, argv) || help || flags->argc == 0) { + cflags_print_usage(flags, + "[OPTION]... [ARG]...", + "Tests the cflags library.", + "Additional information about this library can be found by at:\n" + " https://github.com/WhoBrokeTheBuild/cflags"); + } + + printf("debug: %d\n", debug); + printf("string: %s\n", string); + printf("count: %d\n", count); + printf("amount: %f\n", amount); + + // Print the number of times verbose was added + printf("verbosity: %d\n", verbose->count); + + // Print any additional arguments, in the order they were parsed + for (int i = 1; i < flags->argc; ++i) { + printf("positional arg %d: %s\n", i, flags->argv[i]); + } + + // Cleanup + cflags_free(flags); + + return 0; +} +``` + +## Usage (C++) + +```cpp +#include + +void process_string(std::string str) +{ + printf("processing %s\n", str.c_str()); +} + +void process_cstring(const char * str) +{ + printf("processing %s\n", str); +} + +void process_bool(bool b) +{ + printf("processing %d\n", b); +} + +void process_int(int i) +{ + printf("processing %d\n", i); +} + +void process_float(float f) +{ + printf("processing %f\n", f); +} + +int main(int argc, char * argv[]) +{ + // Create a cflags object + cflags::cflags flags; + + // Add a bool flag, which will be callable with -d or --debug + // The value will be true if it exists, and can bet set to false + // by saying -d false or --debug=false + bool debug = false; + flags.add_bool('d', "debug", &debug, "enable debug mode"); + + // Add a similar help flag, which will be callable with just --help + bool help = false; + flags.add_bool('\0', "help", &help, "print this text and exit"); + + // Add a string flag + std::string string; + flags.add_string('s', "string", &string, "enter a string"); + + // Add a cstring flag + const char * cstring = NULL; + flags.add_cstring('\0', "cstring", &cstring, "enter a string (cstring)"); + + // Add an int flag + int count = 0; + flags.add_int('c', "count", &count, "enter a number"); + + // Add a float flag + float amount = 0.f; + flags.add_float('a', "amount", &amount, "enter a float"); + + // Add a string callback flag. This will call the supplied function with the value + // when it is parsed + flags.add_string_callback('f', "file", &process_string, "process a file"); + + flags.add_cstring_callback('\0', "cfile", &process_cstring, "process a file (cstring)"); + + flags.add_bool_callback('q', "bool-flag", &process_bool, "process a bool"); + + flags.add_int_callback('w', "int-flag", &process_int, "process a int"); + + flags.add_float_callback('e', "float-flag", &process_float, "process a float"); + + // You can also use lambdas + flags.add_string_callback('l', "lambda", + [](std::string value) { + printf("Hello %s\n", value.c_str()); + }, + "use a lambda function" + ); + + // Add a flag that can be called multiple times + auto verbose = flags.add_bool('v', "verbose", NULL, "enables verbose output, repeat up to 4 times for more verbosity"); + + // Parse the command arguments + if (!flags.parse(argc, argv) || help || flags.argc == 0) { + flags.print_usage( + "[OPTION]... [ARG]...", + "Tests the cflags library.", + "Additional information about this library can be found by at:\n" + " https://github.com/WhoBrokeTheBuild/cflags"); + } + + printf("debug: %d\n", debug); + printf("string: %s\n", string.c_str()); + printf("cstring: %s\n", cstring); + printf("count: %d\n", count); + printf("amount: %f\n", amount); + + // Print the number of times verbose was added + printf("verbosity: %d\n", verbose->count); + + // Print any additional arguments, in the order they were parsed + for (auto& arg : flags.args) { + printf("positional arg %s\n", arg.data()); + } + + // For backwards compatability, the additional arguments are also exposed in argc/argv + for (int i = 0; i < flags.argc; ++i) { + printf("positional arg %d: %s\n", i, flags.argv[i]); + } + + return 0; +} +``` + +## Quirks + +### 1. Only the last short-name argument in a group may have a value. + +For example: +```cpp +flags.add_string('f', "file", parse_filename, "parse a filename"); +flags.add_bool('d', "debug", &debug, "enable debug mode"); + +// These will work +test -df test.txt +test -df test.txt -f test2.txt + +// And these will fail +test -fd test.txt +test -ff test.txt test2.txt +``` + +### 2. Any `char *` strings point to the original memory from `argv` + +Call the following code as `program -s hello` +```cpp +int main(int argc, char ** argv) +{ + for (int i = 1; i < argc; ++i) { + printf("argv[%d] @ %p: %s\n", i, argv[i], argv[i]); + } + + cflags_t * flags = cflags_init(); + + const char * string = NULL; + cflags_add_string(flags, 's', "string", &string, "enter a string"); + + cflags_parse(flags, argc, argv); + + // The address pointed to by `string` is the same memory pointed to by argv + printf("string @ %p: %s\n", string, string); + + cflags_free(flags); + + return 0; +} +``` + +Example output +``` +argv[1] @ 0000024F6BDD665F: -s +argv[2] @ 0000024F6BDD6662: hello +string @ 0000024F6BDD6662: hello +``` + +When parsing arguments in the form `--name=value`, the memory pointed to by `argv` is altered and the `=` is replaced by a `\0`. + +When using the C++ version, arguments as `std::string` do not point at `argv` as their memory gets copied. + + diff --git a/external/cflags/cmake/cflags.pc.in b/external/cflags/cmake/cflags.pc.in new file mode 100644 index 00000000..e41788c7 --- /dev/null +++ b/external/cflags/cmake/cflags.pc.in @@ -0,0 +1,9 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=include + +Name: cflags +Description: Command Line Argument Parser in C +Version: @VERSION@ + +Requires: +Cflags: -I${includedir} diff --git a/external/cflags/cmake/cflagsConfig.cmake.in b/external/cflags/cmake/cflagsConfig.cmake.in new file mode 100644 index 00000000..6e847e29 --- /dev/null +++ b/external/cflags/cmake/cflagsConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/cflagsTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/external/cflags/examples/example.c b/external/cflags/examples/example.c new file mode 100644 index 00000000..1ed3fa1a --- /dev/null +++ b/external/cflags/examples/example.c @@ -0,0 +1,56 @@ +#include "cflags.h" +#include + +void parse_file(const char * filename) +{ + printf("parsing %s\n", filename); +} + +int main(int argc, char** argv) +{ + cflags_t * flags = cflags_init(); + + bool help = false; + cflags_add_bool(flags, '\0', "help", &help, "display this help and exit"); + + bool debug = false; + cflags_add_bool(flags, 'd', "debug", &debug, "enable debug mode"); + + int count = 0; + cflags_add_int(flags, 'c', "count", &count, "enter a number"); + + float amount = 0.f; + cflags_add_float(flags, 'a', "amount", &amount, "enter a float"); + + cflags_add_bool(flags, 'q', "really-long-argument-name", NULL, "testing really long argument names"); + + cflags_add_string_callback(flags, 'f', "file", &parse_file, "process a file"); + + cflags_flag_t * verbose = cflags_add_bool(flags, 'v', "verbose", NULL, "enables verbose output, repeat up to 4 times for more verbosity"); + + // Parse the command arguments + if (!cflags_parse(flags, argc, argv) || help || argc == 1) { + cflags_print_usage(flags, + "[OPTION]... [ARG]...", + "Tests the cflags library.", + "Additional information about this library can be found by at:\n" + " https://github.com/WhoBrokeTheBuild/cflags"); + } + + printf("help: %d\n", help); + printf("debug: %d\n", debug); + + printf("count: %d\n", count); + printf("amount: %f\n", amount); + + printf("verbosity: %d\n", verbose->count); + + printf("argc/argv:\n"); + for (int i = 0; i < flags->argc; ++i) { + printf("positional %d: %s\n", i, flags->argv[i]); + } + + cflags_free(flags); + + return 0; +} diff --git a/external/cflags/examples/example.cpp b/external/cflags/examples/example.cpp new file mode 100644 index 00000000..0c17bbbf --- /dev/null +++ b/external/cflags/examples/example.cpp @@ -0,0 +1,64 @@ +#include "cflags.hpp" + +void parse_file(const char * filename) +{ + printf("parsing %s\n", filename); +} + +int main(int argc, char * argv[]) +{ + cflags::cflags flags; + + bool help = false; + flags.add_bool('\0', "help", &help, "display this help and exit"); + + bool debug = false; + flags.add_bool('d', "debug", &debug, "enable debug mode"); + + int count = 0; + flags.add_int('c', "count", &count, "enter a number"); + + float amount = 0.f; + flags.add_float('a', "amount", &amount, "enter a float"); + + flags.add_bool('q', "really-long-argument-name", nullptr, "testing really long argument names"); + + flags.add_cstring_callback('f', "file", &parse_file, "process a file"); + + flags.add_string_callback('n', "name", + [](std::string name) { + printf("Hello %s\n", name.c_str()); + }, + "say hello to name"); + + auto verbose = flags.add_bool('v', "verbose", NULL, "enables verbose output, repeat up to 4 times for more verbosity"); + + if (!flags.parse(argc, argv) || help || argc == 1) { + flags.print_usage( + "[OPTION]... [ARG]...", + "Tests the cflags library.", + "Additional information about this library can be found by contacting:\n" + " sdl.slane@gmail.com"); + return 1; + } + + printf("help: %d\n", help); + printf("debug: %d\n", debug); + + printf("count: %d\n", count); + printf("amount: %f\n", amount); + + printf("verbosity: %d\n", verbose->count); + + printf("args:\n"); + for (auto& arg : flags.args) { + printf("Positional %s\n", arg.data()); + } + + printf("argc/argv:\n"); + for (int i = 0; i < flags.argc; ++i) { + printf("positional %d: %s\n", i, flags.argv[i]); + } + + return 0; +} \ No newline at end of file diff --git a/external/cflags/include/cflags.h b/external/cflags/include/cflags.h new file mode 100644 index 00000000..d6a29c1f --- /dev/null +++ b/external/cflags/include/cflags.h @@ -0,0 +1,505 @@ +// +// cflags version 3.0.3 +// +// MIT License +// +// Copyright (c) 2022 Stephen Lane-Walsh +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// 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 OR COPYRIGHT HOLDERS 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. +// + +#ifndef CFLAGS_H +#define CFLAGS_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define CFLAGS_ERROR_OOM "cflags: out of memory" + +enum cflags_type +{ + CFLAGS_TYPE_UNDEFINED = -1, + CFLAGS_TYPE_STRING, + CFLAGS_TYPE_BOOL, + CFLAGS_TYPE_INT, + CFLAGS_TYPE_FLOAT, + CFLAGS_TYPE_STRING_CALLBACK, + CFLAGS_TYPE_BOOL_CALLBACK, + CFLAGS_TYPE_INT_CALLBACK, + CFLAGS_TYPE_FLOAT_CALLBACK, +}; + +typedef enum cflags_type cflags_type_t; + +struct cflags_flag +{ + char short_name; + const char * long_name; + const char * description; + + cflags_type_t type; + unsigned count; + + struct cflags_flag * next; + + union { + const char ** string_ptr; + bool * bool_ptr; + int * int_ptr; + float * float_ptr; + }; + + void (*string_callback)(const char *); + void (*bool_callback)(bool); + void (*int_callback)(int); + void (*float_callback)(float); +}; + +typedef struct cflags_flag cflags_flag_t; + +struct cflags +{ + const char * program; + + int argc; + char ** argv; + + cflags_flag_t * first_flag; +}; + +typedef struct cflags cflags_t; + +static cflags_t * cflags_init() +{ + cflags_t * flags = (cflags_t *)malloc(sizeof(cflags_t)); + if (!flags) { + fprintf(stderr, CFLAGS_ERROR_OOM); + return NULL; + } + + flags->program = NULL; + flags->argc = 0; + flags->argv = NULL; + flags->first_flag = NULL; + return flags; +} + +cflags_flag_t * _cflags_add_flag(cflags_t * flags) +{ + cflags_flag_t ** next_flag = &flags->first_flag; + + while (*next_flag) { + next_flag = &(*next_flag)->next; + } + + *next_flag = (cflags_flag_t *)malloc(sizeof(cflags_flag_t)); + if (!*next_flag) { + fprintf(stderr, CFLAGS_ERROR_OOM); + return NULL; + } + + (*next_flag)->short_name = '\0'; + (*next_flag)->long_name = NULL; + (*next_flag)->type = CFLAGS_TYPE_UNDEFINED; + (*next_flag)->count = 0; + (*next_flag)->description = NULL; + (*next_flag)->next = NULL; + + return *next_flag; +} + +static cflags_flag_t * cflags_add_string(cflags_t * flags, char short_name, const char * long_name, const char ** value, const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_STRING; + flag->string_ptr = value; + flag->description = description; + return flag; +} + +static cflags_flag_t * cflags_add_bool(cflags_t * flags, char short_name, const char * long_name, bool * value, const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_BOOL; + flag->bool_ptr = value; + flag->description = description; + return flag; +} + +static cflags_flag_t * cflags_add_int(cflags_t * flags, char short_name, const char * long_name, int * value, const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_INT; + flag->int_ptr = value; + flag->description = description; + return flag; +} + +static cflags_flag_t * cflags_add_float(cflags_t * flags, char short_name, const char * long_name, float * value, const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_FLOAT; + flag->float_ptr = value; + flag->description = description; + return flag; +} + +static cflags_flag_t * cflags_add_string_callback(cflags_t * flags, char short_name, const char * long_name, void (*func)(const char *), const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_STRING_CALLBACK; + flag->string_callback = func; + flag->description = description; + return flag; +} + +static cflags_flag_t * cflags_add_bool_callback(cflags_t * flags, char short_name, const char * long_name, void (*func)(bool), const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_BOOL_CALLBACK; + flag->bool_callback = func; + flag->description = description; + return flag; +} + +static cflags_flag_t * cflags_add_int_callback(cflags_t * flags, char short_name, const char * long_name, void (*func)(int), const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_INT_CALLBACK; + flag->int_callback = func; + flag->description = description; + return flag; +} + +static cflags_flag_t * cflags_add_float_callback(cflags_t * flags, char short_name, const char * long_name, void (*func)(float), const char * description) +{ + cflags_flag_t * flag = _cflags_add_flag(flags); + if (!flag) { + return NULL; + } + + flag->short_name = short_name; + flag->long_name = long_name; + flag->type = CFLAGS_TYPE_FLOAT_CALLBACK; + flag->float_callback = func; + flag->description = description; + return flag; +} + +static bool _cflags_parse_bool(const char * str) +{ + return !(strcmp(str, "false") == 0 || + strcmp(str, "FALSE") == 0 || + strcmp(str, "0") == 0); +} + +static void _cflags_process_flag(cflags_flag_t * flag, const char * value) +{ + ++flag->count; + + switch (flag->type) { + case CFLAGS_TYPE_STRING: + if (flag->string_ptr) { + *flag->string_ptr = value; + } + break; + case CFLAGS_TYPE_STRING_CALLBACK: + if (flag->string_callback) { + flag->string_callback(value); + } + break; + case CFLAGS_TYPE_BOOL: + if (flag->bool_ptr) { + if (value) { + *flag->bool_ptr = _cflags_parse_bool(value); + } + else { + *flag->bool_ptr = true; + } + } + break; + case CFLAGS_TYPE_BOOL_CALLBACK: + if (flag->bool_callback) { + if (value) { + flag->bool_callback(_cflags_parse_bool(value)); + } + else { + flag->bool_callback(true); + } + } + break; + case CFLAGS_TYPE_INT: + if (flag->int_ptr) { + if (value) { + *flag->int_ptr = strtol(value, NULL, 10); + } + } + break; + case CFLAGS_TYPE_INT_CALLBACK: + if (flag->int_callback) { + if (value) { + flag->int_callback(strtol(value, NULL, 10)); + } + } + break; + case CFLAGS_TYPE_FLOAT: + if (flag->float_ptr) { + if (value) { + *flag->float_ptr = strtof(value, NULL); + } + } + break; + case CFLAGS_TYPE_FLOAT_CALLBACK: + if (flag->float_callback) { + if (value) { + flag->float_callback(strtof(value, NULL)); + } + } + break; + default: ; + } +} + +static bool cflags_parse(cflags_t * flags, int argc, char ** argv) +{ + flags->argc = 1; + flags->argv = (char **)malloc(flags->argc * sizeof(char *)); + if (!flags->argv) { + fprintf(stderr, CFLAGS_ERROR_OOM); + return false; + } + flags->argv[0] = argv[0]; + flags->program = flags->argv[0]; + + bool passthrough = false; + for (int i = 1; i < argc; ++i) { + char * pch = argv[i]; + if (!passthrough && *pch == '-') { + ++pch; + if (*pch == '-') { + ++pch; + + if (*pch == '\0') { + // All following flags are not to be processed + passthrough = true; + continue; + } + + // Long + char * key = pch; + char * value = NULL; + + char * divider = strchr(pch, '='); + if (divider) { + *divider = '\0'; + value = divider + 1; + } + + bool next_arg_is_value = (i + 1 < argc && argv[i + 1][0] != '-'); + + bool found = false; + cflags_flag_t * flag = flags->first_flag; + while (flag) { + if (strcmp(flag->long_name, key) == 0) { + found = true; + + if (value) { + _cflags_process_flag(flag, value); + } + else if (next_arg_is_value) { + _cflags_process_flag(flag, argv[i + 1]); + ++i; + } + else if (flag->type == CFLAGS_TYPE_BOOL || flag->type == CFLAGS_TYPE_BOOL_CALLBACK) { + _cflags_process_flag(flag, NULL); + } + else { + fprintf(stderr, "%s: option '--%s' requires an value\n", flags->program, key); + return false; + } + + break; + } + + flag = flag->next; + } + + if (!found) { + fprintf(stderr, "%s: unrecognized option '--%s'\n", flags->program, key); + return false; + } + } + else { + // Short + while (*pch) { + bool is_last_short_flag = (*(pch + 1) == '\0'); + bool next_arg_is_value = (i + 1 < argc && argv[i + 1][0] != '-'); + + bool found = false; + cflags_flag_t * flag = flags->first_flag; + while (flag) { + if (flag->short_name == *pch) { + found = true; + + if (is_last_short_flag && next_arg_is_value) { + _cflags_process_flag(flag, argv[i + 1]); + ++i; + } + else if (flag->type == CFLAGS_TYPE_BOOL || flag->type == CFLAGS_TYPE_BOOL_CALLBACK) { + _cflags_process_flag(flag, NULL); + } + else { + fprintf(stderr, "%s: option '-%c' requires an value\n", flags->program, *pch); + return false; + } + + break; + } + + flag = flag->next; + } + + if (!found) { + fprintf(stderr, "%s: unrecognized option '-%c'\n", flags->program, *pch); + return false; + } + + ++pch; + } + } + } + else { + ++flags->argc; + char ** tmp = (char **)realloc(flags->argv, flags->argc * sizeof(char *)); + if (!tmp) { + fprintf(stderr, CFLAGS_ERROR_OOM); + return false; + } + flags->argv = tmp; + flags->argv[flags->argc - 1] = pch; + } + } + + return true; +} + +static void cflags_free(cflags_t * flags) +{ + free(flags->argv); + flags->argv = NULL; + + cflags_flag_t * tmp = NULL; + cflags_flag_t * flag = flags->first_flag; + while (flag) { + tmp = flag; + flag = flag->next; + free(tmp); + } + + free(flags); + flags = NULL; +} + +static void cflags_print_usage(cflags_t * flags, const char * usage, const char * above, const char * below) +{ + printf("%s %s\n", flags->program, usage); + printf("%s\n\n", above); + + cflags_flag_t * flag = flags->first_flag; + while (flag) { + printf(" "); + if (flag->short_name != '\0') { + printf("-%c, ", flag->short_name); + } + else { + printf(" "); + } + size_t long_name_len = 0; + if (flag->long_name) { + printf("--%s", flag->long_name); + long_name_len = strlen(flag->long_name); + } + if (long_name_len > 20) { + printf("\n "); + } + else { + for (size_t i = 0; i < 20 - long_name_len; ++i) { + printf(" "); + } + } + printf("%s\n", flag->description); + flag = flag->next; + } + + printf("\n%s\n", below); +} + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // CFLAGS_H \ No newline at end of file diff --git a/external/cflags/include/cflags.hpp b/external/cflags/include/cflags.hpp new file mode 100644 index 00000000..0e026e8f --- /dev/null +++ b/external/cflags/include/cflags.hpp @@ -0,0 +1,484 @@ +// +// cflags version 3.0.3 +// +// MIT License +// +// Copyright (c) 2022 Stephen Lane-Walsh +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// 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 OR COPYRIGHT HOLDERS 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. +// + +#ifndef CFLAGS_HPP +#define CFLAGS_HPP + +#include +#include +#include +#include +#include + +namespace cflags { + +using std::string; +using std::string_view; +using std::vector; +using std::function; + +struct flag +{ +public: + + enum class type + { + Undefined = -1, + String, + CString, + Bool, + Int, + Float, + StringCallback, + CStringCallback, + BoolCallback, + IntCallback, + FloatCallback, + }; + + char short_name; + string long_name; + string description; + + type type; + unsigned count; + + union { + string * string_ptr; + const char ** cstring_ptr; + bool * bool_ptr; + int * int_ptr; + float * float_ptr; + }; + + function string_callback; + function cstring_callback; + function bool_callback; + function int_callback; + function float_callback; + + flag() + : short_name('\0') + , type(type::Undefined) + , count(0) + , string_ptr(nullptr) // This will set all of the *_ptr members + { } + + inline void process(const char * value) + { + auto parse_bool = [](string_view str) { + return !( + str == "false" || + str == "FALSE" || + str == "0" + ); + }; + + ++count; + + switch (type) { + case type::String: + if (string_ptr) { + *string_ptr = value; + } + break; + case type::StringCallback: + if (string_callback) { + if (value) { + string_callback(value); + } + } + break; + case type::CString: + if (cstring_ptr) { + *cstring_ptr = value; + } + break; + case type::CStringCallback: + if (cstring_callback) { + cstring_callback(value); + } + break; + case type::Bool: + if (bool_ptr) { + if (value) { + *bool_ptr = parse_bool(value); + } + else { + *bool_ptr = true; + } + } + break; + case type::BoolCallback: + if (bool_callback) { + if (value) { + bool_callback(parse_bool(value)); + } + else { + bool_callback(true); + } + } + break; + case type::Int: + if (int_ptr) { + if (value) { + *int_ptr = strtol(value, nullptr, 10); + } + } + break; + case type::IntCallback: + if (int_callback) { + if (value) { + int_callback(strtol(value, nullptr, 10)); + } + } + break; + case type::Float: + if (float_ptr) { + if (value) { + *float_ptr = strtof(value, nullptr); + } + } + break; + case type::FloatCallback: + if (float_callback) { + if (value) { + float_callback(strtof(value, nullptr)); + } + } + break; + default: ; + } + } + +}; + +class cflags +{ +public: + + string program; + vector args; + + int argc; + char ** argv; + + cflags() + : argc(0) + , argv(nullptr) + { } + + flag * add_flag(flag && flag) + { + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_string(char short_name, string long_name, string * value_ptr, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::String; + flag.string_ptr = value_ptr; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_cstring(char short_name, string long_name, const char ** value_ptr, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::CString; + flag.cstring_ptr = value_ptr; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_bool(char short_name, string long_name, bool * value_ptr, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::Bool; + flag.bool_ptr = value_ptr; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_int(char short_name, string long_name, int * value_ptr, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::Int; + flag.int_ptr = value_ptr; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_float(char short_name, string long_name, float * value_ptr, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::Float; + flag.float_ptr = value_ptr; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_string_callback(char short_name, string long_name, function callback, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::StringCallback; + flag.string_callback = callback; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_cstring_callback(char short_name, string long_name, function callback, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::CStringCallback; + flag.cstring_callback = callback; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_bool_callback(char short_name, string long_name, function callback, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::BoolCallback; + flag.bool_callback = callback; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_int_callback(char short_name, string long_name, function callback, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::IntCallback; + flag.int_callback = callback; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + flag * add_float_callback(char short_name, string long_name, function callback, string description) + { + flag flag; + flag.short_name = short_name; + flag.long_name = long_name; + flag.type = flag::type::FloatCallback; + flag.float_callback = callback; + flag.description = description; + + _flags.push_back(flag); + return &_flags.back(); + } + + /// + /// + /// + inline bool parse(int main_argc, char * main_argv[]) + { + argc = main_argc; + argv = main_argv; + + program = argv[0]; + + bool passthrough = false; + for (int i = 1; i < argc; ++i) { + char * pch = argv[i]; + if (!passthrough && *pch == '-') { + ++pch; + if (*pch == '-') { + ++pch; + + if (*pch == '\0') { + // All following flags are not to be processed + passthrough = true; + continue; + } + + // Long + char * key = pch; + char * value = NULL; + + char * divider = strchr(pch, '='); + if (divider) { + *divider = '\0'; + value = divider + 1; + } + + bool next_arg_is_value = (i + 1 < argc && argv[i + 1][0] != '-'); + + bool found = false; + for (auto& flag : _flags) { + if (flag.long_name == key) { + found = true; + + if (value) { + flag.process(value); + } + else if (next_arg_is_value) { + flag.process(argv[i + 1]); + ++i; + } + else if (flag.type == flag::type::Bool || flag.type == flag::type::BoolCallback) { + flag.process(nullptr); + } + else { + fprintf(stderr, "%s: option '--%s' requires an value\n", program.c_str(), key); + return false; + } + + break; + } + } + + if (!found) { + fprintf(stderr, "%s: unrecognized option '--%s'\n", program.c_str(), key); + return false; + } + } + else { + // Short + while (*pch) { + bool is_last_short_flag = (*(pch + 1) == '\0'); + bool next_arg_is_value = (i + 1 < argc && argv[i + 1][0] != '-'); + + bool found = false; + for (flag& flag : _flags) { + if (flag.short_name == *pch) { + found = true; + + if (is_last_short_flag && next_arg_is_value) { + flag.process(argv[i + 1]); + ++i; + } + else if (flag.type == flag::type::Bool || flag.type == flag::type::BoolCallback) { + flag.process(nullptr); + } + else { + fprintf(stderr, "%s: option '-%c' requires an value\n", program.c_str(), *pch); + return false; + } + + break; + } + } + + if (!found) { + fprintf(stderr, "%s: unrecognized option '-%c'\n", program.c_str(), *pch); + return false; + } + + ++pch; + } + } + } + else { + args.push_back(pch); + _argv.push_back(pch); + } + } + + argc = static_cast(_argv.size()); + argv = _argv.data(); + + return true; + } + + void print_usage(const string& usage, const string& above, const string& below) + { + printf("Usage: %s %s\n", program.c_str(), usage.c_str()); + printf("%s\n\n", above.c_str()); + + for (auto& flag : _flags) { + printf(" "); + if (flag.short_name != '\0') { + printf("-%c, ", flag.short_name); + } + else { + printf(" "); + } + + if (!flag.long_name.empty()) { + printf("--%s", flag.long_name.c_str()); + } + if (flag.long_name.size() > 20) { + printf("\n "); + } + else { + for (size_t i = 0; i < 20 - flag.long_name.size(); ++i) { + printf(" "); + } + } + printf("%s\n", flag.description.c_str()); + } + + printf("\n%s\n", below.c_str()); + } + +private: + + vector _argv; + + vector _flags; + +}; + +} // namespace cflags + +#endif // CFLAGS_HPP \ No newline at end of file