The answer by Huw Walters has been very useful for me. I have extended a bit on it to allow also to set the verbosity level explicitely by a number argument. So all these calls will set verbosity to 4:
app -v -v -v -v
app -v4
app -vvvv
app -vvv -v
app -v3 -v
Unfortunately, the backdraw of that solution is that mixing in other options after an -v
doesn't work. So app -vx
will yield an error, even, if x
is also a legal option.
Below is the code. To use it, provide:
("verbose,v", new po_CountAndAddValue(&verbose), "Increase verbosity");
as operator to add_options()
.
class po_CountAndAddValue : public po::typed_value<std::size_t>
{
public:
po_CountAndAddValue(std::size_t* store)
: po::typed_value<std::size_t>(store)
{
default_value(0);
}
po_CountAndAddValue() : po_CountAndAddValue(nullptr) {}
~po_CountAndAddValue() override {}
// Allow zero or one tokens:
unsigned min_tokens() const override { return 0; }
unsigned max_tokens() const override { return 1; }
void xparse(boost::any& store,
const std::vector<std::string>& tokens) const override
{
if(tokens.empty()) {
count_++;
} else {
const std::string& tok = tokens[0];
if(std::all_of(tok.begin(), tok.end(), [](char c){return c=='v';})){
count_ += 1 + tok.size();
} else {
// Add explicitely specified verbosity count:
count_ += boost::lexical_cast<int>(tok);
}
}
// Replace the stored value with the updated access count:
store = boost::any(count_);
}
private:
mutable std::size_t count_{ 0 };
};
In case, that you don't want to allow the mixed use of the verbosity option with and without a number argument, you can add error reporting for that case as follows:
class po_CountOrSetValue : public po::typed_value<std::size_t>
{
public:
po_CountOrSetValue(std::size_t* store)
: po::typed_value<std::size_t>(store)
{
default_value(0);
}
po_CountOrSetValue() : po_CountOrSetValue(nullptr) {}
~po_CountOrSetValue() override {}
// Allow zero or one tokens:
unsigned min_tokens() const override { return 0; }
unsigned max_tokens() const override { return 1; }
class mixed_types_error : public po::error_with_option_name {
public:
mixed_types_error()
: po::error_with_option_name("option '%canonical_option%' wrongly used both with and without value"){}
~mixed_types_error() throw() {}
};
void xparse(boost::any& store,
const std::vector<std::string>& tokens) const override
{
if(tokens.empty() ||
std::all_of(tokens[0].begin(), tokens[0].end(),
[](char c){return c=='v';}))
{
if(set_) {
throw mixed_types_error();
}
count_ += tokens.empty() ? 1 : 1 + tokens[0].size();
} else {
if(std::exchange(set_, true)) {
throw po::multiple_occurrences();
}
if(count_ != 0) {
throw mixed_types_error();
}
count_ = boost::lexical_cast<int>(tokens[0]);
}
// Replace the stored value with the updated count:
store = boost::any(count_);
}
private:
mutable std::size_t count_{ 0 };
mutable bool set_{ false };
};
All of these specifiers work with the first version to set verbosity to 4, but fail with the second version:
app -v3 -v
app -v3 -v1
app -vv -v2
Everything was tested with C++17 and Boost 1.74.