Fire for C++, inspired by python-fire, is a single header library that creates a command line interface from a function signature. Here's the whole program for adding two numbers with command line:
#include <iostream>
#include <fire-hpp/fire.hpp>
int fired_main(int x = fire::arg("-x"), int y = fire::arg("-y")) {
std::cout << x + y << std::endl;
return 0;
}
FIRE(fired_main)
That's all. Usage:
$ ./add -x=1 -y=2
3
As you likely expect,
--helpprints a meaningful message with required arguments and their types.- an error message is displayed for incorrect usage.
- the program runs on Linux, Windows and Mac OS.
See examples for other kinds of arguments.
With most libraries, creating a CLI roughly follows this pattern:
- define arguments
- call
parse(argc, argv); - check whether errors are detected by
parse(), print them and return (optional) - check for
-hand--help, print the help message and return (optional) - for each argument:
- get argument from the map and if necessary convert to the right type
- try/catch for errors in conversion and return (optional)
That's a non-trivial amount of boilerplate, especially for simple scripts. Because of that, programmers (and a lot of library examples) tend to skip the optional parts, however this incurs a significant usability cost. Also, many libraries don't help at all with the conversion step.
With fire-hpp, you only call FIRE(fired_main) and define arguments as function parameters. When fired_main() scope begins, all steps have already been completed.
- flags; named and positional parameters; variadic parameters
- optional parameters/default values
- conversions to integer, floating-point and
std::string - program/parameter descriptions
- standard constructs, such as
-abc <=> -a -b -cand-x=1 <=> -x 1
- Using fire.hpp: C++11 compatible compiler.
- Compiling examples: CMake 3.1+.
- Compiling/running tests: CMake 3.11+ and Python 3.5+. GTest is downloaded, compiled and linked automatically.
Steps to run examples:
- Clone repo:
git clone https://github.com/kongaskristjan/fire-hpp - Create build and change directory:
cd fire-hpp && mkdir build && cd build - Configure/build:
cmake .. && cmake --build .(or substitute the latter command with appropriate build system invocation, eg.make -j8orninja) - If errors are encountered, clear the build directory and disable pedantic warnings as errors with
cmake -D DISABLE_PEDANTIC= ..(you are encouraged to open an issue). - Run:
./examples/basic --helpor./examples/basic -x=3 -y=5
Let's go through each part of the following example.
int fired_main(int x = fire::arg("-x"), int y = fire::arg("-y")) { // Define and convert arguments
std::cout << x + y << std::endl; // Use x and y, they're ints!
return 0;
}
FIRE(fired_main) // call fired_main()
-
FIRE(function name)
FIRE(fired_main)expands into the actualmain()function that defines your program's entry point and fires offfired_main().fired_mainis called without arguments, thus compiler is forced to use the defaultfire::argvalues. -
fire::arg(identifiers[, default value]) A constructor that accepts the name/shorthand/description/position of the argument. Use a brace enclosed list for several of them (eg.
fire::arg({"-x", "--longer-name", "description of the argument"})orfire::arg({0, "zeroth element"}). The library expects a single dash for single-character shorthands, two dashes for multi-character names, and zero dashes for descriptions.fire::argobjects should be used as default values for fired function parameters. See documentation for more info. -
int fired_main(arguments) This is what you perceive as the program entry point. All arguments must be
bool, integral, floating-point,fire::optional<T>,std::stringorstd::vector<T>type and default initialized withfire::argobjects (Failing to initialize properly results in undefined behavior!). See conversions to learn how each of them affects the CLI.
D.1 FIRE(fired_main[, program_description]) and FIRE_NO_EXCEPTIONS(fired_main[, program_description])
FIRE(fired_main) creates the main function that parses arguments and calls fired_main.
FIRE_NO_EXCEPTIONS() is similar, but can be used even if compiler has exceptions disabled. However, this imposes limitations on what the library can parse. Specifically, it disallows space assignment, eg. -x 1 must be written as -x=1.
Program description can be supplied as the second argument:
FIRE(fired_main, "Hello there")
Identifiers are used to find arguments from command line and provide a description. In general, it's a brace enclosed list of elements (braces can be omitted for a single element):
"-s"shorthand name for argument"--multicharacter-name"0index of a positional argument"<name of the positional argument>"- any other string:
"description of any argument" - variadic arguments:
fire::variadic()
-
Example:
int fired_main(int x = fire::arg("-x"));- CLI usage:
program -x=1
- CLI usage:
-
Example:
int fired_main(int x = fire::arg({"-x", "--long-name"}));- CLI usage:
program -x=1 - CLI usage:
program --long-name=1
- CLI usage:
-
Example:
int fired_main(int x = fire::arg({0, "<name of argument>", "description"}));- CLI usage:
program 1 <name of argument>anddescriptionappear in help messages
- CLI usage:
-
Example:
int fired_main(vector<int> x = fire::arg(fire::variadic()));- CLI usage:
program 1 2 3
- CLI usage:
Default value if no value is provided through command line. Can be either std::string, integral or floating-point type and fire::arg must be converted to that same type. This default is also displayed on the help page.
- Example:
int fired_main(int x = fire::arg({"-x", "--long-name"}, 0));- CLI usage:
program->x==0 - CLI usage:
program -x=1->x==1
- CLI usage:
For an optional argument without a default, see fire::optional.
To conveniently obtain arguments with the right type and automatically check the validity of input, fire::arg class defines several implicit conversions.
Converts the argument value on command line to the respective type. Displays an error if the conversion is impossible or default value has wrong type.
-
Example:
int fired_main(std::string name = fire::arg("--name"));- CLI usage:
program --name=fire->name=="fire"
- CLI usage:
-
Example:
int fired_main(double x = fire::arg("-x"));- CLI usage:
program -x=2.5->x==2.5 - CLI usage:
program -x=blah->Error: value blah is not a real number
- CLI usage:
Used for optional arguments without a reasonable default value. This way the default value doesn't get printed in a help message. The underlying type can be std::string, integral or floating-point.
fire::optional is a tear-down version of std::optional, with compatible implementations for has_value(), value_or() and value().
- Example:
int fired_main(fire::optional<std::string> name = fire::arg("--name"));- CLI usage:
program->name.has_value()==false,name.value_or("default")=="default" - CLI usage:
program --name="fire"->name.has_value()==trueandname.value_or("default")==name.value()=="fire"
- CLI usage:
Boolean flags are true when they exist on command line and false when they don't. Multiple single-character flags can be packed on command line by prefixing with a single hyphen: -abc <=> -a -b -c
- Example:
int fired_main(bool flag = fire::arg("--flag"));- CLI usage:
program->flag==false - CLI usage:
program --flag->flag==true
- CLI usage:
A method for getting all positional arguments as a vector. The fire::arg object can be converted to std::vector<std::string>, std::vector<integral type> or std::vector<floating-point type>. Using variadic argument forbids extracting positional arguments with fire::arg(index).
In this case, identifier should be fire::variadic(). Description can be supplied in the usual way.
- Example:
int fired_main(vector<std::string> params = fire::arg({fire::variadic(), "description"}));- CLI usage:
program abc xyz->params=={"abc", "xyz"} - CLI usage:
program->params=={}
- CLI usage:
Fire can easily be used by other C++ CMake projects.
You may use Fire from a folder in your project (typically a git submodule).
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(foo)
set(CMAKE_CXX_STANDARD 11)
add_subdirectory(fire_folder)
add_executable(bar bar.cpp)
target_link_libraries(bar fire-hpp::fire-hpp)
Alternatively, you can also use the more modern FetchContent.
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(foo)
set(CMAKE_CXX_STANDARD 11)
include(FetchContent)
FetchContent_Declare(
fire
GIT_REPOSITORY https://github.com/kongaskristjan/fire-hpp
)
FetchContent_MakeAvailable(fire)
add_executable(bar bar.cpp)
target_link_libraries(bar fire)
Fire can also be installed and found through find_package():
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(foo)
set(CMAKE_CXX_STANDARD 11)
find_package(fire-hpp REQUIRED)
add_executable(bar bar.cpp)
target_link_libraries(bar fire-hpp::fire-hpp)
Fire can be packaged for consumption through Conan by running conan create . fire-hpp/version@user/channel from the root directory of this repository. It can then be consumed as in the find_package() example above if using cmake_find_package generator.
This library uses extensive testing. Unit tests are located in tests/, while examples/ are used as integration tests. The latter also ensures examples are up-to-date. Before committing, please verify python3 ./build/tests/run_standard_tests.py succeed. Releases are also tested on many platforms with python3 ./tests/run_release_tests.py.
v0.2 release is tested on:
- Arch Linux: gcc==10.2.0, clang==10.0.1: C++11, C++14, C++17, C++20
- Ubuntu 18.04: gcc=={4.8, 4.9}: C++11 and gcc=={5.5, 6.5, 7.5, 8.4}: C++11, C++14, C++17
- Ubuntu 18.04: clang=={3.5, 3.6, 3.7, 3.8, 3.9, 4.0}: C++11, C++14 and clang=={5.0, 6.0, 7.0, 8.0, 9.0}: C++11, C++14, C++17
- Windows 10: MSVC=={19.26} (2019 Build Tools): C++11, C++14, C++17
- Mac OS: XCode=={12.0} (requires CMake 3.18.1+): C++11, C++14, C++17
- Sufficient information for contributing
- Create a document describing the internals of this project
- Document the code
- Add contribution guidelines
- Add issue and PR templates
- Thoroughly describe each task in the roadmap
- Subcommands (eg.
git addandgit show, which may have different flags/options) - Possibility to raise errors and print help in fired_main()
- Host documentation on readthedocs.io
- Solve Windows non-ascii character input
- Self-defined objects with string streams
- Argument requirements
fire::arg(identifier).require(condition, msg)- Specialized requirements, eg.
bounds(min, max),one_of([option1], [option2], ...)
fire::arg(identifier).save([condition]), which will save the program from exiting even if not all required arguments are present or correct (eg. for--version)
- Ensure API user gets an error message when using required positional arguments after optional positional arguments
- Allow converting named
fire::argtostd::vectororstd::map(allows multiple occurrences of an argument, like cmake's-D) - Automatic testing for error messages