in sdk/core/perf/inc/azure/perf/argagg.hpp [1046:1288]
inline parser_results parser::parse(int argc, const char** argv, bool posOnly) const
{
// Inspect each definition to see if its valid. You may wonder "why don't
// you do this validation on construction?" I had thought about it but
// realized that since I've made the parser an aggregate type (granted it
// just "aggregates" a single vector) I would need to track any changes to
// the definitions vector and re-run the validity check in order to
// maintain this expected "validity invariant" on the object. That would
// then require hiding the definitions vector as a private entry and then
// turning the parser into a thin interface (by re-exposing setters and
// getters) to the vector methods just so that I can catch when the
// definition has been modified. It seems much simpler to just enforce the
// validity when you actually want to parse because it's at the moment of
// parsing that you know the definitions are complete.
parser_map map = validate_definitions(this->definitions);
// Initialize the parser results that we'll be returning. Store the program
// name (assumed to be the first command line argument) and initialize
// everything else as empty.
std::unordered_map<std::string, option_results> options{};
std::vector<const char*> pos;
parser_results results{argv[0], std::move(options), std::move(pos)};
// Add an empty option result for each definition.
for (const auto& defn : this->definitions)
{
option_results opt_results{{}};
results.options.insert(std::make_pair(defn.name, opt_results));
}
// Don't start off ignoring flags. We only ignore flags after a -- shows up
// in the command line arguments.
bool ignore_flags = false;
// Keep track of any options that are expecting arguments.
const char* last_flag_expecting_args = nullptr;
option_result* last_option_expecting_args = nullptr;
unsigned int num_option_args_to_consume = 0;
// Get pointers to pointers so we can treat the raw pointer array as an
// iterator for standard library algorithms. This isn't used yet but can be
// used to template this function to work on iterators over strings or
// C-strings.
const char** arg_i = argv + 1;
const char** arg_end = argv + argc;
while (arg_i != arg_end)
{
auto arg_i_cstr = *arg_i;
auto arg_i_len = std::strlen(arg_i_cstr);
// Some behavior to note: if the previous option is expecting an argument
// then the next entry will be treated as a positional argument even if
// it looks like a flag.
bool treat_as_positional_argument
= (ignore_flags || num_option_args_to_consume > 0
|| !cmd_line_arg_is_option_flag(arg_i_cstr));
if (treat_as_positional_argument)
{
// If last option is expecting some specific positive number of
// arguments then give this argument to that option, *regardless of
// whether or not the argument looks like a flag or is the special "--"
// argument*.
if (num_option_args_to_consume > 0)
{
last_option_expecting_args->arg = arg_i_cstr;
--num_option_args_to_consume;
++arg_i;
continue;
}
// Now we check if this is just "--" which is a special argument that
// causes all following arguments to be treated as non-options and is
// itself discarded.
if (std::strncmp(arg_i_cstr, "--", 2) == 0 && arg_i_len == 2)
{
ignore_flags = true;
++arg_i;
continue;
}
// If there are no expectations for option arguments then simply use
// this argument as a positional argument.
results.pos.push_back(arg_i_cstr);
++arg_i;
continue;
}
// Reset the "expecting argument" state.
last_flag_expecting_args = nullptr;
last_option_expecting_args = nullptr;
num_option_args_to_consume = 0;
if (!posOnly)
{
// If we're at this point then we're definitely dealing with something
// that is flag-like and has hyphen as the first character and has a
// length of at least two characters. How we handle this potential flag
// depends on whether or not it is a long-option so we check that first.
bool is_long_flag = (arg_i_cstr[1] == '-');
if (is_long_flag)
{
// Long flags have a complication: their arguments can be specified
// using an '=' character right inside the argument. That means an
// argument like "--output=foobar.txt" is actually an option with flag
// "--output" and argument "foobar.txt". So we look for the first
// instance of the '=' character and keep it in long_flag_arg. If
// long_flag_arg is nullptr then we didn't find '='. We need the
// flag_len to construct long_flag_str below.
auto long_flag_arg = std::strchr(arg_i_cstr, '=');
size_t flag_len = arg_i_len;
if (long_flag_arg != nullptr)
{
flag_len = long_flag_arg - arg_i_cstr;
}
std::string long_flag_str(arg_i_cstr, flag_len);
if (!map.known_long_flag(long_flag_str))
{
std::ostringstream msg;
msg << "found unexpected flag: " << long_flag_str;
throw unexpected_option_error(msg.str());
}
const auto defn = map.get_definition_for_long_flag(long_flag_str);
if (long_flag_arg != nullptr && defn->num_args == 0)
{
std::ostringstream msg;
msg << "found argument for option not expecting an argument: " << arg_i_cstr;
throw unexpected_argument_error(msg.str());
}
// We've got a legitimate, known long flag option so we add an option
// result. This option result initially has an arg of nullptr, but that
// might change in the following block.
auto& opt_results = results.options[defn->name];
option_result opt_result{nullptr};
opt_results.all.push_back(std::move(opt_result));
if (defn->requires_arguments())
{
bool there_is_an_equal_delimited_arg = (long_flag_arg != nullptr);
if (there_is_an_equal_delimited_arg)
{
// long_flag_arg would be "=foo" in the "--output=foo" case so we
// increment by 1 to get rid of the equal sign.
opt_results.all.back().arg = long_flag_arg + 1;
}
else
{
last_flag_expecting_args = arg_i_cstr;
last_option_expecting_args = &(opt_results.all.back());
num_option_args_to_consume = defn->num_args;
}
}
++arg_i;
continue;
}
// If we've made it here then we're looking at either a short flag or a
// group of short flags. Short flags can be grouped together so long as
// they don't require any arguments unless the option that does is the
// last in the group ("-o x -v" is okay, "-vo x" is okay, "-ov x" is
// not). So starting after the dash we're going to process each character
// as if it were a separate flag. Note "sf_idx" stands for "short flag
// index".
for (size_t sf_idx = 1; sf_idx < arg_i_len; ++sf_idx)
{
const auto short_flag = arg_i_cstr[sf_idx];
if (!std::isalnum(static_cast<unsigned char>(short_flag)))
{
std::ostringstream msg;
msg << "found non-alphanumeric character '" << arg_i_cstr[sf_idx] << "' in flag group '"
<< arg_i_cstr << "'";
throw std::domain_error(msg.str());
}
if (!map.known_short_flag(short_flag))
{
std::ostringstream msg;
msg << "found unexpected flag '" << arg_i_cstr[sf_idx] << "' in flag group '"
<< arg_i_cstr << "'";
throw unexpected_option_error(msg.str());
}
auto defn = map.get_definition_for_short_flag(short_flag);
auto& opt_results = results.options[defn->name];
// Create an option result with an empty argument (for now) and add it
// to this option's results.
option_result opt_result{nullptr};
opt_results.all.push_back(std::move(opt_result));
if (defn->requires_arguments())
{
// If this short flag's option requires an argument and we're the
// last flag in the short flag group then just put the parser into
// "expecting argument for last option" state and move onto the next
// command line argument.
bool is_last_short_flag_in_group = (sf_idx == arg_i_len - 1);
if (is_last_short_flag_in_group)
{
last_flag_expecting_args = arg_i_cstr;
last_option_expecting_args = &(opt_results.all.back());
num_option_args_to_consume = defn->num_args;
break;
}
// If this short flag's option requires an argument and we're NOT the
// last flag in the short flag group then we automatically consume
// the rest of the short flag group as the argument for this flag.
// This is how we get the POSIX behavior of being able to specify a
// flag's arguments without a white space delimiter (e.g.
// "-I/usr/local/include").
opt_results.all.back().arg = arg_i_cstr + sf_idx + 1;
break;
}
}
}
++arg_i;
continue;
}
// If we're done with all of the arguments but are still expecting
// arguments for a previous option then we haven't satisfied that option.
// This is an error.
if (num_option_args_to_consume > 0)
{
std::ostringstream msg;
msg << "last option \"" << last_flag_expecting_args
<< "\" expects an argument but the parser ran out of command line "
<< "arguments to parse";
throw option_lacks_argument_error(msg.str());
}
return results;
}