Skip to content

Latest commit

 

History

History
160 lines (114 loc) · 6.74 KB

README.md

File metadata and controls

160 lines (114 loc) · 6.74 KB

C++ argparse

Build argparse Version Codecov Standard License

Overview

This is a C++ implementation of Python's argparse module. The aim is to cover as much functionality as possible and retain argparse's familiarity and ease of use. Everyone who already used Python should feel at home using this library. At the same time, the library is implemented using modern C++ features.

Dependencies

C++ argparse is a header-only library, so its setup is minimal. It has no external dependencies and only uses STL. Since it uses std::ranges and some other features, it requires C++20 compiler and standard library. If your compiler struggles, you can switch to v2.2.0, or if you are stuck with C++17, you can still use release v2.1.4.

C++ argparse uses CMake internally, but you don't have to. Just put the argparse.h header somewhere and point your build system to it.

Unit tests use doctest unit testing framework.

Design considerations

This library strives to provide Python-like arguments parsing experience for C++ developers. However, where Python and C++ clash, this implementation goes the C++ way using its idioms.

Named parameters

One obvious example is the lack of C++ support for named function parameters. While in Python you can do

parser = argparse.ArgumentParser(prog='app', usage='%(prog)s', description='Showcase app')

in C++ you need something else. C++ argparse uses Named Parameter Idiom to achieve similar functionality:

auto parser = argparse::ArgumentParser().prog("app").usage("%(prog)s").description("Showcase app");

It is a bit more verbose and a bit less convenient, but I believe it is still usable.

Dynamic attribute creation

The parse_args() function adds attributes representing parsed arguments to an instance of a Namespace class at runtime. In Python creating custom types at runtime is not a problem. Not so in C++. Instead of returning a bespoke class instance, C++ argparse returns a mapping object that uses argument names as keys. This is not as convenient, but not too bad either.

Type-safety

Python does not have any static type-safety built in and instead depends on runtime checks. Therefore it is fine to pass some things as strings:

parser.add_argument('--option', action='store_true')

In C++ we can ask the compiler to help us detect bugs at compile-time, and this is what C++ argparse does. Instead of passing actions as strings, we use enums here:

parser.add_argument("--option").action(argparse::store_true);

If you make a typo, your compiler will let you know.

Typing

Python is a dynamically typed language while C++ is statically typed. This means that while in Python a function can return any type

def fun(arg):
   if not arg:
       return None
   elif arg == 10:
       return 'ten'
   else:
       return 42

in C++ we need a workaround. The workaroud used here is template-based. To get a parsed value, you need to specify which type you expect:

auto args = parser.parse_args(argc, argv);
auto force = args.get_value<bool>("force");

The only exception is std::string which is returned by default by the non-template function overload:

auto args = parser.parse_args(argc, argv);
auto file_name = args.get_value("filename");

Usage

You can follow the tutorial in the project's tutorial subdirectory to learn how to use this library. A short example follows below:

#include "argparse.h"


int move_file(std::string const & src, std::string const & dst, bool force, bool verbose);

int main(int argc, char* argv[])
{
    auto parser = argparse::ArgumentParser();
    parser.add_argument("source").help("source file path");
    parser.add_argument("destination").help("destination file path");
    parser.add_argument("-f", "--force").help("force copying").action(argparse::store_true);
    parser.add_argument("-v", "--verbose").help("prints additional information").action(argparse::store_true);

    auto args = parser.parse_args(argc, argv);

    auto result = move_file(args.get_value("source"), args.get_value("destination"), args.get_value<bool>("force"), args.get_value<bool>("verbose"));

    return result;
}

Supported features

The below lists features of the argparse module that this implementation supports:

  • ArgumentParser objects

    • prog
    • usage
    • description
    • epilog
    • parents
    • formatter_class
    • prefix_chars
    • fromfile_prefix_chars
    • argument_default
    • allow_abbrev
    • conflict_handler
    • add_help
  • The add_argument() method

    • name or flags
    • action (only store, store_true, store_false, store_const, and help)
    • nargs (except for REMAINDER)
    • const (renamed to const_ due to keyword clash)
    • default (renamed to default_ due to keyword clash; only for optional arguments and with no string parsing)
    • type (built-in (except for bool) and user-defined types (via overloading from_string function))
    • choices
    • required
    • help
    • metavar (only for single nargs)
    • dest
  • The parse_args() method

    • no defaults, you need to pass argc and argv explicitly (normally, forward what you got in main)
    • option value syntax
      • passing long option and value as a single command-line argument (--foo=FOO)
      • passing short option and value concatenated (-xX)
      • joining together several short options (-xyz)
      • double-dash pseudo-argument (--)
      • argument abbreviations (prefix matching)
  • Mutual exclusion

    • you can put optional arguments in a mutually exclusive group to have only one of them accepted by the parser
    • the group itself (together with its arguments) is optional

License

This project is released under MIT license, so feel free to do anything you like with it.