Git Product home page Git Product logo

fire-hpp's Introduction

CI status

Fire for C++

Fire for C++ 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,

  • --help prints 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.

What's covered?

Why yet another CLI library?!

With most libraries, creating a CLI roughly follows this pattern:

  1. define arguments
  2. call parse(argc, argv);
  3. check whether errors are detected by parse(), print them and return (optional)
  4. check for -h and --help, print the help message and return (optional)
  5. for each argument:
    1. get argument from the map and if necessary convert to the right type
    2. 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.

Q. Quickstart

Q.1 Requirements

  • 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.

Q.2 Running examples

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 -j8 or ninja)
  • 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/add --help or ./examples/add -x=3 -y=5

Q.3 Usage with cmake

T. Tutorial

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 actual main() function that defines your program's entry point and fires off fired_main(). fired_main is called without arguments, thus compiler is forced to use the default fire::arg values.

  • 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"}) or fire::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::arg objects 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::string or std::vector<T> type and default initialized with fire::arg objects (failing to initialize properly results in undefined behavior!). See conversions to learn how each of them affects the CLI.

D. Documentation

D.1 FIRE(fired_main[, program_description]) and variants

  • FIRE(fired_main[, program_description]) 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.
  • FIRE_ALLOW_UNUSED(...) is similar to FIRE(...), but allows unused arguments. This is useful when raw arguments are accessed (eg. for another library).

Program description can be supplied as the second argument:

FIRE(fired_main, "Hello there")

D.2 fire::arg(identifiers[, default_value]])

D.2.1 Identifiers

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"
  • 0 index 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
  • Example: int fired_main(int x = fire::arg({"-x", "--long-name"}));

    • CLI usage: program -x=1
    • CLI usage: program --long-name=1
  • Example: int fired_main(int x = fire::arg({0, "<name of argument>", "description"}));

    • CLI usage: program 1
    • <name of argument> and description appear in help messages
  • Example: int fired_main(vector<int> x = fire::arg(fire::variadic()));

    • CLI usage: program 1 2 3

D.2.2 Default value (optional)

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

For an optional argument without a default, see fire::optional.

D.2.3 Constraints

Constraints can be applied to arguments by calling fire:arg's constraint methods:

  • fire::arg().min(T minimum) - specifies minimum value
  • fire::arg().max(T maximum) - specifies maximum value
  • fire::arg().bounds(T minimum, T maximum) - specifies both minimum and maximum values
  • fire::arg().one_of({...}) - specifies possible values

These methods

  1. check whether user supplied value fits the constraint and emit a proper error message if condition is not met
  2. append the constraint to argument's help message
  • Example: int fired_main(int x = fire::arg("-x").bounds(-1000, 1000));

    • CLI usage: program -x=100 -> x==100
    • CLI usage: program -x=-10000 -> Error: argument -x value -10000 must be at least -1000
    • CLI usage: program --help -> -x=INTEGER description [-1000 <= x <= 1000] (one of the lines)
  • Example: int fired_main(std::string s = fire::arg("-s").one_of({"hi", "there"}));

    • CLI usage: program -s=hi -> s==hi
    • CLI usage: program -s=hello -> Error: argument -s value must be one of (hi, there), but given was 'hello'

D.3 fire::arg conversions

To conveniently obtain arguments with the right type and automatically check the validity of input, fire::arg class defines several implicit conversions.

D.3.1 std::string, integral, or floating point

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"
  • 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

D.3.2 fire::optional

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()==true and name.value_or("default")==name.value()=="fire"

For an optional argument with a sensible default value, see default value.

D.3.3 bool: flag argument

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

D.3.4 std::vector: variadic argument

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=={}

D.4 Post fired_main() functions

D.4.1.1 Print help or error message with fire formatting

  • fire::print_help() - print the help message
  • fire::input_error(const string &msg) - print error message and exit program
  • fire::input_assert(bool pass, const std::string &msg) - if pass is not satisfied, print error message and exit program

D.4.1.2 Helper function for getting named argument names in assert messages:

std::string fire::helpful_name(const string &name) - return user called name of the specified argument (given by one name) if it exists, otherwise return empty string.

  • Example: int fired_main(optional<int> value = fire::arg({"-v", "--value"}));
    • CLI usage: program -> fire::helpful_name("--value") == ""
    • CLI usage: program -v=2 -> fire::helpful_name("--value") == "-v"
    • CLI usage: program --value=2 -> fire::helpful_name("--value") == "--value"
  • Typical usage: fire::input_assert(value > 0, "Argument " + fire::helpful_name("--value") + " must be greater than 0")

std::string fire::helpful_name(int pos) - return correctly formatted positional argument number

D.4.2 Accessing raw arguments

Some third party libraries require access to raw argc/argv. This is gained through fire::raw_args (of type fire::c_args), which has argc() and argv() methods for accessing the arguments.

Examples:

  • Usage without modification:
int argc = fire::raw_args.argc();
char ** argv = fire::raw_args.argv();
non_modifying_call(argc, argv);
  • Usage with modification:
fire::c_args raw_args_copy = fire::raw_args; 
int& argc = raw_args_copy.argc();
char ** argv = raw_args_copy.argv();
modifying_call(argc, argv);
// Once out of scope, raw_args_copy releases argv strings

You also need FIRE_ALLOW_UNUSED(...) if the third party library processes it's own arguments.

G. Guides

L. Links

Other libraries you might find useful:

  • python-fire: something similar in Python, I got my inspiration from there
  • fire-llvm: an even neater interface (you can write parameters as (int x, int y) instead of (int x = fire::arg("-x"), int y = fire::arg("-y"))). Also adds subcommands support. The downside is that it's not pure C++, and requires you to compile an llvm plugin to actually compile your code.
  • CLI11: A somewhat more conventional C++ CLI library. IMHO A very good interface while avoiding unconventional trickery (like hijacking main()).

fire-hpp's People

Contributors

cxkoda avatar eigenwhat avatar kongaskristjan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fire-hpp's Issues

Add a dedicated LICENSE file

This makes packaging fire-hpp easier and makes it also easier to automatically detect and identify the license

What about "--" to separate positional arguments?

Ideally a command line parsing library should support -- to separate positional arguments from options., to support CLI interface like this:

$ touch -- -actual-file-with-dash-prefix
$ ls -l -- -actual-file-with-dash-prefix
-rw-r--r-- 1 bronek users 0 Aug  8 19:03 -actual-file-with-dash-prefix
$ rm -- -actual-file-with-dash-prefix

BTW, this is lovely library, thank you for doing it!

licensing

This is such a great repo, really love what you're doing! Even in the early stages this project is useful and ready for certain production environments.

It's difficult to consume though without understanding what the licensing requirements will be.

The description string does not work as documented

When I provide a help description string, it is used as a default instead of a description:

int fired_main(std::string file = fire::arg(0, "<first>"))

When I run this as of commit 2a562ff, I get:

$ ./build/colormapper.exe --help

Usage:
  C:\git\maph\submodules\colormapper\build\colormapper.exe [<0>]

Options:
  [<0> STRING]   [default: <first>]

<first> becomes the default value instead of the description. I tried this with an int positional argument as well std::string and it has the same problem.

Thank you for the header! This is an interesting project and it looks promising to reduce boilerplate.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.