The following code extracts command line parameters and modifies argc
and argv
parameters of the. After all parameters are read remaining values (especially after --
) are remaining.
Please keep following questions in mind while reviewing:
- Is it good practice to modify
argc
andargv
? - How to make clear to the caller they change?
- Are the
bool
andstd::string
specialisations obvious? - Is there a shorter/compacter way than using the currently used mixin?
Condensed:
/**
* Basic functions shared by general template and specialisations
*/
template <typename T>
class CliArgMixin
{
protected:
/**
* Helper function to delete arguments.
*/
static void deleteArg(int& argc, char* argv[], int pos)
{
assert(argv);
assert(argc > 0);
if(pos >= argc) return;
for(int i=pos; i<argc-1; ++i)
{
argv[i] = argv[i+1];
}
argv[argc] = 0;
argc--;
argv[argc] = 0;
}
/**
* Search by an alternative argument name.
* Use this to use long and short arguments e.g. '-l 10' and '--list 10'
*/
bool search(int& argc, char* argv[], const std::string& name)
{
assert(argv);
assert(argc > 0);
if(name.empty()) return false;
std::string entry;
for(int i=0; i<argc; ++i)
{
assert(argv[i]);
if(argv[i][0] == '-' && argv[i][1] != '-')
{
entry.assign(argv[i] + 1);
}
else if(argv[i][0] == '-' && argv[i][1] == '-')
{
// skip on --
if(argv[i][2] == 0) return false;
entry.assign(argv[i] + 2);
}
else
{
continue;
}
if(entry == name)
{
extract(argc, argv, i);
m_extracted = true;
return true;
}
}
return false;
}
/**
* Search condesed POSIX short options like '-x12'
*/
void searchCondensed(int& argc, char* argv[], const std::string& name)
{
assert(argc > 0);
assert(argv);
if(name.empty()) return;
std::string entry;
for(int i=0; i<argc; ++i)
{
if(argv[i][0] == '-' && argv[i][1] != '-')
{
entry = argv[i][1];
}
else if(argv[i][0] == '-' && argv[i][1] == '-')
{
// skip on --
if(argv[i][2] == 0) return;
}
else
{
continue;
}
if(entry == name)
{
extractCondensed(argc, argv, i);
m_extracted = true;
return;
}
}
}
/**
* Returns true if an argument value from the command line was extracted
* false otherwise.
*/
bool extractedValue() const { return m_extracted; }
CliArgMixin(const T& dflt)
: m_value(dflt)
, m_extracted(false)
{}
virtual ~CliArgMixin() {}
virtual void extract(int& argc, char* argv[], int index) = 0;
virtual void extractCondensed(int& argc, char* argv[], int index) = 0;
T m_value;
bool m_extracted;
};
/**
* General template.
*/
template <typename T>
class CliArg : public CliArgMixin<T>
{
typedef CliArgMixin<T> Mixin;
public:
CliArg(int& argc, char* argv[], const std::string& opt, const std::string& longOpt, const T& d = T())
: Mixin(d)
{
if(opt.length() > 1)
{
throw std::invalid_argument("Short option must be one character or empty");
}
if(longOpt.length() == 1)
{
throw std::invalid_argument("Long option must not be one character");
}
if( Mixin::search(argc, argv, longOpt) == false )
{
if( Mixin::search(argc, argv, opt) == false )
{
Mixin::searchCondensed(argc, argv, opt);
}
}
}
~CliArg() {}
const T& value() const { return CliArgMixin<T>::m_value; }
operator const T&() const { return CliArgMixin<T>::m_value; }
private:
CliArg() = delete;
virtual void extract(int& argc, char* argv[], int index)
{
if(index+1 < argc)
{
std::istringstream(argv[index+1]) >> Mixin::m_value;
Mixin::deleteArg(argc, argv, index);
// TRICKY: after the i-th was a delete index is now the old i+1;
Mixin::deleteArg(argc, argv, index);
}
else
{
std::ostringstream msg;
msg << "Missing value for parameter: " << argv[index];
throw std::runtime_error( msg.str() );
}
}
virtual void extractCondensed(int& argc, char** argv, int index)
{
// skip '-' and opt
std::istringstream(argv[index]+2) >> Mixin::m_value;
Mixin::deleteArg(argc, argv, index);
}
};
/**
* Specialisation for std::string values
*/
template <>
class CliArg< std::string > : public CliArgMixin< std::string >
{
typedef CliArgMixin<std::string> Mixin;
public:
CliArg(int& argc, char* argv[], const std::string& opt, const std::string& longOpt, const std::string& d = "")
: Mixin(d)
{
if(opt.length() > 1)
{
throw std::invalid_argument("Short option must be one character or empty");
}
if(longOpt.length() == 1)
{
throw std::invalid_argument("Long option must not be one character");
}
if( Mixin::search(argc, argv, longOpt) == false)
{
if( Mixin::search(argc, argv, opt) == false)
{
Mixin::searchCondensed(argc, argv, opt);
}
}
}
~CliArg() {}
const std::string& value() const { return Mixin::m_value; }
operator const std::string&() const { return Mixin::m_value; }
private:
CliArg() = delete;
virtual void extract(int& argc, char** argv, int index)
{
if(index+1 < argc)
{
Mixin::m_value = argv[index+1];
Mixin::deleteArg(argc, argv, index);
// TRICKY: after the i-th was a delete i is now the old i+1;
Mixin::deleteArg(argc, argv, index);
}
else
{
std::ostringstream msg;
msg << "Missing value for parameter: " << argv[index];
throw std::runtime_error( msg.str() );
}
}
virtual void extractCondensed(int& argc, char** argv, int index)
{
// skip '-' and opt
Mixin::m_value = argv[index]+2;
Mixin::deleteArg(argc, argv, index);
}
};
/**
* Specialisation for bool flags with no following value.
*/
template <>
class CliArg< bool > : public CliArgMixin< bool >
{
typedef CliArgMixin< bool > Mixin;
public:
CliArg(int& argc, char* argv[], const std::string& opt, const std::string& longOpt)
: Mixin(false)
{
assert(argc > 0);
assert(argv);
if(opt.length() > 1)
{
throw std::invalid_argument("Short option must be one character or empty");
}
if(longOpt.length() == 1)
{
throw std::invalid_argument("Long option must not be one character");
}
if( Mixin::search(argc, argv, longOpt) == false )
{
if( Mixin::search(argc, argv, opt) == false )
{
searchCondensed(argc, argv, opt);
}
}
}
~CliArg() {}
bool value() const { return Mixin::m_value; }
operator bool() const { return Mixin::m_value; }
private:
CliArg() = delete;
virtual void extract(int& argc, char** argv, int index)
{
Mixin::m_value = true;
Mixin::deleteArg(argc, argv, index);
}
/**
* There are no condensed value for short options
*/
virtual void extractCondensed(int&, char**, int) {}
/**
* Only condensed bool are of type '-asd'
*/
void searchCondensed(int& argc, char* argv[], const std::string& opt)
{
if(opt.empty()) return;
for(int index=0; index<argc; ++index)
{
const size_t len = ::strlen(argv[index]);
if(len < 2) continue;
if( argv[index][0] != '-' ) continue;
if( argv[index][1] == '-' ) return;
for(uint i=1; i<len; ++i)
{
if( argv[index][i] == opt[0] )
{
Mixin::m_value = true;
Mixin::m_extracted = true;
// remove the opt from the string.
argv[index][i] = argv[index][len-1];
argv[index][len-1] = 0;
break;
}
}
}
}
};
Usage
a.out --verbose -n 2 input1 input2
bodhi::CliArg< bool > isVerbose(argc, argv, "v", "verbose");
bodhi::CliArg< int > count(argc, argv, "n", "--number");
if( verbose == true ) // be verbose
argc
is reduced by 2 and input1
is the next in argv
Update:
fyi I reworked my code to adhere all points made here: https://gist.github.com/aggsol/6008239
std::vector<std::string> args(argv + 1, argv + argc);
\$\endgroup\$argc
andargv
. \$\endgroup\$argc
and/orargv
? There's nothing magical about them. Even if you were talking about the arguments tomain()
, it's still perfectly valid, and that's the normal pattern for argument parsing in Qt, for example. \$\endgroup\$