Giter Site home page Giter Site logo

argumentum's Introduction

Build Status Language grade: C/C++

Argumentum / Argparse

Argumentum is a C++ library for writing command-line program interfaces. The arguments that a program supports are registered in an instance of arument_parser, the main library class. argument_parser processes the input arguments, checks that they are valid and converts them to C++ variables. It also generates help and usage messages when requested.

The parsed values are stored in normal C++ variables. Support for numeric types, std::string, std::vector and std::optional is built into the parser. Any type that can be converted from string with a constructor or an assignment operator can also be used. Other types of values are supported with parsing actions. The library requires a C++17 compiler.

The library is loosely based on the Python argparse module. It covers most functionality of Python argparse.

Building

The library can be built as a static library or used as header-only. See Building and consuming the library.

A basic example

In this example the program accepts integers and finds the largest one. If the option --sum is passed to the program, the numbers are summed, instead. (Compare with Python argparse.)

#include <climits>
#include <argumentum/argparse.h>
#include <numeric>
#include <vector>

using namespace std;
using namespace argumentum;

int main( int argc, char** argv )
{
   vector<int> numbers;
   bool isSum = false;

   auto parser = argument_parser{};
   parser.config().program( argv[0] ).description( "Accumulator" );
   parser.add_argument( numbers, "N" ).minargs( 1 ).metavar( "INT" ).help( "Integers" );
   parser.add_argument( isSum, "--sum", "-s" )
         .nargs( 0 )
         .help( "Sum the integers (default: find the max)" );

   if ( !parser.parse_args( argc, argv, 1 ) )
      return 1;

   auto mmax = []( auto&& a, auto&& b ) { return max( a, b ); };
   auto acc = isSum ? accumulate( numbers.begin(), numbers.end(), 0 )
                    : accumulate( numbers.begin(), numbers.end(), INT_MIN, mmax );
   cout << acc << "\n";
   return 0;
}

Target values

The parser parses input strings and stores the parsed results in target values which are ordinary C++ variables. The target values must outlive the parser because the parser stores target value references. On the other hand, the parser can be destroyed after parsing is done.

The supported types of target values are:

  • C++ numeric types, bool, std::string,
  • any type that has a constructor that accepts std::string,
  • any type that has an operator= that accepts std::string,
  • any type T for which a converter argumentum::from_string<T>::convert exists,
  • std::vector of simple target values.

If information about whether a value was set or not is needed, std::optional can be used:

   std::optional<std::string> str;
   parser.add_argument( str, "-s" ).maxargs( 1 );

   // ... parser.parse_args ...

   if ( !str )
      std::cout << "The option -s was not used.\n";
   else
      std::cout << "The option -s was set with value '" << *str << "' .\n";

Additional types can be supported through parsing actions like in the examples below.

Variants of the basic example

The same example implemented with an action and a default value:

#include <climits>
#include <argumentum/argparse.h>
#include <numeric>
#include <vector>

using namespace std;
using namespace argumentum;

int main( int argc, char** argv )
{
   vector<int> numbers;
   auto max = []( int a, int b ) { return std::max( a, b ); };
   auto sum = []( int a, int b ) { return a + b; };
   std::pair<std::function<int( int, int )>, int> operation;

   auto parser = argument_parser{};
   parser.config().program( argv[0] ).description( "Accumulator" );
   parser.add_argument( numbers, "N" ).minargs( 1 ).metavar( "INT" ).help( "Integers" );
   parser.add_argument( operation, "--sum", "-s" )
         .nargs( 0 )
         .absent( std::make_pair( max, INT_MIN ) )
         .action( [&]( auto& target, const std::string& value ) {
            target = std::make_pair( sum, 0 );
         } )
         .help( "Sum the integers (default: find the max)" );

   if ( !parser.parse_args( argc, argv, 1 ) )
      return 1;

   auto acc = accumulate( numbers.begin(), numbers.end(), operation.second, operation.first );
   cout << acc << "\n";
   return 0;
}

In simple programs target variables can be declared in the function where the arguments are defined and parsed. In larger programs it is more convenient to store variables in one or more structures. This is the same example rewritten so that it stores options in a structure:

#include <climits>
#include <argumentum/argparse.h>
#include <numeric>
#include <vector>

using namespace std;
using namespace argumentum;

class AccumulatorOptions : public argumentum::Options
{
public:
   vector<int> numbers;
   std::pair<std::function<int( int, int )>, int> operation;

protected:
   void add_arguments( argument_parser& parser ) override
   {
      auto max = []( int a, int b ) { return std::max( a, b ); };
      auto sum = []( int a, int b ) { return a + b; };

      parser.add_argument( numbers, "N" ).minargs( 1 ).metavar( "INT" ).help( "Integers" );
      parser.add_argument( operation, "--sum", "-s" )
            .nargs( 0 )
            .absent( std::make_pair( max, INT_MIN ) )
            .action( [&]( auto& target, const std::string& value ) {
               target = std::make_pair( sum, 0 );
            } )
            .help( "Sum the integers (default: find the max)" );
   }
};

void execute( AccumulatorOptions& opt )
{
   auto acc = accumulate(
         opt.numbers.begin(), opt.numbers.end(), opt.operation.second, opt.operation.first );
   cout << acc << "\n";
}

int main( int argc, char** argv )
{
   auto parser = argument_parser{};
   parser.config().program( argv[0] ).description( "Accumulator" );

   auto pOptions = std::make_shared<AccumulatorOptions>();
   parser.add_arguments( pOptions );

   if ( !parser.parse_args( argc, argv, 1 ) )
      return 1;

   execute( *pOptions );
   return 0;
}

When a program becomes even more complex it can be subdivided into commands that often act as independent programs. We can rewrite the above example with commands. The main change is that the class AccumulatorOptions is now derived from CommandOptions which has the method execute that we use to execute the selected command.

Note: the interface for defining and executing commands is not stable and will change in the future.

#include <climits>
#include <argumentum/argparse.h>
#include <numeric>
#include <vector>

using namespace std;
using namespace argumentum;

class AccumulatorOptions : public argumentum::CommandOptions
{
public:
   vector<int> numbers;
   std::pair<std::function<int( int, int )>, int> operation;

   void execute( const ParseResults& res )
   {
      auto acc = accumulate( 
         numbers.begin(), numbers.end(), operation.second, operation.first );
      cout << acc << "\n";
   }

protected:
   void add_arguments( argument_parser& parser ) override
   {
     // ... same as above
   }
};

class CmdEchoOptions : public argumentum::CommandOptions
{
public:
   vector<int> numbers;

public:
   void execute( const ParseResult& res ) override
   {
      for ( auto n : numbers )
         cout << n << " ";
      cout << "\n";
   }

protected:
   void add_arguments( argument_parser& parser ) override
   {
      parser.add_argument( numbers, "N" ).minargs( 1 ).metavar( "INT" ).help( "Integers" );
   };
};


int main( int argc, char** argv )
{
   auto parser = argument_parser{};
   parser.config().program( argv[0] ).description( "Accumulator" );
   parser.add_command<CmdAccumulatorOptions>( "fold" ).help( "Accumulate integer values." );
   parser.add_command<CmdEchoOptions>( "echo" ).help( "Echo integers from the command line." );

   auto res = parser.parse_args( argc, argv, 1 );
   if ( !res )
      return 1;

   auto pcmd = res.commands.back();
   if ( !pcmd )
      return 1;

   pcmd->execute( res );
   return 0;
}

Command options are instantiated only when the appropriate command is selected with an argument. The chain of instantiated subcommands is stored in ParseResults::commands. Typically we execute only the last instantiated (the "deepest") subcommand.

argumentum's People

Contributors

mmahnic avatar

Watchers

 avatar

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.