blob: c993e0d0650bba14fa1bcdefd6a2afa478d1f644 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CMDLINE_ARGS_PARSER_H_
#define CMDLINE_ARGS_PARSER_H_
#include <functional>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include <cmdline/optional_bool.h>
#include <cmdline/status.h>
namespace cmdline {
// A command line arguments parser.
//
// Background: The fxl::CommandLine class is designed to be either a global or
// passed around, and code can query it for random switch strings as it feels
// necessary. In the fxl::CommandLine model, the set of command-line switches
// isn't known in advance. This means that it can't generate help or validate
// the switches. It also can't support switches with values that don't use an
// equal sign ("-c foo") because it can't disambiguate whether "foo" is a value
// for the "-c" switch or a standalone parameter. fxl::CommandLine also
// supports modification and generation of new command lines.
//
// In contrast, this command-line parser requires registration of all switches
// in advance. It can then validate inputs, generate help, and accept more
// flexible value formatting. This class is designed exclusively as a parser
// which makes it simpler.
//
// The command line args parser has two layers. The lower "general" layer is a
// concrete class that parses the command line and calls callbacks for
// switches that have been registered.
//
// On top of this is the ArgsParser which is a template that fills
// a struct with registered options. This is what most code will want, but
// you can still register custom callbacks for more complex behavior.
class GeneralArgsParser {
public:
// The callbacks may return an error to indicate a problem with the argument.
// In this case, parsing will stop and the error will be returned. These
// callbacks are called in order as switches are processed, so can be
// called more than once.
// Callback used for command-line switch presence checks. There is no value
// and if one is provided it will be an error.
using OnOffSwitchCallback = std::function<void()>;
// Callback used for string-value switches.
using StringCallback = std::function<Status(const std::string&)>;
GeneralArgsParser();
~GeneralArgsParser();
// disallow copy and assign
GeneralArgsParser(const GeneralArgsParser&) = delete;
GeneralArgsParser& operator=(const GeneralArgsParser&) =
delete;
// The parameter type (or whether there is a parameter at all) is controlled
// by the type of callback passed to the AddGeneralSwitch command.
//
// If there is no short name, pass a zero for the character.
//
// The callback will be called if the switch is specified. With passed-in
// strings must outlive this class (they're assumed to be static).
void AddGeneralSwitch(const char* long_name,
const char short_name, const char* help,
OnOffSwitchCallback on_switch,
OnOffSwitchCallback off_switch = nullptr);
void AddGeneralSwitch(const char* long_name, const char short_name,
const char* help, StringCallback);
// Constructs a help reference for all switches based on the help strings
// passed to AddGeneralSwitch().
std::string GetHelp() const;
// Parses the given command line. The callbacks are called for any provided
// switches, and any non-switch values are placed into the given output
// vector.
Status ParseGeneral(int argc, const char* argv[],
std::vector<std::string>* params) const;
private:
struct Record {
const char* long_name = nullptr;
char short_name = 0;
const char* help_text = nullptr;
// Only one of these should be non-null, indicates the parameter type.
OnOffSwitchCallback on_switch_callback;
StringCallback string_callback;
// This callback should also be included if allowing --no<switchname>
OnOffSwitchCallback off_switch_callback;
};
// Returns true if this record takes an argument.
static bool NeedsArg(const Record* record);
std::vector<Record> records_;
std::string invalid_option_suggestion_ = "Try --help";
};
// ArgsParser -----------------------------------------------------------
//
// Parses a command line into a struct and a vector of string parameters.
template <typename ResultStruct>
class ArgsParser : public GeneralArgsParser {
public:
ArgsParser() = default;
~ArgsParser() = default;
// disallow copy and assign
ArgsParser(const ArgsParser&) = delete;
ArgsParser& operator=(const ArgsParser&) = delete;
// Presence detector for flags that have no values ("--enable-foo"). It sets
// a boolean to true if the parameter is present on the command line. The
// structure should default the boolean to false to detect a set. If a value
// is present for the switch ("--enable-foo=bar") it will give an error.
// If the default for the bool is true, the user can disable a boolean
// switch by prefixing the long name with "no", as in: "--nofoo".
//
// Example
// struct MyOptions {
// bool foo_set = false;
// };
// ArgsParser<MyOptions> parser;
// parser.AddSwitch("foo", 'f', kFooHelp, &MyOptions::foo_set);
void AddSwitch(const char* long_name,
const char short_name, const char* help,
bool ResultStruct::*value) {
AddGeneralSwitch(
long_name, short_name, help,
[this, value]() { result_.*value = true; },
[this, value]() { result_.*value = false; });
}
// Sets a std::optional with the value if the parameter is present. The
// value will be required.
//
// If the optional validator lambda is given, it will be called with the
// string value for the parameter, and invalid values can be rejected by
// returning a cmdline::Status::Error("Your message"); otherwise return
// cmdline::Status::Ok().
//
// Example
// struct MyOptions {
// std::optional<std::string> foo;
// };
// ArgsParser<MyOptions> parser;
// parser.AddSwitch("foo", 'f', kFooHelp, &MyOptions::foo);
void AddSwitch(const char* long_name,
const char short_name, const char* help,
std::optional<std::string> ResultStruct::*value,
StringCallback validator = nullptr) {
AddGeneralSwitch(
long_name, short_name, help,
[this, value, validator](const std::string& v) -> Status {
if (validator != nullptr) {
Status status = validator(v);
if (status.has_error()) {
return status;
}
}
result_.*value = v;
return Status::Ok();
});
}
// Sets a OptionalBool with the value if the switch is present. The value
// will be required.
//
// Note that std::optional<bool> is not supported because this type can
// facilitate error-prone uses. std::optional<> implements |operator bool()|
// to return true if the value is set. The compiler will, thus, allow a
// std_optional<bool> to be used in a boolean expression, which might appear
// to resolve to the value, but is not. The boolean expression would
// evaluate the wrong boolean, creating a bug that is hard to detect
// when reviewing the code.
//
// Example
// struct MyOptions {
// OptionalBool foo;
// };
// ArgsParser<MyOptions> parser;
// parser.AddSwitch("foo", 'f', kFooHelp, &MyOptions::foo);
void AddSwitch(const char* long_name, const char short_name, const char* help,
OptionalBool ResultStruct::*value) {
AddGeneralSwitch(
long_name, short_name, help,
[this, value]() { result_.*value = true; },
[this, value]() { result_.*value = false; });
}
// Sets a command-line option of any type streamable to an iostream (via
// operator ">>") with the value, if the parameter is present. The value
// will be required.
//
// If the optional validator lambda is given, it will be called with the
// string value for the parameter, and invalid values can be rejected by
// returning a cmdline::Status::Error("Your message"); otherwise return
// cmdline::Status::Ok().
//
// Example
// struct MyOptions {
// size_t foo; // Note the type could be int, double, std::string, ...
// };
// ArgsParser<MyOptions> parser;
// parser.AddSwitch("foo", 'f', kFooHelp, &MyOptions::foo);
template <typename T>
void AddSwitch(const char* long_name, const char short_name,
const char* help,
T ResultStruct::*value,
StringCallback validator = nullptr) {
AddGeneralSwitch(
long_name, short_name, help,
[this, long_name, value, validator](const std::string& v)
-> Status {
if (validator != nullptr) {
Status status = validator(v);
if (status.has_error()) {
return status;
}
}
std::stringstream ss(v);
ss >> result_.*value;
if (ss.fail()) {
return Status::Error(
"'" + v + "' is invalid for --" + long_name);
}
std::string trailing;
ss >> trailing;
if (trailing.size() > 0) {
return Status::Error(
"Invalid trailing characters '" + trailing +
"' for --" + long_name);
}
return Status::Ok();
});
}
// Collects a list of all values passed with this flag. This allows multiple
// flag invocations. For examples "-f foo -f bar" would produce a vector
// { "foo", "bar" }
//
// If the optional validator lambda is given, it will be called with the
// string value for the parameter, and invalid values can be rejected by
// returning a cmdline::Status::Error("Your message"); otherwise return
// cmdline::Status::Ok().
//
// Example
// struct MyOptions {
// std::vector<std::string> foo;
// };
// ArgsParser<MyOptions> parser;
// parser.AddSwitch("foo", 'f', kFooHelp, &MyOptions::foo);
void AddSwitch(const char* long_name,
const char short_name, const char* help,
std::vector<std::string> ResultStruct::*value,
StringCallback validator = nullptr) {
AddGeneralSwitch(
long_name, short_name, help,
[this, value, validator](const std::string& v) -> Status {
if (validator != nullptr) {
Status status = validator(v);
if (status.has_error()) {
return status;
}
}
(result_.*value).push_back(v);
return Status::Ok();
});
}
// Parses the given command line, returning the success.
//
// Example
// MyOptions options;
// std::vector<std::string> params;
// Status status = parser.Parse(argc, argv, &options, &params);
// if (status.has_error())
// <print error and return>
// <use options and params>
Status Parse(int argc, const char* argv[], ResultStruct* options,
std::vector<std::string>* params) {
Status status = GeneralArgsParser::ParseGeneral(argc, argv, params);
if (status.has_error())
return status;
*options = std::move(result_);
return Status::Ok();
}
private:
// Make it harder to accidentally call the base class' parse function since
// this won't return any options.
Status ParseGeneral(int argc, const char* argv[],
std::vector<std::string>* params) const = delete;
// Collects the values while Parse() is running. This needs to be a member
// because the lambdas registered for the ArgsParser reference into
// it.
ResultStruct result_;
};
} // namespace cmdline
#endif // CMDLINE_ARGS_PARSER_H_