Skip to content

Commit

Permalink
Version 3.0.0
Browse files Browse the repository at this point in the history
Add C++ specific version
Allow for passthrough arguments by specifying --
Add basic error handling for
* unrecognized arguments
* arguments that require values
If an error is encountered, it is printed and parse() returns false
Add parsing logic and C++ example to README
Add C++ guards to C version
Switch from scanf to strtol/strtof
  • Loading branch information
WhoBrokeTheBuild committed Apr 1, 2022
1 parent cb7f255 commit 14acdd7
Show file tree
Hide file tree
Showing 8 changed files with 908 additions and 109 deletions.
13 changes: 3 additions & 10 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
*.vs
*.vscode
/[Bb]uild

/build
test
cflags.pc
cflagsConfig.cmake
cflagsConfigVersion.cmake
cmake_install.cmake
CMakeCache.txt
CMakeFiles/
install_manifest.txt
Makefile
56 changes: 50 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.2 FATAL_ERROR)

PROJECT(cflags
LANGUAGES C
DESCRIPTION "Command line flag parsing library in C"
VERSION 2.0.1)
PROJECT(
cflags
LANGUAGES C CXX
DESCRIPTION "Command line flag parsing library in C/C++"
VERSION 3.0.0
)

###
### Install
### cflags
###

ADD_LIBRARY(cflags INTERFACE)
Expand All @@ -17,8 +19,31 @@ TARGET_INCLUDE_DIRECTORIES(
$<INSTALL_INTERFACE:include>
)

###
### cppflags
###

ADD_LIBRARY(cppflags INTERFACE)

TARGET_INCLUDE_DIRECTORIES(
cppflags INTERFACE
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include/>
$<INSTALL_INTERFACE:include>
)

SET_TARGET_PROPERTIES(
cppflags
PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)

###
### Install
###

INSTALL(
TARGETS cflags
TARGETS cflags cppflags
EXPORT cflagsTargets
INCLUDES DESTINATION include
)
Expand Down Expand Up @@ -69,3 +94,22 @@ INSTALL(
DIRECTORY ${CMAKE_SOURCE_DIR}/include/
DESTINATION include
)

###
### Test executables
###

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
)
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2019 Stephen Lane-Walsh
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
Expand Down
220 changes: 208 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ https://golang.org/pkg/flag/

## Building

cflags is a header only library. To use it, simply copy `cflags.h` to your project, or add it to your include path.
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:

Expand All @@ -19,9 +19,32 @@ sudo make install

This will install both CMake and pkg-config configuration files.

## Usage

```c
## 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 <cflags.h>

void process_string(const char * str)
Expand Down Expand Up @@ -53,15 +76,15 @@ int main(int argc, char** argv)
// 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")
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")
cflags_add_string(flags, 's', "string", &string, "enter a string");

// Add an int flag
int count = 0;
Expand All @@ -85,7 +108,18 @@ int main(int argc, char** argv)
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
cflags_parse(flags, argc, argv);
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);
Expand All @@ -95,20 +129,182 @@ int main(int argc, char** argv)
printf("positional arg %d: %s\n", i, flags->argv[i]);
}

// However, argv[0] remains the same
printf("argv[0] = %s\n", flags->argv[0]);
// Cleanup
cflags_free(flags);

if (help || flags->argc == 0) {
cflags_print_usage(flags,
return 0;
}
```
## Usage (C++)
```cpp
#include <cflags.hpp>
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");
}
// Cleanup
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.
Loading

0 comments on commit 14acdd7

Please sign in to comment.