Git Product home page Git Product logo

argagg's Introduction

Argument Aggregator

Build Status Coverage Status Doxygen Documentation

This is yet another C++ command line argument/option parser. It was written as a simple and idiomatic alternative to other frameworks like getopt, Boost program options, TCLAP, and others. The goal is to achieve the majority of argument parsing needs in a simple manner with an easy to use API. It operates as a single pass over all arguments, recognizing flags prefixed by - (short) or -- (long) and aggregating them into easy to access structures with lots of convenience functions. It defers processing types until you access them, so the result structures end up just being pointers into the original command line argument C-strings.

argagg supports POSIX recommended argument syntax conventions:

  • Options (short) start with a hyphen (-) and long options start with two hyphens (--)
  • Multiple short options can be grouped following a single hyphen
    • -a -b -c can also be written -abc or -bac, etc.
  • Option names are alpha numeric but long options may include hyphens
    • -v is valid, --ftest-coverage is valid
    • -# is not valid, --bad$option is not valid
  • Short options can be provided arguments with or without whitespace delimiters
    • -I /usr/local/include and -I/usr/local/include are equally valid
  • Long options can be provided arguments with whitespace or equal sign delimiters
    • --output test.txt and --output=test.txt are equivalent
  • Options and positional arguments can be interleaved
  • -- can be specified to treat all following arguments as positional arguments (i.e. not options)

Help message formatting is provided via indent-preserving word wrapping.

The project has only one required header: argagg.hpp. Optional headers under include/argagg/convert contain extra argument conversion specializations.

Introduction

To use just create an argagg::parser object. The struct doesn't provide any explicit methods for defining options. Instead we define the options using initialization lists.

argagg::parser argparser {{
    { "help", {"-h", "--help"},
      "shows this help message", 0},
    { "delim", {"-d", "--delim"},
      "delimiter (default: ,)", 1},
    { "num", {"-n", "--num"},
      "number", 1},
  }};

An option is specified by four things: the name of the option, the strings that activate the option (flags), the option's help message, and the number of arguments the option expects.

With the parser defined you actually parse the arguments by calling the argagg::parser::parse() method. If there are any problems an exception is thrown.

argagg::parser_results args;
try {
  args = argparser.parse(argc, argv);
} catch (const std::exception& e) {
  std::cerr << e.what() << '\n';
  return EXIT_FAILURE;
}

You can check if an option shows up in the command line arguments by accessing the option by name from the parser results and using the implicit boolean conversion. You can write out a simplistic option help message by streaming the argagg::parser instance itself.

if (args["help"]) {
  std::cerr << argparser;
  //     -h, --help
  //         shows this help message
  //     -d, --delim
  //         delimiter (default: ,)
  //     -n, --num
  //         number
  return EXIT_SUCCESS;
}

That help message is only for the flags. If you want a usage message it's up to you to provide it.

if (args["help"]) {
  std::cerr << "Usage: program [options] ARG1 ARG2\n" << argparser;
  // Usage: program [options] ARG1 ARG2
  //     -h, --help
  //         shows this help message
  //     -d, --delim
  //         delimiter (default: ,)
  //     -n, --num
  //         number
  return EXIT_SUCCESS;
}

A special output stream, argagg::fmt_ostream, is provided that will word wrap the usage and help (see ./examples/joinargs.cpp for a better example).

if (args["help"]) {
  argagg::fmt_ostream fmt(std::cerr);
  fmt << "Usage: program [options] ARG1 ARG2\n" << argparser;
  return EXIT_SUCCESS;
}

Generally argagg tries to do a minimal amount of work to leave most of the control with the user.

If you want to get an option argument but fallback on a default value if the option is not specified then you can use the argagg::option_results::as() API and provide a default value.

auto delim = args["delim"].as<std::string>(",");

If you don't mind being implicit an implicit conversion operator is provided allowing you to write simple assignments.

int x = 0;
if (args["num"]) {
  x = args["num"];
}

Finally, you can get all of the positional arguments as an std::vector using the argagg::parser_results::pos member. You can alternatively convert individual positional arguments using the same conversion functions as the option argument conversion methods.

auto y = 0.0;
if (args.pos.size() > 0) {
  y = args.as<double>(0);
}

One can also specify -- on the command line in order to treat all following arguments as not options.

For a more detailed treatment take a look at the examples or test cases.

Custom argument conversion functions can also be defined by specializing either argagg::convert::arg<T>() or argagg::convert::converter<T>. See test_csv.cpp as well as TEST_CASE("custom conversion function") and TEST_CASE("parse_next_component() example") in test.cpp.

Mental Model

The parser just returns a structure of pointers to the C-strings in the original argv array. The parse() method returns a parser_results object which has two things: position arguments and option results. The position arguments are just a std::vector of const char*. The option results are a mapping from option name (std::string) to option_results objects. The option_results objects are just an std::vector of option_result objects. Each instance of an option_result represents the option showing up on the command line. If there was an argument associated with it then the option_result's arg member will not be nullptr.

Consider the following command:

gcc -g -I/usr/local/include -I. -o test main.o foo.o -L/usr/local/lib -lz bar.o -lpng

This would produce a structure like follows, written in psuedo-YAML, where each string is actually a const char* pointing to some part of a string in the original argv array:

parser_results:
  program: "gcc"
  pos: ["main.o", "foo.o", "bar.o"]
  options:
    version:
    debug:
      all:
      - arg: null
    include_path:
      all:
      - arg: "/usr/local/include"
      - arg: "."
    library_path:
      all:
      - arg: "/usr/local/lib"
    library:
      all:
      - arg: "z"
      - arg: "png"
    output:
      all:
      - arg: "test"

Conversion to types occurs at the very end when the as<T>() API is used. Up to that point argagg is just dealing with C-strings.

API Reference

Doxygen documentation can be found here.

Quick Reference

Structs

  • option_result
    • const char* arg
  • option_results
    • std::vector<option_result> all
  • parser_results
    • const char* program
    • std::unordered_map<std::string, option_results> options
    • std::vector<const char*> pos
  • definition
    • const char* name
    • std::vector<std::string> flag
    • std::string help
    • unsigned int num_args
  • parser_map
    • std::array<const definition*, 256> short_map
    • std::unordered_map<std::string, const definition*> long_map
  • parser
    • std::vector<definition> definitions

Exceptions

  • unexpected_argument_error
  • unexpected_option_error
  • option_lacks_argument_error
  • invalid_flag

Installation

There is just a single required header file (argagg.hpp) so you can copy that whereever you want. If you want to properly install it you can use the CMake script. The CMake script exists primarily to build the tests and documentation, but an install target for the header is provided which will install all header files.

The standard installation dance using CMake and make is as follows:

mkdir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
make install
ctest -V # optionally run tests

Override CMAKE_INSTALL_PREFIX to change the installation location. By default (on UNIX variants) it will install to /usr/local resulting in the header being copied to /usr/local/include/argagg/argagg.hpp.

If you have Doxygen it should build and install documentation as well.

There are no dependencies other than the standard library.

Edge Cases

There are some interesting edge cases that show up in option parsing. I used the behavior of gcc as my target reference in these cases.

Greedy Arguments

Remember that options that require arguments will greedily process arguments.

Say we have the following options: -a, -b, -c, and -o. They all don't accept arguments except -o. Below is a list of permutations for short flag grouping and the results:

  • -abco foo: -o's argument is foo
  • -aboc foo: -o's argument is c, foo is a positional argument
  • -aobc foo: -o's argument is bc, foo is a positional argument
  • -oabc foo: -o's argument is abc, foo is a positional argument

For whitespace delimited arguments the greedy processing means the next argument element (in argv) will be treated as an argument for the previous option, regardless of whether or not it looks like a flag or some other special entry. That means you get behavior like below:

  • --output=foo -- --bar: --output's argument is foo, --bar is a positional argument
  • --output -- --bar: --output's argument is --, --bar is treated as a flag
  • --output --bar: --output's argument is --bar

Adding Argument Conversions

Arguments are converted into types by defining template specializations for the function argagg::convert::arg<>(). Specializations for integer and floating point types are pre-defined in include/argagg/argagg.hpp. An extension for parsing an argument as a comma-separated list of strings is defined in include/argagg/convert/comma_separated_strings.hpp.

argagg's People

Contributors

jadamcrain avatar monterrabyte avatar myint avatar nagesh4193 avatar rianquinn avatar sschoedel avatar vietjtnguyen 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

argagg's Issues

Allow `-long_name`

Is it possible to allow long names with a single dash?
e.g. -log=errors-only
obviously, short opt-s grouping should be disabled in this case

bool argument

how can i take a bool argument from cli ?
for example i want to run my program like writing ./CLParser --size 2 --num 1 --istrue false or ./CLParser --size 2 --num 1 --istrue
if i dont write nothing it takes true as default value

Support for options like -std=foo

Hi,

I would like to know if argagg could implement support for short options with =value syntax, like -std=c++11 of GCC and Clang

incorrect integer conversion

pattern is

    T ret = static_cast<T>(std::strtoX(arg, &endptr, 0));
    if (endptr == arg) {
      throw std::invalid_argument(msg.str());
    }

issues:

  • static_cast hides target value overflow if T shorter than long
  • unsigned values greater than signed.max() result in ERANGE
  • unparsed suffix ignored - "10x" considered as "10"
  • throw std::invalid_argument is actually ignored, because
    try {
      return convert::arg<T>(this->arg);
    } catch (...) {
      return t;
    }

Cannot use the NOT operator in VS2017

Hi,

I am trying to compile with a simple

if(!args["path"]) {
  std::cerr << "Hey I need a path!!!" << std::endl;
  return 0;
}

But I get the following error in Visual Studio 2017

Error C2675 unary '!': 'argagg::option_results' does not define this operator or a conversion to a type acceptable to the predefined operator

any ideas?

(Update: In gcc 5.4 it works properly... This is weird)

argagg::parser_results::operator[] throws std::out_of_range when key does not exsits

stack trace:

__GI_raise 0x00007ffffe145428
__GI_abort 0x00007ffffe14702a
__gnu_cxx::__verbose_terminate_handler() 0x00007ffffe7920d5
<unknown> 0x00007ffffe78fcc6
std::terminate() 0x00007ffffe78fd11
__cxa_throw 0x00007ffffe78ff54
std::__throw_out_of_range(char const*) 0x00007ffffe7b99af
std::__detail::_Map_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, argagg::option_results>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, argagg::option_results> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true>, true>::at hashtable_policy.h:774
std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, argagg::option_results, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, argagg::option_results> > >::at unordered_map.h:981
argagg::parser_results::operator[] argagg.hpp:763
main Application.cpp:55
__libc_start_main 0x00007ffffe130830
_start 0x000000000042b049

Code:

    try { pargs = parser.parse(argc, argv); }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return -1;
    }
    if ( pargs["help"]) {
        argagg::fmt_ostream fmt(std::cerr);
        fmt << "Usage:" << std::endl << parser;
        return 0;
    }

According to the follwing page, the method at() does throw this exception when the key is not found.

http://en.cppreference.com/w/cpp/container/unordered_map/at

I believe that there is a misuse in the argagg code

CMake Deprecation Warning

Hi, I'm getting the following deprecation warning when building the project:

CMake Deprecation Warning at libs/argagg/CMakeLists.txt:1 (cmake_minimum_required):
  Compatibility with CMake < 2.8.12 will be removed from a future version of
  CMake.

  Update the VERSION argument <min> value or use a ...<max> suffix to tell
  CMake that the project does not need compatibility with older versions.


CMake Warning (dev) at libs/argagg/CMakeLists.txt:2 (project):
  Policy CMP0048 is not set: project() command manages VERSION variables.
  Run "cmake --help-policy CMP0048" for policy details.  Use the cmake_policy
  command to set the policy and suppress this warning.

  The following variable(s) would be set to empty:

    PROJECT_VERSION
    PROJECT_VERSION_MAJOR
    PROJECT_VERSION_MINOR
    PROJECT_VERSION_PATCH
This warning is for project developers.  Use -Wno-dev to suppress it.

This should be easy an easy fix I guess.

Doc Issue

argagg::parser_results args;
try {
  args = argparser.parse(argc, argv);
} catch (const std::exception& e) {
  std::cerr << e.what() << std::endl;
  return EXIT_FAILURE;
}

should be

argagg::parser_results args;
try {
  args = argparser.parse(argc, argv);
} catch (const std::exception& e) {
  std::cerr << e.what() << '\n';
  return EXIT_FAILURE;
}

When using std::cerr, the output is not buffered, and so std::endl is redundant. When using std::cout, you should only use std::endl as well when you explicitly need a flush, which is not often the case (i.e., normally, there isn't a good reason to use it)

Optional argument param

Is it possible to support an optional argument for parameter?
E.g. it should be possible to use both --log and --log=errors-only (the 1st variant assumes some default level)

Warnings on MSVC: unused local variable

I've got the following warnings on MSVC (with /W3), they should be an easy fix :

argagg.hpp(861,35): warning C4101: 'e': unreferenced local variable
argagg.hpp(873,35): warning C4101: 'e': unreferenced local variable 

Thank you for your work on the library!

Get rid of definition names, key by the option itself

I realized that the definition name isn't really needed. I used it because I wanted a name to key on so that you can reference the option's parse results using that name, but why not just reference the option by any one of the options' flags? So instead of

//...
  parser argparser {{
//...
      {
        "sep", {"-s", "--sep"},
        "separator (default ',')", 1},
//...
    }};
//...
  auto sep = args["sep"].as<string>(",");
//...

you can just do

//...
  parser argparser {{
//...
      {
        {"-s", "--sep"},
        "separator (default ',')", 1},
//...
    }};
//...
  auto sep = args["--sep"].as<string>(",");
  // or alternatively...
  //auto sep = args["-s"].as<string>(",");
//...

The option name isn't really used for anything else.

Method of using defaut argument still fails

Hi,

I've been trying to use the method you describe to supply a default option for a flag; however, I cannot seem to get it to work. I do just as you suggest:

int32_t mode = args["mode"].as<int32_t>(1234);

If I say an arg has one parameter, but then don't supply it on the command line, the program fails with:

Encountered exception while parsing arguments: last option "--def-opt"
expects an argument but the parser ran out of command line arguments

(Here, even your sample1 program seems to have the problem)

Am I doing something wrong?

Thanks!

Add a CMake interface target

This would allow using the library by using target_link_libraries instead of having to copy files around or setting include paths manually. It's useful when using git submodules or CMake's FetchContent to download dependencies.

I've created a pull request to implement this (#33).

Support for sinks of unparsed arguments

I would like to have a way to tell argagg to forward unknown arguments to a generic sink, so that unrecognized arguments can be processed by other means. Similar to LLVM's Sink which tells the parser that an option eats all unrecognized args.

Add global order of options

Expand the data structure so that a user can walk through all option results in the order of parse. This will likely entail a back reference from the option result to the parent option definition.

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.