Use ArgTraits instead of ValueExtractor specialization Bug 1711487

This commit is contained in:
macbishop 2007-06-14 21:02:01 +00:00
parent 847b8c9478
commit 00f07eb977
18 changed files with 488 additions and 416 deletions

View File

@ -1,5 +1,6 @@
noinst_PROGRAMS = test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 noinst_PROGRAMS = test1 test2 test3 test4 test5 test6 test7 test8 test9 \
test10 test11 test12
test1_SOURCES = test1.cpp test1_SOURCES = test1.cpp
test2_SOURCES = test2.cpp test2_SOURCES = test2.cpp
@ -11,6 +12,8 @@ test7_SOURCES = test7.cpp
test8_SOURCES = test8.cpp test8_SOURCES = test8.cpp
test9_SOURCES = test9.cpp test9_SOURCES = test9.cpp
test10_SOURCES = test10.cpp test10_SOURCES = test10.cpp
test11_SOURCES = test11.cpp
test12_SOURCES = test12.cpp
INCLUDES = -I$(top_srcdir)/include INCLUDES = -I$(top_srcdir)/include

52
examples/test11.cpp Normal file
View File

@ -0,0 +1,52 @@
#include "tclap/CmdLine.h"
#include <iterator>
using namespace TCLAP;
// Define a simple 3D vector type
struct Vect3D {
double v[3];
// operator= will be used to assign to the vector
Vect3D& operator=(const std::string &str)
{
std::istringstream iss(str);
if (!(iss >> v[0] >> v[1] >> v[2]))
throw TCLAP::ArgParseException(str + " is not a 3D vector");
return *this;
}
std::ostream& print(std::ostream &os) const
{
std::copy(v, v + 3, std::ostream_iterator<double>(os, " "));
return os;
}
};
// Create an ArgTraits for the 3D vector type that declares it to be
// of string like type
namespace TCLAP {
template<>
struct ArgTraits<Vect3D> {
typedef StringLike ValueCategory;
};
}
int main(int argc, char *argv[])
{
CmdLine cmd("Command description message", ' ', "0.9");
ValueArg<Vect3D> vec("v", "vect", "vector",
true, Vect3D(), "3D vector", cmd);
try {
cmd.parse(argc, argv);
} catch(std::exception &e) {
std::cout << e.what() << std::endl;
return EXIT_FAILURE;
}
vec.getValue().print(std::cout);
std::cout << std::endl;
}

68
examples/test12.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "tclap/CmdLine.h"
#include <iterator>
#include <algorithm>
using namespace TCLAP;
// Define a simple 3D vector type
struct Vect3D {
double v[3];
// operator= will be used to assign to the vector
Vect3D& operator=(const std::string &str)
{
std::istringstream iss(str);
if (!(iss >> v[0] >> v[1] >> v[2]))
throw TCLAP::ArgParseException(str + " is not a 3D vector");
return *this;
}
std::ostream& print(std::ostream &os) const
{
std::copy(v, v + 3, std::ostream_iterator<double>(os, " "));
return os;
}
};
std::ostream& operator<<(std::ostream &os, const Vect3D &v)
{
return v.print(os);
}
// Create an ArgTraits for the 3D vector type that declares it to be
// of string like type
namespace TCLAP {
template<>
struct ArgTraits<Vect3D> {
typedef StringLike ValueCategory;
};
}
int main(int argc, char *argv[])
{
CmdLine cmd("Command description message", ' ', "0.9");
MultiArg<Vect3D> vec("v", "vect", "vector",
true, "3D vector", cmd);
try {
cmd.parse(argc, argv);
} catch(std::exception &e) {
std::cout << e.what() << std::endl;
return EXIT_FAILURE;
}
std::copy(vec.begin(), vec.end(),
std::ostream_iterator<Vect3D>(std::cout, "\n"));
std::cout << "REVERSED" << std::endl;
// use alt. form getValue()
std::vector<Vect3D> v(vec.getValue());
std::reverse(v.begin(), v.end());
std::copy(v.begin(), v.end(),
std::ostream_iterator<Vect3D>(std::cout, "\n"));
}

View File

@ -24,11 +24,27 @@
#ifndef TCLAP_ARGUMENT_H #ifndef TCLAP_ARGUMENT_H
#define TCLAP_ARGUMENT_H #define TCLAP_ARGUMENT_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#else
#define HAVE_SSTREAM
#endif
#include <string> #include <string>
#include <vector> #include <vector>
#include <list> #include <list>
#include <iostream> #include <iostream>
#if defined(HAVE_SSTREAM)
#include <sstream>
typedef std::istringstream istringstream;
#elif defined(HAVE_STRSTREAM)
#include <strstream>
typedef std::istrstream istringstream;
#else
#error "Need a stringstream (sstream or strstream) to compile!"
#endif
#include <tclap/ArgException.h> #include <tclap/ArgException.h>
#include <tclap/Visitor.h> #include <tclap/Visitor.h>
#include <tclap/CmdLineInterface.h> #include <tclap/CmdLineInterface.h>
@ -350,6 +366,84 @@ typedef std::vector<Arg*>::iterator ArgVectorIterator;
*/ */
typedef std::list<Visitor*>::iterator VisitorListIterator; typedef std::list<Visitor*>::iterator VisitorListIterator;
// We use two empty structs to get compile type specialization
// function to work
/**
* A value like argument value type is a value that can be set using
* operator>>. This is the default value type.
*/
struct ValueLike {};
/**
* A string like argument value type is a value that can be set using
* operator=(string). Usefull if the value type contains spaces which
* will be broken up into individual tokens by operator>>.
*/
struct StringLike {};
/**
* Arg traits are used to get compile type specialization when parsing
* argument values. Using an ArgTraits you can specify the way that
* values gets assigned to any particular type during parsing. The two
* supported types are string like and value like.
*/
template<typename T>
struct ArgTraits {
typedef ValueLike ValueCategory;
};
/**
* Strings have string like argument traits.
*/
template<>
struct ArgTraits<std::string> {
typedef StringLike ValueCategory;
};
/*
* Extract a value of type T from it's string representation contained
* in strVal. The ValueLike parameter used to select the correct
* specialization of ExtractValue depending on the value traits of T.
* ValueLike traits use operator>> to assign the value from strVal.
*/
template<typename T> void
ExtractValue(T &destVal, const std::string& strVal, ValueLike vl)
{
std::istringstream is(strVal);
int valuesRead = 0;
while ( is.good() ) {
if ( is.peek() != EOF )
is >> destVal;
else
break;
valuesRead++;
}
if ( is.fail() )
throw( ArgParseException("Couldn't read argument value "
"from string '" + strVal + "'"));
if ( valuesRead > 1 )
throw( ArgParseException("More than one valid value parsed from "
"string '" + strVal + "'"));
}
/*
* Extract a value of type T from it's string representation contained
* in strVal. The ValueLike parameter used to select the correct
* specialization of ExtractValue depending on the value traits of T.
* StringLike uses assignment (operator=) to assign from strVal.
*/
template<typename T> void
ExtractValue(T &destVal, const std::string& strVal, StringLike sl)
{
destVal = strVal;
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
//BEGIN Arg.cpp //BEGIN Arg.cpp

View File

@ -29,135 +29,7 @@
#include <tclap/Arg.h> #include <tclap/Arg.h>
#include <tclap/Constraint.h> #include <tclap/Constraint.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#else
#define HAVE_SSTREAM
#endif
#if defined(HAVE_SSTREAM)
#include <sstream>
#elif defined(HAVE_STRSTREAM)
#include <strstream>
#else
#error "Need a stringstream (sstream or strstream) to compile!"
#endif
namespace TCLAP { namespace TCLAP {
template<class T> class MultiArg;
namespace MULTI_ARG_HELPER {
enum Error_e { EXTRACT_FAILURE = 1000, EXTRACT_TOO_MANY };
/**
* This class is used to extract a value from an argument.
* It is used because we need a special implementation to
* deal with std::string and making a specialiced function
* puts it in the T segment, thus generating link errors.
* Having a specialiced class makes the symbols weak.
* This is not pretty but I don't know how to make it
* work any other way.
*/
template<class T>
class ValueExtractor
{
friend class MultiArg<T>;
private:
/**
* Reference to the vector of values where the result of the
* extraction will be put.
*/
std::vector<T> &_values;
/**
* Constructor.
* \param values - Where the values extracted will be put.
*/
ValueExtractor(std::vector<T> &values) : _values(values) {}
/**
* Method that will attempt to parse the input stream for values
* of type T.
* \param val - Where the values parsed will be put.
*/
int extractValue( const std::string& val )
{
T temp;
#if defined(HAVE_SSTREAM)
std::istringstream is(val);
#elif defined(HAVE_STRSTREAM)
std::istrstream is(val.c_str());
#else
#error "Need a stringstream (sstream or strstream) to compile!"
#endif
int valuesRead = 0;
while ( is.good() )
{
if ( is.peek() != EOF )
is >> temp;
else
break;
valuesRead++;
}
if ( is.fail() )
return EXTRACT_FAILURE;
if ( valuesRead > 1 )
return EXTRACT_TOO_MANY;
_values.push_back(temp);
return 0;
}
};
/**
* Specialization for string. This is necessary because istringstream
* operator>> is not able to ignore spaces... meaning -x "X Y" will only
* read 'X'... and thus the specialization.
*/
template<>
class ValueExtractor<std::string>
{
friend class MultiArg<std::string>;
private:
/**
* Reference to the vector of strings where the result of the
* extraction will be put.
*/
std::vector<std::string> &_values;
/**
* Constructor.
* \param values - Where the strings extracted will be put.
*/
ValueExtractor(std::vector<std::string> &values) : _values(values) {}
/**
* Method that will attempt to parse the input stream for values
* of type std::string.
* \param val - Where the values parsed will be put.
*/
int extractValue( const std::string& val )
{
_values.push_back( val );
return 0;
}
};
} //namespace MULTI_ARG_HELPER
/** /**
* An argument that allows multiple values of type T to be specified. Very * An argument that allows multiple values of type T to be specified. Very
* similar to a ValueArg, except a vector of values will be returned * similar to a ValueArg, except a vector of values will be returned
@ -166,7 +38,12 @@ class ValueExtractor<std::string>
template<class T> template<class T>
class MultiArg : public Arg class MultiArg : public Arg
{ {
protected: public:
typedef std::vector<T> container_type;
typedef typename container_type::iterator iterator;
typedef typename container_type::const_iterator const_iterator;
protected:
/** /**
* The list of values parsed from the CmdLine. * The list of values parsed from the CmdLine.
@ -193,7 +70,7 @@ class MultiArg : public Arg
bool _allowMore; bool _allowMore;
public: public:
/** /**
* Constructor. * Constructor.
@ -307,6 +184,18 @@ class MultiArg : public Arg
*/ */
const std::vector<T>& getValue(); const std::vector<T>& getValue();
/**
* Returns an iterator over the values parsed from the command
* line.
*/
const_iterator begin() const { return _values.begin(); }
/**
* Returns the end of the values parsed from the command
* line.
*/
const_iterator end() const { return _values.end(); }
/** /**
* Returns the a short id string. Used in the usage. * Returns the a short id string. Used in the usage.
* \param val - value to be used. * \param val - value to be used.
@ -492,18 +381,14 @@ bool MultiArg<T>::isRequired() const
template<class T> template<class T>
void MultiArg<T>::_extractValue( const std::string& val ) void MultiArg<T>::_extractValue( const std::string& val )
{ {
MULTI_ARG_HELPER::ValueExtractor<T> ve(_values); try {
T tmp;
ExtractValue(tmp, val, typename ArgTraits<T>::ValueCategory());
_values.push_back(tmp);
} catch( ArgParseException &e) {
throw ArgParseException(e.error(), toString());
}
int err = ve.extractValue(val);
if ( err == MULTI_ARG_HELPER::EXTRACT_FAILURE )
throw( ArgParseException("Couldn't read argument value "
"from string '" + val + "'", toString() ) );
if(err == MULTI_ARG_HELPER::EXTRACT_TOO_MANY)
throw( ArgParseException("More than one valid value "
"parsed from string '" + val + "'",
toString() ) );
if ( _constraint != NULL ) if ( _constraint != NULL )
if ( ! _constraint->check( _values.back() ) ) if ( ! _constraint->check( _values.back() ) )
throw( CmdLineParseException( "Value '" + val + throw( CmdLineParseException( "Value '" + val +

View File

@ -29,135 +29,8 @@
#include <tclap/Arg.h> #include <tclap/Arg.h>
#include <tclap/Constraint.h> #include <tclap/Constraint.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#else
#define HAVE_SSTREAM
#endif
#if defined(HAVE_SSTREAM)
#include <sstream>
#elif defined(HAVE_STRSTREAM)
#include <strstream>
#else
#error "Need a stringstream (sstream or strstream) to compile!"
#endif
namespace TCLAP { namespace TCLAP {
template<class T> class ValueArg;
namespace VALUE_ARG_HELPER {
enum Error_e { EXTRACT_FAILURE = 1000, EXTRACT_TOO_MANY };
/**
* This class is used to extract a value from an argument.
* It is used because we need a special implementation to
* deal with std::string and making a specialiced function
* puts it in the T segment, thus generating link errors.
* Having a specialiced class makes the symbols weak.
* This is not pretty but I don't know how to make it
* work any other way.
*/
template<class T> class ValueExtractor
{
/**
*
*/
friend class ValueArg<T>;
private:
/**
* Reference to the value where the result of the extraction will
* be put.
*/
T &_value;
/**
* Constructor.
* \param value - Where the value extracted will be put.
*/
ValueExtractor(T &value) : _value(value) { }
/**
* Method that will attempt to parse the input stream for a value
* of type T.
* \param val - Where the value parsed will be put.
*/
int extractValue( const std::string& val )
{
#if defined(HAVE_SSTREAM)
std::istringstream is(val);
#elif defined(HAVE_STRSTREAM)
std::istrstream is(val.c_str());
#else
#error "Need a stringstream (sstream or strstream) to compile!"
#endif
int valuesRead = 0;
while ( is.good() )
{
if ( is.peek() != EOF )
is >> _value;
else
break;
valuesRead++;
}
if ( is.fail() )
return EXTRACT_FAILURE;
if ( valuesRead > 1 )
return EXTRACT_TOO_MANY;
return 0;
}
};
/**
* Specialization for string. This is necessary because istringstream
* operator>> is not able to ignore spaces... meaning -x "X Y" will only
* read 'X'... and thus the specialization.
*/
template<> class ValueExtractor<std::string>
{
/**
*
*/
friend class ValueArg<std::string>;
private:
/**
* Reference to the value where the result of the extraction will
* be put.
*/
std::string &_value;
/**
* Constructor.
* \param value - Where the value extracted will be put.
*/
ValueExtractor(std::string &value) : _value(value) {}
/**
* Method that will attempt to parse the input stream for a value
* of type std::string.
* \param val - Where the string parsed will be put.
*/
int extractValue( const std::string& val )
{
_value = val;
return 0;
}
};
} //namespace VALUE_ARG_HELPER
/** /**
* The basic labeled argument that parses a value. * The basic labeled argument that parses a value.
* This is a template class, which means the type T defines the type * This is a template class, which means the type T defines the type
@ -500,24 +373,17 @@ std::string ValueArg<T>::longID(const std::string& val) const
template<class T> template<class T>
void ValueArg<T>::_extractValue( const std::string& val ) void ValueArg<T>::_extractValue( const std::string& val )
{ {
VALUE_ARG_HELPER::ValueExtractor<T> ve(_value); try {
ExtractValue(_value, val, typename ArgTraits<T>::ValueCategory());
int err = ve.extractValue(val); } catch( ArgParseException &e) {
throw ArgParseException(e.error(), toString());
if ( err == VALUE_ARG_HELPER::EXTRACT_FAILURE ) }
throw( ArgParseException("Couldn't read argument value from string '" +
val + "'", toString() ) );
if ( err == VALUE_ARG_HELPER::EXTRACT_TOO_MANY )
throw( ArgParseException(
"More than one valid value parsed from string '" +
val + "'", toString() ) );
if ( _constraint != NULL ) if ( _constraint != NULL )
if ( ! _constraint->check( _value ) ) if ( ! _constraint->check( _value ) )
throw( CmdLineParseException( "Value '" + val + throw( CmdLineParseException( "Value '" + val +
"' does not meet constraint: " + + "' does not meet constraint: "
_constraint->description(), + _constraint->description(),
toString() ) ); toString() ) );
} }

View File

@ -2,8 +2,9 @@
let "suc = 0" let "suc = 0"
let "fail = 0" let "fail = 0"
NUMTEST=67
for (( tno = 1 ; $tno < 62 ; tno = $tno + 1 )); do for (( tno = 1 ; $tno <= $NUMTEST ; tno = $tno + 1 )); do
./testCheck.sh $tno ./testCheck.sh $tno
if [ "$?" -eq "0" ]; then if [ "$?" -eq "0" ]; then
echo "OK" echo "OK"

9
tests/test63.out Normal file
View File

@ -0,0 +1,9 @@
PARSE ERROR:
Required argument missing: vect
Brief USAGE:
../examples/test11 -v <3D vector> [--] [--version] [-h]
For complete USAGE and HELP type:
../examples/test11 --help

13
tests/test63.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
# this tests whether all required args are listed as
# missing when no arguments are specified
# failure
../examples/test11 > tmp.out 2>&1
if cmp -s tmp.out $srcdir/test63.out; then
exit 0
else
exit 1
fi

1
tests/test64.out Normal file
View File

@ -0,0 +1 @@
1 2 3

13
tests/test64.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
# this tests whether all required args are listed as
# missing when no arguments are specified
# failure
../examples/test11 -v "1 2 3" > tmp.out 2>&1
if cmp -s tmp.out $srcdir/test64.out; then
exit 0
else
exit 1
fi

9
tests/test65.out Normal file
View File

@ -0,0 +1,9 @@
1 2 3
4 5 6
7 8 9
-1 0.2 0.4
REVERSED
-1 0.2 0.4
7 8 9
4 5 6
1 2 3

14
tests/test65.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/sh
# this tests whether all required args are listed as
# missing when no arguments are specified
# failure
../examples/test12 -v "1 2 3" -v "4 5 6" -v "7 8 9" -v "-1 0.2 0.4" \
> tmp.out 2>&1
if cmp -s tmp.out $srcdir/test65.out; then
exit 0
else
exit 1
fi

9
tests/test66.out Normal file
View File

@ -0,0 +1,9 @@
PARSE ERROR:
Required argument missing: vect
Brief USAGE:
../examples/test12 -v <3D vector> ... [--] [--version] [-h]
For complete USAGE and HELP type:
../examples/test12 --help

13
tests/test66.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
# this tests whether all required args are listed as
# missing when no arguments are specified
# failure
../examples/test12 > tmp.out 2>&1
if cmp -s tmp.out $srcdir/test66.out; then
exit 0
else
exit 1
fi

9
tests/test67.out Normal file
View File

@ -0,0 +1,9 @@
PARSE ERROR: Argument: -v (--vect)
a 1 0.3 is not a 3D vector
Brief USAGE:
../examples/test12 -v <3D vector> ... [--] [--version] [-h]
For complete USAGE and HELP type:
../examples/test12 --help

13
tests/test67.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
# this tests whether all required args are listed as
# missing when no arguments are specified
# failure
../examples/test12 -v "a 1 0.3" > tmp.out 2>&1
if cmp -s tmp.out $srcdir/test67.out; then
exit 0
else
exit 1
fi

View File

@ -7,7 +7,7 @@ if [ "$1" == "" ]
then then
echo "USAGE: testCheck.sh <test num>" echo "USAGE: testCheck.sh <test num>"
else else
cmd="./test$1.sh" cmd="sh ./test$1.sh"
out="test$1.out" out="test$1.out"
$cmd $cmd
if cmp -s tmp.out $out if cmp -s tmp.out $out