this is basically a design document version - it sets the scenery for upcoming additions

This commit is contained in:
Hartmut Seichter 2018-04-02 01:06:50 +02:00
parent 32aac45162
commit 550d27273f
1504 changed files with 1051518 additions and 127 deletions

2
src/deps/arrrgh/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.DS_Store
a.out

128
src/deps/arrrgh/README.md Normal file
View file

@ -0,0 +1,128 @@
Arrrgh
======
Jeff Wofford | July 2014 | e: biz@jeffwofford.com | w: http://www.jeffwofford.com
**arrrgh** is a fast, small, simple, powerful, single-header library for parsing command line arguments in C++ using more-or-less POSIX parsing rules. It is written using modern C++11 paradigms so it's safe, fast, and tends to invite clean, small, to-the-point code.
There are lots of other command line parsers out there. Yet every time I wrote a new command line program, I ended up rolling my own underpowered and error-prone argument parsing. I like [TCLAP](http://tclap.sourceforge.net/) but it's a full library with makefiles, lots of source and headers, and the rest. Likewise for getopt, boost, and others: the command line parsing code can end up vastly outweighing the program it's meant to serve. Plus, integrating headers and libraries into C/C++ programs is never a lot of fun, especially when porting to different IDEs and compilers. [The Lean Mean Option Parser](http://optionparser.sourceforge.net/index.html) is nice and small but I didn't prefer its style. You can disagree.
I wrote **arrrgh** to escape the madness. It's a single file. You copy it into your project. You include it. You set up the arguments you want to support. It does the rest.
# To Use
1. Include the header:
#include "arrrgh.hpp"
There's no .lib or .so or .cpp or anything else to muck around with. It's all here.
2. Create a parser object and give it your program name and description (for the "usage" output):
arrrgh::parser parser( "<my-prog>", "<description>" );
3. Add your arguments, templated on the desired type. Example:
const auto& myArgument = parser.add< float >( ... );
4. In the add() function you'll indicate the long-form ("--example") and short-form ("-e") switches for this
argument. You can nullify one or the other if you want.
You should specify a description (the second argument).
You may also specify whether the argument is optional or required, and what the default value is (in case
the user doesn't supply it):
... parser.add< float >(
"<long-form>", // Use "example" if you want the user to say "--example",
// or leave blank for no long-form.
"<description>", // For "usage" output.
'<short-form>', // A character. Use '\0' for no short-form.
[arrrgh::Optional|arrrgh::Required], // To indicate whether it's required.
[default-value] // The default value. Defaults to 0 or its equivalent for this type.
);
5. Let 'er rip:
parser.parse( argc, argv );
Use try...catch... if you want to catch problems in a healthy way.
6. Access argument values:
myArgument.value() // Returns a float if we templated on <float>.
try...catch... helps here too because users may give invalid strings for non-string types.
7. Call parser.show_usage() if you want to print help text.
8. Do other stuff. See "Example usage" below for "unlabeled" arguments, the "--" marker, and such.
# Example Usage
#include "arrrgh.hpp"
int main( int argc, const char* argv[] )
{
arrrgh::parser parser( "arrrghsample", "Parses a mix of many argument types and combinations." );
const auto& useAscii = parser.add< bool >( "ascii",
"Use ASCII instead of that UNICORN thing or whatever it is.",
'a',
arrrgh::Optional,
true /* defaults to true */ );
const auto& runFast = parser.add< bool >( "fast", "Should this program run fast?", 'f' );
const auto& doCalibrate = parser.add< bool >( "calibrate", "Calibrate sensors." /* no short-form */ );
const auto& kindaOdd = parser.add< bool >( "", "No long-form argument for this one.", 'o' );
const auto& mass = parser.add< float >( "mass", "The mass of the thing.", 'm', arrrgh::Optional, 3.141f );
const auto& mana = parser.add< float >( "mana", "The mana of the thing.", 'M' );
const auto& height = parser.add< int >( "height", "The height of the thing.", 'h', arrrgh::Required );
const auto& name = parser.add< std::string >( "name", "The name of the wind.", 's', arrrgh::Required );
// Unleash the hounds.
//
try
{
// Example command line:
// arrrghsample --height=16.25 -fo unlabeled --name="Absolom, Absolom" -h=8 -- --weirdly-unlabeled
parser.parse( argc, argv );
}
catch( const std::exception& e )
{
std::cerr << "Error parsing arguments: " << e.what() << std::endl;
parser.show_usage( std::cerr );
exit( 1 );
}
// Get argument values.
//
try
{
std::cout << std::boolalpha; // So that bool values come through as "true/false" rather than "1/0".
std::cout << "useAscii=" << useAscii.value() << std::endl;
std::cout << "runFast=" << runFast.value() << std::endl;
std::cout << "doCalibrate=" << doCalibrate.value() << std::endl;
std::cout << "kindaOdd=" << kindaOdd.value() << std::endl;
std::cout << "mass=" << mass.value() << std::endl;
std::cout << "mana=" << mana.value() << std::endl;
std::cout << "height=" << height.value() << std::endl;
std::cout << "name=" << name.value() << std::endl;
// What about unlabeled arguments?
//
// Notice that "--weirdly-unlabeled" looks like a switch but comes through nicely as unlabeled.
// That's because the example command line indicated "--" before it, which ends switch parsing
// (everything else becomes unlabeled).
//
std::cout << "Unlabeled:\n";
parser.each_unlabeled_argument( []( const std::string& arg ) { std::cout << "\t" << arg << std::endl; } );
}
catch( const std::exception& e )
{
std::cerr << "Error reading argument values: " << e.what() << std::endl;
}
return 0;
}
# License
Released under the MIT license. See arrrgh.hpp.

776
src/deps/arrrgh/arrrgh.hpp Normal file
View file

@ -0,0 +1,776 @@
#ifndef arrrgh_hpp_included
#define arrrgh_hpp_included
//
// arrrgh.hpp
//
// Created by Jeff Wofford on 7/2/14.
// Copyright (c) 2014 Jeff Wofford.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// **arrrgh** is a fast, small, simple, powerful, single-header library for parsing command line arguments in C++
// using more-or-less POSIX parsing rules.
//
// To use:
//
// 1. Include the header:
//
// #include "arrrgh.hpp"
//
// There's no .lib or .so or .cpp or anything else to muck around with. It's all here.
//
// 2. Create a parser object and give it your program name and description (for the "usage" output):
//
// arrrgh::parser parser( "<my-prog>", "<description>" );
//
// 3. Add your arguments, templated on the desired type. Example:
//
// const auto& myArgument = parser.add< float >( ... );
//
// 4. In the add() function you'll indicate the long-form ("--example") and short-form ("-e") switches for this
// argument. You can nullify one or the other if you want.
// You should specify a description (the second argument).
// You may also specify whether the argument is optional or required, and what the default value is (the value
// to be used if the user doesn't supply it):
//
// ... parser.add< float >(
// "<long-form>", // Use "example" if you want the user to say "--example",
// // or leave blank for no long-form.
// "<description>", // For "usage" output.
// '<short-form>', // A character. Use '\0' for no short-form.
// [arrrgh::Optional|arrrgh::Required], // To indicate whether it's required.
// [default-value] // The default value. Defaults to 0 or its equivalent for this type.
// );
//
// 5. Let 'er rip:
//
// parser.parse( argc, argv );
//
// Use try...catch... if you want to catch problems in a healthy way.
//
// 6. Access argument values:
//
// myArgument.value() // Returns a float if we templated on <float>.
//
// try...catch... helps here too because users may give invalid strings for non-string types.
//
// 7. Call parser.show_usage() if you want to print help text.
//
// 8. Do other stuff. See "Example usage" below for "unlabeled" arguments, the "--" marker, and such.
//
// Example usage:
//
// ------------------------------------------------------------------------------------------------------------------
//
// #include "arrrgh.hpp"
//
// int main( int argc, const char* argv[] )
// {
// arrrgh::parser parser( "arrrghsample", "Parses a mix of many argument types and combinations." );
//
// const auto& useAscii = parser.add< bool >( "ascii",
// "Use ASCII instead of that UNICORN thing or whatever it is.",
// 'a',
// arrrgh::Optional,
// true /* defaults to true */ );
// const auto& runFast = parser.add< bool >( "fast", "Should this program run fast?", 'f' );
// const auto& doCalibrate = parser.add< bool >( "calibrate", "Calibrate sensors." /* no short-form */ );
// const auto& kindaOdd = parser.add< bool >( "", "No long-form argument for this one.", 'o' );
// const auto& mass = parser.add< float >( "mass", "The mass of the thing.", 'm', arrrgh::Optional, 3.141f );
// const auto& mana = parser.add< float >( "mana", "The mana of the thing.", 'M' );
// const auto& height = parser.add< int >( "height", "The height of the thing.", 'h', arrrgh::Required );
// const auto& name = parser.add< std::string >( "name", "The name of the wind.", 's', arrrgh::Required );
//
// // Unleash the hounds.
// //
// try
// {
// // Example command line:
// // arrrghsample --height=16.25 -fo unlabeled --name="Absolom, Absolom" -h=8 -- --weirdly-unlabeled
// parser.parse( argc, argv );
// }
// catch( const std::exception& e )
// {
// std::cerr << "Error parsing arguments: " << e.what() << std::endl;
// parser.show_usage( std::cerr );
// exit( 1 );
// }
//
// // Get argument values.
// //
// try
// {
// std::cout << std::boolalpha; // So that bool values come through as "true/false" rather than "1/0".
// std::cout << "useAscii=" << useAscii.value() << std::endl;
// std::cout << "runFast=" << runFast.value() << std::endl;
// std::cout << "doCalibrate=" << doCalibrate.value() << std::endl;
// std::cout << "kindaOdd=" << kindaOdd.value() << std::endl;
// std::cout << "mass=" << mass.value() << std::endl;
// std::cout << "mana=" << mana.value() << std::endl;
// std::cout << "height=" << height.value() << std::endl;
// std::cout << "name=" << name.value() << std::endl;
//
// // What about unlabeled arguments?
// //
// // Notice that "--weirdly-unlabeled" looks like a switch but comes through nicely as unlabeled.
// // That's because the example command line indicated "--" before it, which ends switch parsing
// // (everything else becomes unlabeled).
// //
// std::cout << "Unlabeled:\n";
// parser.each_unlabeled_argument( []( const std::string& arg ) { std::cout << "\t" << arg << std::endl; } );
// }
// catch( const std::exception& e )
// {
// std::cerr << "Error reading argument values: " << e.what() << std::endl;
// }
//
// return 0;
// }
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <cassert>
#include <cctype>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <vector>
namespace arrrgh
{
// Utility functions and macros.
//
template< typename Function >
std::string collect_string( Function&& fn )
{
return fn();
}
#define arrrgh_collect_string( expression ) \
arrrgh::collect_string( [&]() { std::ostringstream stream; stream << expression; return stream.str(); } )
#define ARRRGH_EXCEPTION( exception_class ) \
struct exception_class : public std::runtime_error { using runtime_error::runtime_error; };
enum Requirement
{
Optional,
Required
};
template< typename ValueT >
struct type_traits
{
static constexpr bool always_requires_value() { return false; }
static constexpr const char* name();
};
// argument classes.
//
class argument_abstract
{
public:
ARRRGH_EXCEPTION( MissingValue )
ARRRGH_EXCEPTION( Nameless )
std::string best_name() const
{
if( m_longForm.empty() )
{
return std::string{ m_letter };
}
else
{
return m_longForm;
}
}
bool assigned() const
{
return m_assigned;
}
const std::string& value_string() const
{
return m_value;
}
void clear_value()
{
m_assigned = false;
m_value.clear();
}
// FOR TESTING.
// Gets the string value of the argument as a result of its conversion to the
// templated value_t for that particular argument subclass.
//
virtual std::string converted_value_string() const = 0;
virtual bool has_default_value() const = 0;
protected:
bool m_assigned;
explicit argument_abstract( const std::string& longForm,
const std::string& explanation,
char letter,
Requirement required )
: m_assigned( false )
, m_longForm( longForm )
, m_explanation( explanation )
, m_letter( letter )
, m_requirement( required )
{
assert( !m_longForm.empty() || m_letter != '\0' ); // Gotta specify at least one.
assert( m_longForm.empty() || m_longForm[ 0 ] != '-' ); // Don't start your switch names with -.
assert( m_letter == '\0' || is_valid_short_form( m_letter )); // Has to be valid or nothing.
}
bool has_long_form( const std::string& longForm ) const
{
return !m_longForm.empty() && m_longForm == longForm;
}
bool has_short_form( char shortForm ) const
{
return m_letter != '\0' && m_letter == shortForm;
}
bool required() const
{
return m_requirement == Required;
}
virtual bool required_value() const = 0;
void assign( const std::string& givenKey, std::string&& valueString )
{
m_assigned = true;
m_value = std::move( valueString );
if( m_value.empty() && required_value() )
{
throw MissingValue{ arrrgh_collect_string( "Argument required a value but received none." ) };
}
}
void print( std::ostream& out ) const
{
out << " ";
bool hasLetter = m_letter != '\0';
if( hasLetter )
{
out << "-" << m_letter;
}
bool hasLong = !m_longForm.empty();
if( hasLetter && hasLong )
{
out << ", ";
}
if( hasLong )
{
out << "--" << m_longForm;
}
if( required_value() )
{
out << "=<" << value_type_name() << ">";
}
out << "\n\t\t";
if( m_requirement == Required )
{
out << "[required] ";
}
out << m_explanation;
}
static bool is_valid_short_form( char c )
{
return std::isalpha( c );
}
virtual std::string value_type_name() const = 0;
private:
std::string m_longForm;
std::string m_explanation;
char m_letter;
Requirement m_requirement;
std::string m_value;
friend class parser;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template< typename ValueType >
class argument : public argument_abstract
{
public:
typedef ValueType value_t;
// Exception types.
//
ARRRGH_EXCEPTION( ValueConversionError )
operator value_t() const
{
value_t result = m_defaultValue;
if( m_assigned )
{
std::istringstream stream( value_string() );
stream >> std::boolalpha >> result;
if( stream.fail() )
{
throw ValueConversionError{
arrrgh_collect_string( "Could not convert value '" << value_string()
<< "' to the desired argument type." )};
}
}
return result;
}
value_t value() const { return operator value_t(); }
private:
value_t m_defaultValue = value_t{};
explicit argument( const std::string& longForm,
const std::string& explanation,
char letter,
Requirement required,
const value_t& defaultValue )
: argument_abstract( longForm, explanation, letter, required )
, m_defaultValue( defaultValue )
{}
virtual std::string value_type_name() const override
{
return type_traits< value_t >::name();
}
virtual std::string converted_value_string() const override
{
std::ostringstream stream;
stream << std::boolalpha << value();
return stream.str();
}
virtual bool has_default_value() const override
{
return value() == m_defaultValue;
}
virtual bool required_value() const override
{
return type_traits< value_t >::always_requires_value();
}
friend class parser;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class parser
{
public:
// Exception classes.
//
ARRRGH_EXCEPTION( InvalidParameters )
ARRRGH_EXCEPTION( UnknownArgument )
ARRRGH_EXCEPTION( FoundDoubleHyphensLeadingNowhere )
ARRRGH_EXCEPTION( InvalidArgumentCharacter )
ARRRGH_EXCEPTION( MissingRequiredArguments )
explicit parser( const std::string& programName, const std::string& programDescription )
: m_program( programName )
, m_description( programDescription )
{}
template< typename ValueT >
argument< ValueT >& add( const std::string& longForm,
const std::string& explanation,
char letter = '\0',
Requirement required = Optional,
const ValueT& defaultValue = ValueT{} )
{
// Verify that no other argument has this longForm or letter.
//
assert( !has_long_form_argument( longForm ));
assert( !has_letter_argument( letter ));
argument< ValueT >* arg = new argument< ValueT >{
longForm,
explanation,
letter,
required,
defaultValue };
m_arguments.emplace_back( arg );
return *arg;
}
bool has_long_form_argument( const std::string& longForm ) const
{
return std::any_of( m_arguments.begin(), m_arguments.end(),
[&]( const std::unique_ptr< argument_abstract >& arg )
{
assert( arg );
return arg->has_long_form( longForm );
} );
}
bool has_letter_argument( char letter ) const
{
return std::any_of( m_arguments.begin(), m_arguments.end(),
[&]( const std::unique_ptr< argument_abstract >& arg )
{
assert( arg );
return arg->has_short_form( letter );
} );
}
void parse( const int argc, const char* argv[] )
{
if( argc == 0 || !argv )
{
throw InvalidParameters{ "Received no arguments." };
}
// If there's no help argument, add one.
//
if( !has_long_form_argument( "help" ))
{
add< bool >( "help",
"Prints this help message.",
!has_letter_argument( 'h' ) ? 'h' : '\0' );
}
auto iterHelpArg = std::find_if( m_arguments.begin(), m_arguments.end(), []( const std::unique_ptr< argument_abstract >& arg )
{
return arg->has_long_form( "help" );
} );
assert( m_arguments.end() != iterHelpArg );
const argument< bool >& helpArg = *static_cast< const argument< bool >* >( iterHelpArg->get() );
m_programExecutionPath = argv[ 0 ];
bool doneWithSwitches = false; // When false, still looking for switches. When true, all arguments
// are considered "unlabeled."
for( int i = 1; i < argc; ++i )
{
assert( argv[ i ] );
const std::string arg{ argv[ i ] };
assert( !arg.empty() );
// Determine which configured argument corresponds to this program argument, if any,
// and parse any value it might have.
// Do we have a leading hyphen?
//
if( !doneWithSwitches && arg[ 0 ] == '-' )
{
// Yes we do.
// Do we have two?
//
if( arg.size() > 1 && arg[ 1 ] == '-' )
{
// Yes. So we're expecting a long-form argument.
// Read to the end or to the = sign.
//
const auto keyEnd = arg.find_first_of( '=' );
const auto key = arg.substr( 2, keyEnd - 2 );
std::string value;
if( keyEnd < arg.size() )
{
value = arg.substr( keyEnd + 1 );
}
if( key.empty() )
{
// This is either "--" or, more troublingly, "--=..."
// Either way, don't look for arguments any more:
// anything else is "unlabeled."
//
doneWithSwitches = true;
continue;
}
// Find this argument.
//
auto& argument = find_matching_argument( key, true /* long form */ );
process_argument( argument, arg, std::move( value ));
}
else
{
// No we don't. Just one.
// Consider each following letter to be a short-form argument letter.
//
for( size_t i = 1; i < arg.size(); ++i )
{
const char c = arg[ i ];
// Is this a reasonable argument character?
//
if( argument_abstract::is_valid_short_form( c ))
{
// This is a legitimate argument.
// Does it have a value?
//
std::string value;
bool hasAssignment = i + 1 < arg.size() && arg[ i + 1 ] == '=';
if( hasAssignment )
{
// Looks like it. Read it.
//
value = arg.substr( i + 2 );
}
auto& argument = find_matching_argument( std::string{ c }, false /* short form */ );
process_argument( argument, arg, std::move( value ));
if( hasAssignment )
{
break;
}
}
else
{
throw InvalidArgumentCharacter{
arrrgh_collect_string( "Invalid argument with character '" << c << "'." ) };
}
}
}
}
else
{
// No we don't. This is an unlabeled argument.
//
m_unlabeledArguments.emplace_back( std::move( arg ));
}
}
// Did each of the *required* arguments get assigned?
//
if( std::any_of( m_arguments.begin(), m_arguments.end(),
[&]( const std::unique_ptr< argument_abstract >& arg )
{
assert( arg );
return !arg->assigned() && arg->required();
} ))
{
throw MissingRequiredArguments{ "Some required arguments were missing." };
}
// Did our help argument get set?
//
if( helpArg.value() )
{
show_usage();
}
}
void show_usage( std::ostream& out = std::cout ) const
{
out << m_program << ": " << m_description << std::endl;
out << "usage: " << m_program << std::endl;
for( const auto& arg : m_arguments )
{
assert( arg );
arg->print( out );
out << std::endl;
}
out << std::endl;
}
template< typename Function >
void each_argument( Function&& fn ) const
{
std::for_each( m_arguments.begin(), m_arguments.end(),
[&]( const std::unique_ptr< argument_abstract >& arg )
{
assert( arg );
fn( *arg );
} );
}
template< typename Function >
void each_unlabeled_argument( Function&& fn ) const
{
std::for_each( m_unlabeledArguments.begin(), m_unlabeledArguments.end(), fn );
}
void clear_values()
{
std::for_each( m_arguments.begin(), m_arguments.end(),
[]( const std::unique_ptr< argument_abstract >& arg )
{
assert( arg );
arg->clear_value();
} );
m_unlabeledArguments.clear();
}
protected:
argument_abstract& find_matching_argument( const std::string& argString, bool longForm )
{
auto iterFound = std::find_if( m_arguments.begin(), m_arguments.end(),
[&]( const std::unique_ptr< argument_abstract >& arg )
{
assert( arg );
if( longForm )
{
return arg->has_long_form( argString );
}
else
{
return arg->has_short_form( argString[ 0 ] );
}
} );
if( iterFound != m_arguments.end() )
{
return **iterFound;
}
else
{
throw UnknownArgument{ arrrgh_collect_string( "Unrecognized argument \"-" << ( longForm ? "-" : "" )
<< argString << "\"." ) };
}
}
void process_argument( argument_abstract& argument, const std::string& key, std::string&& value )
{
argument.assign( key, std::move( value ));
}
private:
std::string m_program;
std::string m_description;
std::string m_programExecutionPath;
std::vector< std::unique_ptr< argument_abstract >> m_arguments;
std::vector< std::string > m_unlabeledArguments;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Specializations
//
template<>
inline argument< bool >::operator bool() const
{
return m_assigned || m_defaultValue;
}
template<>
inline argument< std::string >::operator std::string() const
{
return m_assigned ? value_string() : m_defaultValue;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// arrrgh::type_traits
//
template<>
struct type_traits< bool >
{
static constexpr bool always_requires_value() { return false; }
static constexpr const char* name() { return "bool"; }
};
template<>
struct type_traits< std::string >
{
static constexpr bool always_requires_value() { return true; }
static constexpr const char* name() { return "string"; }
};
template<>
struct type_traits< float >
{
static constexpr bool always_requires_value() { return true; }
static constexpr const char* name() { return "number"; }
};
template<>
struct type_traits< double >
{
static constexpr bool always_requires_value() { return true; }
static constexpr const char* name() { return "number"; }
};
template<>
struct type_traits< int >
{
static constexpr bool always_requires_value() { return true; }
static constexpr const char* name() { return "int"; }
};
template<>
struct type_traits< size_t >
{
static constexpr bool always_requires_value() { return true; }
static constexpr const char* name() { return "size_t"; }
};
#undef arrrgh_collect_string
#undef ARRRGH_EXCEPTION
}
#endif

View file

@ -0,0 +1,66 @@
#include "arrrgh.hpp"
int main( int argc, const char* argv[] )
{
arrrgh::parser parser( "arrrghsample", "Parses a mix of many argument types and combinations." );
const auto& useAscii = parser.add< bool >( "ascii",
"Use ASCII instead of that UNICORN thing or whatever it is.",
'a',
arrrgh::Optional,
true /* defaults to true */ );
const auto& runFast = parser.add< bool >( "fast", "Should this program run fast?", 'f' );
const auto& doCalibrate = parser.add< bool >( "calibrate", "Calibrate sensors." /* no short-form */ );
const auto& kindaOdd = parser.add< bool >( "", "No long-form argument for this one.", 'o' );
const auto& mass = parser.add< float >( "mass", "The mass of the thing.", 'm', arrrgh::Optional, 3.141f );
const auto& mana = parser.add< float >( "mana", "The mana of the thing.", 'M' );
const auto& height = parser.add< int >( "height", "The height of the thing.", 'h', arrrgh::Required );
const auto& name = parser.add< std::string >( "name", "The name of the wind.", 's', arrrgh::Required );
// Unleash the hounds.
//
try
{
// Example command line:
// arrrghsample --height=16.25 -fo unlabeled --name="Absolom, Absolom" -h=8 -- --weirdly-unlabeled
parser.parse( argc, argv );
}
catch( const std::exception& e )
{
std::cerr << "Error parsing arguments: " << e.what() << std::endl;
parser.show_usage( std::cerr );
exit( 1 );
}
parser.show_usage( std::cerr );
// Get argument values.
//
try
{
std::cout << std::boolalpha; // So that bool values come through as "true/false" rather than "1/0".
std::cout << "useAscii=" << useAscii.value() << std::endl;
std::cout << "runFast=" << runFast.value() << std::endl;
std::cout << "doCalibrate=" << doCalibrate.value() << std::endl;
std::cout << "kindaOdd=" << kindaOdd.value() << std::endl;
std::cout << "mass=" << mass.value() << std::endl;
std::cout << "mana=" << mana.value() << std::endl;
std::cout << "height=" << height.value() << std::endl;
std::cout << "name=" << name.value() << std::endl;
// What about unlabeled arguments?
//
// Notice that "--weirdly-unlabeled" looks like a switch but comes through nicely as unlabeled.
// That's because the example command line indicated "--" before it, which ends switch parsing
// (everything afterward becomes unlabeled).
//
std::cout << "Unlabeled:\n";
parser.each_unlabeled_argument( []( const std::string& arg ) { std::cout << "\t" << arg << std::endl; } );
}
catch( const std::exception& e )
{
std::cerr << "Error reading argument values: " << e.what() << std::endl;
}
return 0;
}

View file

@ -0,0 +1,455 @@
//
// main.cpp
//
// Created by Jeff Wofford on 7/2/14.
// Copyright (c) 2014 Jeff Wofford. All rights reserved.
//
#include "arrrgh.hpp"
#include <iostream>
namespace
{
const bool VERBOSE = true;
struct program
{
program( const std::string& program, const std::string& description )
: m_name( program )
, m_parser( program, description )
{}
const std::string& name() const { return m_name; }
arrrgh::parser& parser() { return m_parser; }
bool test( int argc, const char* argv[], bool expectFailure )
{
m_parser.clear_values();
try
{
m_parser.parse( argc, argv );
}
catch( const std::exception& e )
{
if( VERBOSE )
{
std::cerr << "Error parsing arguments: " << e.what() << std::endl;
}
return expectFailure;
}
try
{
m_parser.each_argument( [&]( const arrrgh::argument_abstract& arg )
{
const std::string value = arg.converted_value_string();
if( VERBOSE )
{
std::cout << "Argument " << arg.best_name() << "=" << value << std::endl;
}
} );
m_parser.each_unlabeled_argument( [&]( const std::string& arg )
{
if( VERBOSE )
{
std::cout << "Unlabeled: \"" << arg << "\"" << std::endl;
}
} );
}
catch( const std::exception& e )
{
if( VERBOSE )
{
std::cerr << "Error reading argument values: " << e.what() << std::endl;
}
return expectFailure;
}
if( VERBOSE )
{
if( expectFailure )
{
std::cout << m_name << " failed to fail.\n";
}
}
return !expectFailure;
}
bool test( std::string commandLine, bool expectFailure )
{
if( VERBOSE )
{
std::cout << "Testing " << m_name << ": '" << commandLine << "' expecting " << ( expectFailure ? "failure" : "success" ) << "..." << std::endl;
}
// Prepend program name.
commandLine = m_name + " " + commandLine;
// Break command line into arguments.
const char* const rawCommandLine = commandLine.c_str();
const char* const endCommandLine = rawCommandLine + commandLine.size();
std::vector< std::string > args;
bool quoted = false;
bool skippingSpace = false;
const char* tokenStart = rawCommandLine;
for( const char* pc = tokenStart; pc != endCommandLine; ++pc )
{
const char c = *pc;
if( !quoted && std::isspace( c ))
{
if( !skippingSpace )
{
// Here endeth a token. Copy it.
//
std::string arg;
std::copy( tokenStart, pc, std::back_inserter( arg ));
args.emplace_back( std::move( arg ));
}
skippingSpace = true;
}
else
{
if( skippingSpace )
{
// Start of new token.
//
tokenStart = pc;
}
skippingSpace = false;
if( c == '\"' )
{
quoted = !quoted;
}
}
}
if( !skippingSpace )
{
// Copy the last token.
//
std::string arg;
std::copy( tokenStart, endCommandLine, std::back_inserter( arg ));
args.emplace_back( std::move( arg ));
}
// Convert arg strings to array of pointers.
//
std::vector< const char* > rawArgs;
std::transform( args.begin(), args.end(), std::back_inserter( rawArgs ), []( const std::string& s ) { return s.c_str(); } );
return test( static_cast< int >( args.size() ), rawArgs.data(), expectFailure );
}
private:
std::string m_name;
arrrgh::parser m_parser;
};
}
#define VERIFY_TEST( expr ) if( !(expr) ) std::cout << "Test failed: " << #expr << std::endl;
#define VERIFY_EQUAL( exprA, exprB ) if( !( (exprA) == (exprB) )) std::cout << "Test failed: \"" << (exprA) << "\" != \"" << (exprB) << "\"" << std::endl;
void testProgramWithNoArgs()
{
program program( "no_args", "Tests program with no arguments." );
// EXPECT SUCCESS: No arguments passed in.
//
if( !program.test( "", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
}
// EXPECT FAILURE: Unwanted arguments passed in.
//
if( !program.test( "-a --switch=purple", true ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
}
// EXPECT SUCCESS: Test with unexpected unlabeled arguments passed in. This is okay.
//
if( !program.test( "file1.jpg", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
}
}
void testSingleOptionalBoolArg()
{
program program( "args1b", "Tests a single optional bool argument." );
const auto& arg = program.parser().add< bool >( "long", "Really awesome description.", 'l' );
// EXPECT SUCCESS: No arguments passed in.
//
if( !program.test( "", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_TEST( !arg.value() );
}
// EXPECT SUCCESS: Short form argument passed in.
//
if( !program.test( "-l", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_TEST( arg.value() );
}
// EXPECT SUCCESS: Long form argument passed in.
//
if( !program.test( "--long", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_TEST( arg.value() );
}
// EXPECT FAILURE: Missing hyphen before long form.
//
if( !program.test( "-long", true ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
}
}
void testSetOfBoolArg()
{
program program( "args3b", "Tests sets of bool args." );
const auto& argA = program.parser().add< bool >( "", "a.", 'a' );
const auto& argB = program.parser().add< bool >( "", "b.", 'b' );
const auto& argC = program.parser().add< bool >( "", "c.", 'c' );
// EXPECT SUCCESS: No arguments passed in.
//
if( !program.test( "", false ) )
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_TEST( !argA.value() );
VERIFY_TEST( !argB.value() );
VERIFY_TEST( !argC.value() );
}
// EXPECT SUCCESS: Independent arguments passed in.
//
if( !program.test( "-a -c", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_TEST( argA.value() );
VERIFY_TEST( !argB.value() );
VERIFY_TEST( argC.value() );
}
// EXPECT SUCCESS: Collection of arguments passed in.
//
if( !program.test( "-bc", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_TEST( !argA.value() );
VERIFY_TEST( argB.value() );
VERIFY_TEST( argC.value() );
}
}
void testOptionalNumericArg()
{
program program( "args1f", "Tests optional numeric argument." );
const auto& arg = program.parser().add< double >( "weight", "The weight of the thing.", 'w' );
// EXPECT SUCCESS: No arguments passed in.
//
if( !program.test( "", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_EQUAL( arg.value(), 0 );
}
// EXPECT FAILURE: Passed without assignment
//
if( !program.test( "-w", true ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
}
// EXPECT SUCCESS: Assigned with short form.
//
if( !program.test( "-w=4.5", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_EQUAL( arg.value(), 4.5 );
}
// EXPECT SUCCESS: Assigned with long form.
//
if( !program.test( "--weight=128", false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_EQUAL( arg.value(), 128 );
}
}
void testUnlabeledArgs()
{
program program( "args_unlabeled", "Tests unlabeled arguments." );
// EXPECT SUCCESS: Several arguments passed.
//
const std::string givenArgString = "aardvark b Caveman";
if( !program.test( givenArgString, false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
std::string argString;
program.parser().each_unlabeled_argument( [&]( const std::string& arg )
{
argString += arg;
argString += " ";
} );
argString.pop_back(); // Final space.
VERIFY_EQUAL( givenArgString, argString );
}
}
void testEverything( bool withEqualSign )
{
program program( "enchilada", "The whole enchilada: a mix of all argument types and situations." );
const auto& argA = program.parser().add< bool >( "", "a.", 'a' );
const auto& argB = program.parser().add< bool >( "", "b.", 'b' );
const auto& argC = program.parser().add< bool >( "", "c.", 'c' );
const auto& argD = program.parser().add< bool >( "", "d.", 'd' );
const auto& argF = program.parser().add< float >( "float", "f.", 'f', arrrgh::Optional, 3.141f );
const auto& argG = program.parser().add< float >( "groat-moat", "g.", 'g' );
const auto& argH = program.parser().add< int >( "height", "h.", 'h', arrrgh::Required );
const auto& argS = program.parser().add< std::string >( "name", "s.", 's', arrrgh::Required );
auto argBreak = [withEqualSign]()
{
return withEqualSign ? "=" : " ";
};
const std::string args = std::string( "-a --groat-moat" ) + argBreak() + "16.25 -bd unlabeled --name" + argBreak() + "\"Absolom, Absolom\" -h" + argBreak() + "8 -- --weirdly-unlabeled";
if( !program.test( args, false ))
{
std::cout << program.name() << " FAILED" << std::endl;
}
else
{
if( VERBOSE ) std::cout << program.name() << " succeeded.\n";
VERIFY_TEST( argA.value() );
VERIFY_TEST( argB.value() );
VERIFY_TEST( !argC.value() );
VERIFY_TEST( argD.value() );
VERIFY_EQUAL( argF.value(), 3.141f );
VERIFY_EQUAL( argG.value(), 16.25f );
VERIFY_EQUAL( argH.value(), 8 );
// Note: The command line processor (e.g. bash) actually
// removes the quotes, but we're too lazy to do that in our command line tokenizer.
VERIFY_EQUAL( argS.value(), "\"Absolom, Absolom\"" );
std::string argString;
program.parser().each_unlabeled_argument( [&]( const std::string& arg )
{
argString += arg;
argString += " ";
} );
argString.pop_back(); // Final space.
VERIFY_EQUAL( argString, "unlabeled --weirdly-unlabeled" );
}
}
int main( int argc, const char* argv[] )
{
testProgramWithNoArgs();
testSingleOptionalBoolArg();
testSetOfBoolArg();
testOptionalNumericArg();
testUnlabeledArgs();
testEverything( true /* with equal sign */ );
std::cout << "Done.\n";
return 0;
}