mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 20:40:11 +01:00
Add a rich command-line argument parsing library. (#2978)
This library is designed around supporting the kinds of use cases we expect in the toolchain and other Carbon tools. It supports subcommands, options, and prints help. There is still a decent chunk of work to be done to finish polishing this, but it should give us a solid starting point. For details about the library and a brief roadmap, see the main comment in `command_line.h` which provides a comprehensive overview of the library and a roadmap of the remaining work. Porting the driver to this went fairly well, but did require some changes. That port is separated into a follow-up PR #2979. --------- Co-authored-by: Jon Ross-Perkins <jperkins@google.com> Co-authored-by: Lucile Rose Nihlen <luci.the.rose@gmail.com> Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
co-authored by
Jon Ross-Perkins
Lucile Rose Nihlen
Richard Smith
parent
09242eeebd
commit
db91db9097
@@ -12,6 +12,29 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "command_line",
|
||||
srcs = ["command_line.cpp"],
|
||||
hdrs = ["command_line.h"],
|
||||
deps = [
|
||||
":check",
|
||||
":ostream",
|
||||
"@llvm-project//llvm:Support",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "command_line_test",
|
||||
srcs = ["command_line_test.cpp"],
|
||||
deps = [
|
||||
":command_line",
|
||||
"//testing/base:gtest_main",
|
||||
"//testing/base:test_raw_ostream",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@llvm-project//llvm:Support",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "check",
|
||||
srcs = [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,856 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#ifndef CARBON_COMMON_COMMAND_LINE_H_
|
||||
#define CARBON_COMMON_COMMAND_LINE_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "common/check.h"
|
||||
#include "common/ostream.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/PointerIntPair.h"
|
||||
#include "llvm/ADT/STLForwardCompat.h"
|
||||
#include "llvm/ADT/Sequence.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
|
||||
// # Command-line argument parsing library.
|
||||
//
|
||||
// This is a collection of tools to describe both simple and reasonably complex
|
||||
// command line interfaces, and parse arguments based on those descriptions. It
|
||||
// optimizes for command line tools that will parse arguments exactly once per
|
||||
// execution, and specifically tools that make heavy use of subcommand-style
|
||||
// command line interfaces.
|
||||
//
|
||||
// ## Terminology used by this library
|
||||
//
|
||||
// _Argument_ or _arg_: One of the parsed components of the command line.
|
||||
//
|
||||
// _Option_: A _named_ argument starting with `--` to configure some aspect of
|
||||
// the tool. These often have both long spellings that start with `--` and
|
||||
// short, single character spellings that start with `-` and can be bundled with
|
||||
// other single character options.
|
||||
//
|
||||
// _Flag_: A boolean or binary option. These can only be enabled or disabled.
|
||||
//
|
||||
// _Positional argument_: An argument that is not named but is identified based
|
||||
// on the order in which it is encountered in the command line, with options
|
||||
// removed. Only a leaf command can contain positional arguments.
|
||||
//
|
||||
// _Value_: The value parameter provided to an argument. For options, this is
|
||||
// provided after an `=` and the option name. For positional arguments, the
|
||||
// value is the only thing provided. The string of the value is parsed according
|
||||
// to the kind of argument with fairly simple rules. See the argument builders
|
||||
// for more details.
|
||||
//
|
||||
// _Command_: The container of options, subcommands, and positional arguments to
|
||||
// parse and an action to take when successful.
|
||||
//
|
||||
// _Leaf command_: A command that doesn't contain subcommands. This is the only
|
||||
// kind of command that can contain positional arguments.
|
||||
//
|
||||
// _Subcommand_: A command nested within another command and identified by a
|
||||
// specific name that ends the parsing of arguments based on the parent and
|
||||
// switches to parse based on the specific subcommand's options and positional
|
||||
// arguments. A command with subcommands cannot parse positional arguments.
|
||||
//
|
||||
// _Action_: An open-ended callback, typically reflecting a specific subcommand
|
||||
// being parsed. Can either directly perform the operation or simply mark which
|
||||
// operation was selected.
|
||||
//
|
||||
// _Meta action_: An action fully handled by argument parsing such as displaying
|
||||
// help, version information, or completion.
|
||||
//
|
||||
// Example command to illustrate the different components:
|
||||
//
|
||||
// git --no-pager clone --shared --filter=blob:none my-repo my-directory
|
||||
//
|
||||
// This command breaks down as:
|
||||
// - `git`: The top-level command.
|
||||
// - `--no-pager`: A negated flag on the top-level command (`git`).
|
||||
// - `clone`: A subcommand.
|
||||
// - `--shared`: A positive flag for the subcommand (`clone`).
|
||||
// - `--filter=blob:none`: An option named `filter` with a value `blob:none`.
|
||||
// - `my-repo`: The first positional argument to the subcommand.
|
||||
// - `my-directory`: the second positional argument to the subcommand.
|
||||
//
|
||||
// **Note:** while the example uses a `git` command to be relatively familiar
|
||||
// and well documented, this library does not support the same flag syntaxes as
|
||||
// `git` or use anything similar for its subcommand dispatch. This example is
|
||||
// just to help clarify the terminology used, and carefully chosen to only use a
|
||||
// syntax that overlaps with this library's parsed syntax.
|
||||
//
|
||||
// ## Options and flags
|
||||
//
|
||||
// The option syntax and behavior supported by this library is designed to be
|
||||
// strict and relatively simple while still supporting a wide range of expected
|
||||
// use cases:
|
||||
//
|
||||
// - All options must have a unique long name that is accessed with a `--`
|
||||
// prefix. The name must consist of characters in the set [-a-zA-Z0-9], and it
|
||||
// must not start with a `-` or `no-`.
|
||||
//
|
||||
// - Values are always attached using an `=` after the name. Only a few simple
|
||||
// value formats are supported:
|
||||
// - Arbitrary strings
|
||||
// - Integers as parsed by `llvm::StringRef` and whose value fits in an
|
||||
// `int`.
|
||||
// - One of a fixed set of strings
|
||||
//
|
||||
// - Options may be parsed multiple times, and the behavior can be configured:
|
||||
// - Each time, they can set a new value, overwriting any previous.
|
||||
// - They can append the value to a container.
|
||||
// - TODO: They can increment a count.
|
||||
//
|
||||
// - Options may have a default value that will be synthesized even if they do
|
||||
// not occur in the parsed command line.
|
||||
//
|
||||
// - Flags (boolean options) have some special rules.
|
||||
// - They may be spelled normally, and default to setting that flag to `true`.
|
||||
// - They may also accept a value of either exactly `true` or `false` and that
|
||||
// is the value.
|
||||
// - They may be spelled with a `no-` prefix, such as `--no-verbose`, and that
|
||||
// is exactly equivalent to `--verbose=false`.
|
||||
// - For flags with a default `true` value, they are rendered in help using
|
||||
// the `no-` prefix.
|
||||
//
|
||||
// - Options may additionally have a short name of a single character [a-zA-Z].
|
||||
// - There is no distinction between the behavior of long and short names.
|
||||
// - The short name can only specify the positive or `true` value for flags.
|
||||
// There is no negative form of short names.
|
||||
// - Short names are parsed after a single `-`, such as `-v`.
|
||||
// - Any number of short names for boolean flags or options with default
|
||||
// values may be grouped after `-`, such as `-xyz`: this is three options,
|
||||
// `x`, `y`, and `z`.
|
||||
// - Short options may include a value after an `=`, but not when grouped with
|
||||
// other short options.
|
||||
//
|
||||
// - Options are parsed from any argument until either a subcommand switches to
|
||||
// that subcommand's options or a `--` argument ends all option parsing to
|
||||
// allow arguments that would be ambiguous with the option syntax.
|
||||
//
|
||||
// ## Subcommands
|
||||
//
|
||||
// Subcommands can be nested to any depth, and each subcommand gets its own
|
||||
// option space.
|
||||
//
|
||||
// ## Positional arguments
|
||||
//
|
||||
// Leaf commands (and subcommands) can accept positional arguments. These work
|
||||
// similar to options but consist *only* of the value. They have names, but the
|
||||
// name is only used in documentation and error messages. Except for the special
|
||||
// case of the exact argument `-`, positional argument values cannot start with
|
||||
// a `-` character until after option parsing is ended with a `--` argument.
|
||||
//
|
||||
// Singular positional arguments store the single value in that position.
|
||||
// Appending positional arguments append all of the values in sequence.
|
||||
//
|
||||
// Multiple positional arguments to a single command are parsed in sequence. For
|
||||
// appending positional arguments, the sequence of values appended is ended with
|
||||
// a `--` argument. For example, a command that takes two sequences of
|
||||
// positional arguments might look like:
|
||||
//
|
||||
// my-diff-tool a.txt b.txt c.txt -- new-a.txt new-b.txt new-c.txt
|
||||
//
|
||||
// The `--` used in this way *both* ends option parsing and the positional
|
||||
// argument sequence. Note that if there are no positional arguments prior to
|
||||
// the first `--`, then it will just end option parsing. To pass an empty
|
||||
// sequence of positional arguments two `--` arguments would be required. Once
|
||||
// option parsing is ended, even a single positional argument can be skipped
|
||||
// using a `--` argument without a positional argument.
|
||||
//
|
||||
// ## Help text blocks and rendering
|
||||
//
|
||||
// At many points in this library, a block of text is specified as a string.
|
||||
// These should be written using multi-line raw string literals that start on
|
||||
// their own line such as:
|
||||
//
|
||||
// ```cpp
|
||||
// ...
|
||||
// .help = R"""(
|
||||
// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
// tempor incididunt ut labore et dolore magna aliqua.
|
||||
// )""",
|
||||
// ```
|
||||
//
|
||||
// The code using these blocks will remove leading and trailing newlines. TODO:
|
||||
// It should also reflow all of the text in a way that tries to be roughly
|
||||
// similar to how Markdown would be reflowed when rendered:
|
||||
//
|
||||
// - Blank lines will be preserved.
|
||||
// - (TODO) A best effort to detect lists and other common Markdown constructs
|
||||
// and preserve the newlines between those.
|
||||
// - (TODO) Fenced regions will be preserved exactly.
|
||||
//
|
||||
// The remaining blocks will have all newlines removed when there is no column
|
||||
// width information on the output stream, or will be re-flowed to the output
|
||||
// stream's column width when available.
|
||||
//
|
||||
// ## The `Args` class
|
||||
//
|
||||
// The `Args` class is not a meaningful type, it serves two purposes: to give a
|
||||
// structured name prefix to the inner types, and to allow marking many
|
||||
// components `private` and using `friend` to grant access to implementation
|
||||
// details.
|
||||
//
|
||||
// ## Roadmap / TODOs / planned future work
|
||||
//
|
||||
// - Detect column width when the stream is a terminal and re-flow text.
|
||||
// - Implement short help display mode (and enable that by default).
|
||||
// - Add help text to one-of values and render it when present.
|
||||
// - Add formatting when the stream supports it (colors, bold, etc).
|
||||
// - Improve error messages when parsing fails.
|
||||
// - Reliably display the most appropriate usage string.
|
||||
// - Suggest typo corrections?
|
||||
// - Print snippet or otherwise render the failure better?
|
||||
// - Add support for responding to shell autocomplete queries.
|
||||
// - Finish adding support for setting and printing version information.
|
||||
// - Add short option counting support (`-vvv` -> `--verbose=3`).
|
||||
//
|
||||
namespace Carbon::CommandLine {
|
||||
|
||||
// Forward declare some implementation detail classes and classes that are
|
||||
// friended.
|
||||
struct Arg;
|
||||
struct Command;
|
||||
class MetaPrinter;
|
||||
class Parser;
|
||||
class CommandBuilder;
|
||||
|
||||
// The result of parsing arguments can be a parse error, a successfully parsed
|
||||
// command line, or a meta-success due to triggering a meta-action during the
|
||||
// parse such as rendering help text.
|
||||
enum class ParseResult {
|
||||
// Signifies an error parsing arguments. It will have been diagnosed using
|
||||
// the streams provided to the parser, and no useful parsed arguments are
|
||||
// available.
|
||||
Error,
|
||||
|
||||
// Signifies that program arguments were successfully parsed and can be
|
||||
// used.
|
||||
Success,
|
||||
|
||||
// Signifies a successful meta operation such as displaying help text
|
||||
// was performed. No parsed arguments are available, and the side-effects
|
||||
// have been directly provided via the streams provided to the parser.
|
||||
MetaSuccess,
|
||||
};
|
||||
auto operator<<(llvm::raw_ostream& output, ParseResult result)
|
||||
-> llvm::raw_ostream&;
|
||||
|
||||
// Actions are stored in data structures so we use an owning closure to model
|
||||
// them.
|
||||
using ActionT = std::function<void()>;
|
||||
|
||||
// The core argument info used to render help and other descriptive
|
||||
// information. This is used for both options and positional arguments.
|
||||
// Commands use an extended version below.
|
||||
struct ArgInfo {
|
||||
// The name of the argument. For options, this is the long name parsed after
|
||||
// `--`. Option names must also be unique. Conventionally, these are spelled
|
||||
// in lower case with a single dash separating words as that tends to be
|
||||
// especially easy to type and reasonably easy to read.
|
||||
//
|
||||
// For positional arguments this is only used for help text, and is often
|
||||
// spelled with `ALL-CAPS` to make it clear that it is a placeholder name
|
||||
// and does not appear in the actual command line.
|
||||
llvm::StringRef name;
|
||||
|
||||
// Optional short name for options. This must consist of a single character
|
||||
// in the set [a-zA-Z].
|
||||
llvm::StringRef short_name = "";
|
||||
|
||||
// For options, it is sometimes useful to render a distinct value
|
||||
// placeholder name to help clarify what the value should contain. These are
|
||||
// often in `ALL-CAPS` to make it clear they are placeholders. For example,
|
||||
// an `output` option might set this to `FILE` so it renders as
|
||||
// `--output=FILE`.
|
||||
llvm::StringRef value_name = "";
|
||||
|
||||
// A long-form help string that will be rendered for this argument in
|
||||
// command help strings. It supports multiple lines and is rendered as a
|
||||
// text block described in the `Args` top-level comment.
|
||||
llvm::StringRef help = "";
|
||||
|
||||
// A short help string for the argument. This should be as short as
|
||||
// possible, and always fit easily on a single line. Ideally, it can fit in
|
||||
// a narrow column of a single line and is in the range of 40 columns wide.
|
||||
//
|
||||
// If not provided, the first paragraph (up to a blank line) from `help`
|
||||
// will be used.
|
||||
llvm::StringRef help_short = "";
|
||||
};
|
||||
|
||||
// The kinds of arguments that can be parsed.
|
||||
enum class ArgKind {
|
||||
Invalid,
|
||||
Flag,
|
||||
Integer,
|
||||
String,
|
||||
OneOf,
|
||||
MetaActionOnly,
|
||||
};
|
||||
auto operator<<(llvm::raw_ostream& output, ArgKind kind) -> llvm::raw_ostream&;
|
||||
|
||||
// A builder used to configure an argument for parsing.
|
||||
class ArgBuilder {
|
||||
public:
|
||||
// When marked as required, if an argument is not provided explicitly in the
|
||||
// command line the parse will produce an error.
|
||||
void Required(bool is_required);
|
||||
|
||||
// An argument can be hidden from the help output.
|
||||
void HelpHidden(bool is_help_hidden);
|
||||
|
||||
// Sets a meta-action to run when this argument is parsed. This is used to
|
||||
// set up arguments like `--help` or `--version` that can be entirely
|
||||
// handled during parsing and rather than produce parsed information about
|
||||
// the command line, override that for some custom behavior.
|
||||
template <typename T>
|
||||
void MetaAction(T action);
|
||||
|
||||
protected:
|
||||
friend CommandBuilder;
|
||||
explicit ArgBuilder(Arg& arg);
|
||||
|
||||
Arg& arg_;
|
||||
};
|
||||
|
||||
// Customized argument builder when the value is a boolean flag.
|
||||
class FlagBuilder : public ArgBuilder {
|
||||
public:
|
||||
// Flags can be defaulted to true. However, flags always have *some*
|
||||
// default, this merely customizes which value is default. If uncustomized,
|
||||
// the default of a flag is false.
|
||||
void Default(bool flag_value);
|
||||
|
||||
// Configures the argument to store a parsed value in the provided storage.
|
||||
//
|
||||
// This must be called on the builder.
|
||||
void Set(bool* flag);
|
||||
|
||||
private:
|
||||
using ArgBuilder::ArgBuilder;
|
||||
};
|
||||
|
||||
// Customized argument builder when the value is an integer.
|
||||
class IntegerArgBuilder : public ArgBuilder {
|
||||
public:
|
||||
// Sets a default for the argument with the provided value. There is no
|
||||
// default for integer arguments unless this is called.
|
||||
//
|
||||
// For arguments that are `Set` below, this value will be stored even if the
|
||||
// argument is never explicitly provided. For arguments that are `Append`ed
|
||||
// below, this value will be used whenever the argument occurs without an
|
||||
// explicit value, but unless the argument is parsed nothing will be
|
||||
// appended.
|
||||
void Default(int integer_value);
|
||||
|
||||
// Configures the argument to store a parsed value in the provided storage.
|
||||
// Each time the argument is parsed, it will write a new value to this
|
||||
// storage.
|
||||
//
|
||||
// Exactly one of this method or `Append` below must be configured for the
|
||||
// argument.
|
||||
void Set(int* integer);
|
||||
|
||||
// Configures the argument to append a parsed value to the provided
|
||||
// container. Each time the argument is parsed, a new value will be
|
||||
// appended.
|
||||
//
|
||||
// Exactly one of this method or `Set` above must be configured for the
|
||||
// argument.
|
||||
void Append(llvm::SmallVectorImpl<int>* sequence);
|
||||
|
||||
private:
|
||||
using ArgBuilder::ArgBuilder;
|
||||
};
|
||||
|
||||
// Customized argument builder when the value is a string.
|
||||
class StringArgBuilder : public ArgBuilder {
|
||||
public:
|
||||
// Sets a default for the argument with the provided value. There is no
|
||||
// default for string arguments unless this is called.
|
||||
//
|
||||
// For arguments that are `Set` below, this value will be stored even if the
|
||||
// argument is never explicitly provided. For arguments that are `Append`ed
|
||||
// below, this value will be used whenever the argument occurs without an
|
||||
// explicit value, but unless the argument is parsed nothing will be
|
||||
// appended.
|
||||
void Default(llvm::StringRef string_value);
|
||||
|
||||
// Configures the argument to store a parsed value in the provided storage.
|
||||
// Each time the argument is parsed, it will write a new value to this
|
||||
// storage.
|
||||
//
|
||||
// Exactly one of this method or `Append` below must be configured for the
|
||||
// argument.
|
||||
void Set(llvm::StringRef* string);
|
||||
|
||||
// Configures the argument to append a parsed value to the provided
|
||||
// container. Each time the argument is parsed, a new value will be
|
||||
// appended.
|
||||
//
|
||||
// Exactly one of this method or `Set` above must be configured for the
|
||||
// argument.
|
||||
void Append(llvm::SmallVectorImpl<llvm::StringRef>* sequence);
|
||||
|
||||
private:
|
||||
using ArgBuilder::ArgBuilder;
|
||||
};
|
||||
|
||||
// Customized argument builder when the value is required to be one of a fixed
|
||||
// set of possible strings, and each one maps to a specific value of some
|
||||
// type, often an enumerator.
|
||||
class OneOfArgBuilder : public ArgBuilder {
|
||||
public:
|
||||
// A tiny helper / builder type for describing one of the possible values
|
||||
// that a particular argument accepts.
|
||||
//
|
||||
// Beyond carrying the string, type, and value for this candidate, it also
|
||||
// allows marking the candidate as the default.
|
||||
template <typename T>
|
||||
class OneOfValueT {
|
||||
public:
|
||||
// Configure whether a candidate is the default. If not called, it is not
|
||||
// the default. This can only be used when setting, not when appending.
|
||||
auto Default(bool is_default) && -> OneOfValueT;
|
||||
|
||||
private:
|
||||
friend OneOfArgBuilder;
|
||||
|
||||
explicit OneOfValueT(llvm::StringRef str, T value);
|
||||
|
||||
llvm::StringRef str;
|
||||
T value;
|
||||
bool is_default = false;
|
||||
};
|
||||
|
||||
// A helper function to create an object that models one of the candidate
|
||||
// values for this argument. It takes the string used to select this value
|
||||
// on the command line, deduces the type of the value, and accepts the value
|
||||
// itself that should be used when this candidate is parsed.
|
||||
template <typename T>
|
||||
static auto OneOfValue(llvm::StringRef string, T value) -> OneOfValueT<T>;
|
||||
|
||||
// Configures the argument to store a parsed value in the provided storage.
|
||||
// Each time the argument is parsed, it will write a new value to this
|
||||
// storage.
|
||||
//
|
||||
// Exactly one of this method or `AppendOneOf` below must be configured for
|
||||
// the argument.
|
||||
//
|
||||
// For one-of arguments, this method also provides an array of possible
|
||||
// values that may be used:
|
||||
//
|
||||
// ```cpp
|
||||
// arg_b.SetOneOf(
|
||||
// {
|
||||
// arg_b.OneOfValue("x", 1),
|
||||
// arg_b.OneOfValue("y", 2),
|
||||
// arg_b.OneOfValue("z", 3).Default(true),
|
||||
// },
|
||||
// &value);
|
||||
// ```
|
||||
//
|
||||
// The only value strings that will be parsed are those described in the
|
||||
// array, and the value stored in the storage for a particular parsed value
|
||||
// string is the one associated with it. There must be a single homogeneous
|
||||
// type for the all of the candidate values and that type must be storable
|
||||
// into the result storage pointee type. At most one of the array can also
|
||||
// be marked as a default value, which will make specifying a value
|
||||
// optional, and also will cause that value to be stored into the result
|
||||
// even if the argument is not parsed explicitly.
|
||||
template <typename T, typename U, size_t N>
|
||||
void SetOneOf(const OneOfValueT<U> (&values)[N], T* result);
|
||||
|
||||
// Configures the argument to append a parsed value to the provided
|
||||
// container. Each time the argument is parsed, a new value will be
|
||||
// appended.
|
||||
//
|
||||
// Exactly one of this method or `SetOneOf` above must be configured for the
|
||||
// argument.
|
||||
//
|
||||
// Similar to `SetOneOf`, this must also describe the candidate value
|
||||
// strings that can be parsed and the consequent values that are used for
|
||||
// those strings:
|
||||
//
|
||||
// ```cpp
|
||||
// arg_b.AppendOneOf(
|
||||
// {
|
||||
// arg_b.OneOfValue("x", 1),
|
||||
// arg_b.OneOfValue("y", 2),
|
||||
// arg_b.OneOfValue("z", 3),
|
||||
// },
|
||||
// &values);
|
||||
// ```
|
||||
//
|
||||
// Instead of storing, these values are appended.
|
||||
//
|
||||
// However, appending one-of arguments cannot use a default. The values must
|
||||
// always be explicitly parsed.
|
||||
template <typename T, typename U, size_t N>
|
||||
void AppendOneOf(const OneOfValueT<U> (&values)[N],
|
||||
llvm::SmallVectorImpl<T>* sequence);
|
||||
|
||||
private:
|
||||
using ArgBuilder::ArgBuilder;
|
||||
|
||||
template <typename U, size_t N, typename MatchT, size_t... Indices>
|
||||
void OneOfImpl(const OneOfValueT<U> (&input_values)[N], MatchT match,
|
||||
std::index_sequence<Indices...> /*indices*/);
|
||||
};
|
||||
|
||||
// The extended info for a command, including for a subcommand.
|
||||
//
|
||||
// This provides the primary descriptive information for commands used by help
|
||||
// and other diagnostic messages. For subcommands, it also provides the name
|
||||
// used to access the subcommand.
|
||||
struct CommandInfo {
|
||||
// The name of the command. For subcommands, this is also the argument
|
||||
// spelling that accesses this subcommand. It must consist of characters in
|
||||
// the set [-a-zA-Z0-9], and must not start with a `-`.
|
||||
llvm::StringRef name;
|
||||
|
||||
// An optional version string that will be rendered as part of version meta
|
||||
// action by the library. While this library does not impose a machine
|
||||
// parsable structure, users will expect this to be extractable and parsable
|
||||
// in practice.
|
||||
//
|
||||
// Subcommands with an empty version will inherit the first non-empty parent
|
||||
// version.
|
||||
//
|
||||
// Whether a (possibly inherited) version string is non-empty determines
|
||||
// whether this library provides the version-printing meta actions via a
|
||||
// `--version` flag or, if there are subcommands, a `version` subcommand.
|
||||
llvm::StringRef version = "";
|
||||
|
||||
// Optional build information to include when printing the version.
|
||||
llvm::StringRef build_info = "";
|
||||
|
||||
// An optional long-form help string for the command.
|
||||
//
|
||||
// When accessing a command's dedicated help output, this will form the main
|
||||
// prose output at the beginning of the help message. When listing
|
||||
// subcommands, the subcommand's `help` string will be used to describe it
|
||||
// in the list.
|
||||
//
|
||||
// The help meta actions are available regardless of whether this is
|
||||
// provided in order to mechanically describe other aspects of the command.
|
||||
//
|
||||
// This field supports multiple lines and uses the text block handling
|
||||
// described in the top-level `Args` comment.
|
||||
llvm::StringRef help = "";
|
||||
|
||||
// An optional short help string for the command.
|
||||
//
|
||||
// This should be very short, at most one line and ideally fitting within a
|
||||
// narrow column of a line. If left blank, the first paragraph of text (up
|
||||
// to the first blank line) in the `help` string will be used.
|
||||
llvm::StringRef help_short = "";
|
||||
|
||||
// An optional custom block of text to describe the usage of the command.
|
||||
//
|
||||
// The usage should just be a series of lines with possible invocations of
|
||||
// the command summarized as briefly as possible. Ideally, each variation on
|
||||
// a single line. The goal is to show the *structure* of different command
|
||||
// invocations, not to be comprehensive.
|
||||
//
|
||||
// When omitted, the library will generate these based on the parsing logic.
|
||||
// The generated usage is expected to be suitable for the vast majority of
|
||||
// users, but can be customized in cases where necessary.
|
||||
llvm::StringRef usage = "";
|
||||
|
||||
// An optional epilogue multi-line block of text appended to the help display
|
||||
// for this command. It is only used for this command's dedicated help, but
|
||||
// can contain extra, custom guidance that is especially useful to have at
|
||||
// the very end of the output.
|
||||
llvm::StringRef help_epilogue = "";
|
||||
};
|
||||
|
||||
// Commands are classified based on the action they result in when run.
|
||||
//
|
||||
// Commands that require a subcommand have no action and are just a means to
|
||||
// reach the subcommand actions.
|
||||
//
|
||||
// Commands with _meta_ actions are also a separate kind from those with
|
||||
// normal actions.
|
||||
enum class CommandKind {
|
||||
Invalid,
|
||||
RequiresSubcommand,
|
||||
Action,
|
||||
MetaAction,
|
||||
};
|
||||
auto operator<<(llvm::raw_ostream& output, CommandKind kind)
|
||||
-> llvm::raw_ostream&;
|
||||
|
||||
// Builder used to configure a command to parse.
|
||||
//
|
||||
// An instance of this is used to configure the top-level command, and a fresh
|
||||
// instance is provided for configuring each subcommand as well.
|
||||
class CommandBuilder {
|
||||
public:
|
||||
using Kind = CommandKind;
|
||||
|
||||
void AddFlag(const ArgInfo& info,
|
||||
llvm::function_ref<void(FlagBuilder&)> build);
|
||||
void AddIntegerOption(const ArgInfo& info,
|
||||
llvm::function_ref<void(IntegerArgBuilder&)> build);
|
||||
void AddStringOption(const ArgInfo& info,
|
||||
llvm::function_ref<void(StringArgBuilder&)> build);
|
||||
void AddOneOfOption(const ArgInfo& info,
|
||||
llvm::function_ref<void(OneOfArgBuilder&)> build);
|
||||
void AddMetaActionOption(const ArgInfo& info,
|
||||
llvm::function_ref<void(ArgBuilder&)> build);
|
||||
|
||||
void AddIntegerPositionalArg(
|
||||
const ArgInfo& info, llvm::function_ref<void(IntegerArgBuilder&)> build);
|
||||
void AddStringPositionalArg(
|
||||
const ArgInfo& info, llvm::function_ref<void(StringArgBuilder&)> build);
|
||||
void AddOneOfPositionalArg(const ArgInfo& info,
|
||||
llvm::function_ref<void(OneOfArgBuilder&)> build);
|
||||
|
||||
void AddSubcommand(const CommandInfo& info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build);
|
||||
|
||||
// Subcommands can be hidden from the help listing of their parents with
|
||||
// this setting. Hiding a subcommand doesn't disable its own help, it just
|
||||
// removes it from the listing.
|
||||
void HelpHidden(bool is_help_hidden);
|
||||
|
||||
// Exactly one of these three should be called to select and configure the
|
||||
// kind of the built command.
|
||||
void RequiresSubcommand();
|
||||
void Do(ActionT action);
|
||||
void Meta(ActionT meta_action);
|
||||
|
||||
private:
|
||||
friend Parser;
|
||||
|
||||
explicit CommandBuilder(Command& command, MetaPrinter& meta_printer);
|
||||
|
||||
auto AddArgImpl(const ArgInfo& info, ArgKind kind) -> Arg&;
|
||||
void AddPositionalArgImpl(const ArgInfo& info, ArgKind kind,
|
||||
llvm::function_ref<void(Arg&)> build);
|
||||
void Finalize();
|
||||
|
||||
Command& command_;
|
||||
MetaPrinter& meta_printer_;
|
||||
|
||||
llvm::SmallDenseSet<llvm::StringRef> arg_names_;
|
||||
llvm::SmallDenseSet<llvm::StringRef> subcommand_names_;
|
||||
};
|
||||
|
||||
// Builds a description of a command and then parses the provided arguments
|
||||
// for that command.
|
||||
//
|
||||
// This is the main entry point to both build up the description of the
|
||||
// command whose arguments are being parsed and to do the parsing. Everything
|
||||
// is done in a single invocation as the common case is to build a command
|
||||
// description, parse the arguments once, and then run with that
|
||||
// configuration.
|
||||
//
|
||||
// The `out` stream is treated like `stdout` would be for a Unix-style command
|
||||
// line tool, and `errors` like `stderr`: any errors or diagnostic information
|
||||
// are printed to `errors`, but meta-actions like printing a command's help go
|
||||
// to `out`.
|
||||
auto Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args,
|
||||
llvm::raw_ostream& out, llvm::raw_ostream& errors,
|
||||
const CommandInfo& command_info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build) -> ParseResult;
|
||||
|
||||
// Implementation details only below.
|
||||
|
||||
// The internal representation of a parsable argument description.
|
||||
struct Arg {
|
||||
using Kind = ArgKind;
|
||||
using ValueActionT =
|
||||
std::function<bool(const Arg& arg, llvm::StringRef value_string)>;
|
||||
using DefaultActionT = std::function<void(const Arg& arg)>;
|
||||
|
||||
explicit Arg(const ArgInfo& info);
|
||||
~Arg();
|
||||
|
||||
ArgInfo info;
|
||||
Kind kind = Kind::Invalid;
|
||||
bool has_default = false;
|
||||
bool is_required = false;
|
||||
bool is_append = false;
|
||||
bool is_help_hidden = false;
|
||||
|
||||
// Meta action storage, only populated if this argument causes a meta action.
|
||||
ActionT meta_action;
|
||||
|
||||
// Storage options depending on the kind.
|
||||
union {
|
||||
// Singular argument storage pointers.
|
||||
bool* flag_storage;
|
||||
int* integer_storage;
|
||||
llvm::StringRef* string_storage;
|
||||
|
||||
// Appending argument storage pointers.
|
||||
llvm::SmallVectorImpl<int>* integer_sequence;
|
||||
llvm::SmallVectorImpl<llvm::StringRef>* string_sequence;
|
||||
|
||||
// One-of information.
|
||||
struct {
|
||||
llvm::OwningArrayRef<llvm::StringRef> value_strings;
|
||||
ValueActionT value_action;
|
||||
};
|
||||
};
|
||||
|
||||
// Default values depending on the kind.
|
||||
union {
|
||||
bool default_flag;
|
||||
int default_integer;
|
||||
llvm::StringRef default_string;
|
||||
struct {
|
||||
DefaultActionT default_action;
|
||||
int default_value_index;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// The internal representation of a parsable command description, including its
|
||||
// options, positional arguments, and subcommands.
|
||||
struct Command {
|
||||
using Kind = CommandBuilder::Kind;
|
||||
|
||||
explicit Command(const CommandInfo& info, Command* parent = nullptr);
|
||||
|
||||
CommandInfo info;
|
||||
Command* parent;
|
||||
ActionT action;
|
||||
Kind kind = Kind::Invalid;
|
||||
|
||||
bool is_help_hidden = false;
|
||||
|
||||
llvm::SmallVector<std::unique_ptr<Arg>> options;
|
||||
llvm::SmallVector<std::unique_ptr<Arg>> positional_args;
|
||||
llvm::SmallVector<std::unique_ptr<Command>> subcommands;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void ArgBuilder::MetaAction(T action) {
|
||||
CARBON_CHECK(!arg_.meta_action) << "Cannot set a meta action twice!";
|
||||
arg_.meta_action = std::move(action);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto OneOfArgBuilder::OneOfValueT<T>::Default(
|
||||
bool is_default) && -> OneOfValueT {
|
||||
OneOfValueT result = std::move(*this);
|
||||
result.is_default = is_default;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
OneOfArgBuilder::OneOfValueT<T>::OneOfValueT(llvm::StringRef str, T value)
|
||||
: str(str), value(std::move(value)) {}
|
||||
|
||||
template <typename T>
|
||||
auto OneOfArgBuilder::OneOfValue(llvm::StringRef str, T value)
|
||||
-> OneOfValueT<T> {
|
||||
return OneOfValueT<T>(str, value);
|
||||
}
|
||||
|
||||
template <typename T, typename U, size_t N>
|
||||
void OneOfArgBuilder::SetOneOf(const OneOfValueT<U> (&values)[N], T* result) {
|
||||
static_assert(N > 0, "Must include at least one value.");
|
||||
arg_.is_append = false;
|
||||
OneOfImpl(
|
||||
values, [result](T value) { *result = value; },
|
||||
std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
template <typename T, typename U, size_t N>
|
||||
void OneOfArgBuilder::AppendOneOf(const OneOfValueT<U> (&values)[N],
|
||||
llvm::SmallVectorImpl<T>* sequence) {
|
||||
static_assert(N > 0, "Must include at least one value.");
|
||||
arg_.is_append = true;
|
||||
OneOfImpl(
|
||||
values, [sequence](T value) { sequence->push_back(value); },
|
||||
std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
// An implementation tool for the one-of value candidate handling. Delegating to
|
||||
// this allows us to deduce a pack of indices from the array of candidates, and
|
||||
// then use that variadic pack to operate on the array in the variadic space.
|
||||
// This includes packaging the components up separately into our storage
|
||||
// representation, as well as processing the array to find and register any
|
||||
// default.
|
||||
//
|
||||
// The representation is especially tricky because we want all of the actual
|
||||
// values and even the *type* of values to be erased. We do that by building
|
||||
// lambdas that do the type-aware operations and storing those into type-erased
|
||||
// function objects.
|
||||
template <typename U, size_t N, typename MatchT, size_t... Indices>
|
||||
void OneOfArgBuilder::OneOfImpl(const OneOfValueT<U> (&input_values)[N],
|
||||
MatchT match,
|
||||
std::index_sequence<Indices...> /*indices*/) {
|
||||
std::array<llvm::StringRef, N> value_strings = {input_values[Indices].str...};
|
||||
std::array<U, N> values = {input_values[Indices].value...};
|
||||
|
||||
// Directly copy the value strings into a heap-allocated array in the
|
||||
// argument.
|
||||
new (&arg_.value_strings)
|
||||
llvm::OwningArrayRef<llvm::StringRef>(value_strings);
|
||||
|
||||
// And build a type-erased action that maps a specific value string to a value
|
||||
// by index.
|
||||
new (&arg_.value_action) Arg::ValueActionT(
|
||||
[values, match](const Arg& arg, llvm::StringRef value_string) -> bool {
|
||||
for (int i : llvm::seq<int>(0, N)) {
|
||||
if (value_string == arg.value_strings[i]) {
|
||||
match(values[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Fold over all the input values to see if there is a default.
|
||||
if ((input_values[Indices].is_default || ...)) {
|
||||
CARBON_CHECK(!arg_.is_append) << "Can't append default.";
|
||||
CARBON_CHECK((input_values[Indices].is_default + ... + 0) == 1)
|
||||
<< "Cannot default more than one value.";
|
||||
|
||||
arg_.has_default = true;
|
||||
|
||||
// First build a lambda that configures the default using an index. We'll
|
||||
// call this below, this lambda isn't the one that is stored.
|
||||
auto set_default = [&](size_t index, const auto& default_value) {
|
||||
// Now that we have the desired default index, build a lambda and store it
|
||||
// as the default action. This lambda is stored and so it captures the
|
||||
// necessary information explicitly and by value.
|
||||
new (&arg_.default_action)
|
||||
Arg::DefaultActionT([value = default_value.value,
|
||||
match](const Arg& /*arg*/) { match(value); });
|
||||
|
||||
// Also store the index itself for use when printing help.
|
||||
arg_.default_value_index = index;
|
||||
};
|
||||
|
||||
// Now we fold across the inputs and in the one case that is the default, we
|
||||
// call the lambda. This is just a somewhat awkward way to write a loop with
|
||||
// a condition in it over a pack.
|
||||
((input_values[Indices].is_default
|
||||
? set_default(Indices, input_values[Indices])
|
||||
: static_cast<void>(0)),
|
||||
...);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Carbon::CommandLine
|
||||
|
||||
#endif // CARBON_COMMON_COMMAND_LINE_H_
|
||||
@@ -0,0 +1,938 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "common/command_line.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "testing/base/test_raw_ostream.h"
|
||||
|
||||
namespace Carbon::Testing {
|
||||
namespace {
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Eq;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::Not;
|
||||
using ::testing::StrEq;
|
||||
|
||||
constexpr CommandLine::CommandInfo TestCommandInfo = {
|
||||
.name = "test",
|
||||
.help = "A test command.",
|
||||
.help_epilogue = "TODO",
|
||||
};
|
||||
|
||||
enum class TestEnum {
|
||||
Val1,
|
||||
Val2,
|
||||
};
|
||||
|
||||
enum class TestSubcommand {
|
||||
Sub1,
|
||||
Sub2,
|
||||
};
|
||||
|
||||
TEST(ArgParserTest, BasicCommand) {
|
||||
bool flag = false;
|
||||
int integer_option = -1;
|
||||
llvm::StringRef string_option = "";
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args) {
|
||||
return CommandLine::Parse(
|
||||
args, llvm::errs(), llvm::errs(), TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddIntegerOption({.name = "option1"},
|
||||
[&](auto& arg_b) { arg_b.Set(&integer_option); });
|
||||
b.AddStringOption({.name = "option2"},
|
||||
[&](auto& arg_b) { arg_b.Set(&string_option); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "--option2=value", "--option1=42"}),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("value"));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, BooleanFlags) {
|
||||
bool flag = false;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "--no-flag"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag", "--flag=false"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag=true"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag=true", "--no-flag"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--no-flag=true"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Cannot specify a value when using a flag name prefixed "
|
||||
"with 'no-' -- that prefix implies a value of 'false'.\n"));
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag=false"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Cannot specify a value when using a flag name prefixed "
|
||||
"with 'no-' -- that prefix implies a value of 'false'.\n"));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, ArgDefaults) {
|
||||
bool flag = false;
|
||||
int integer_option = -1;
|
||||
llvm::StringRef string_option = "";
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) {
|
||||
arg_b.Default(true);
|
||||
arg_b.Set(&flag);
|
||||
});
|
||||
b.AddIntegerOption({.name = "option1"}, [&](auto& arg_b) {
|
||||
arg_b.Default(7);
|
||||
arg_b.Set(&integer_option);
|
||||
});
|
||||
b.AddStringOption({.name = "option2"}, [&](auto& arg_b) {
|
||||
arg_b.Default("default");
|
||||
arg_b.Set(&string_option);
|
||||
});
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({}, llvm::errs()), Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(7));
|
||||
EXPECT_THAT(string_option, StrEq("default"));
|
||||
|
||||
EXPECT_THAT(parse({"--option1", "--option2"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(7));
|
||||
EXPECT_THAT(string_option, StrEq("default"));
|
||||
|
||||
EXPECT_THAT(parse({"--option1=42", "--option2=explicit"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("explicit"));
|
||||
|
||||
EXPECT_THAT(
|
||||
parse({"--option1=42", "--option2=explicit", "--option1", "--option2"},
|
||||
llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(7));
|
||||
EXPECT_THAT(string_option, StrEq("default"));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, ShortArgs) {
|
||||
bool flag = false;
|
||||
bool example = false;
|
||||
int integer_option = -1;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag", .short_name = "f"},
|
||||
[&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddFlag({.name = "example", .short_name = "x"},
|
||||
[&](auto& arg_b) { arg_b.Set(&example); });
|
||||
b.AddIntegerOption({.name = "option1", .short_name = "o"},
|
||||
[&](auto& arg_b) {
|
||||
arg_b.Default(123);
|
||||
arg_b.Set(&integer_option);
|
||||
});
|
||||
b.AddIntegerOption({.name = "option2", .short_name = "z"},
|
||||
[&](auto& arg_b) { arg_b.Set(&integer_option); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"-f", "-o=42", "-x"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_TRUE(example);
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
|
||||
flag = false;
|
||||
example = false;
|
||||
EXPECT_THAT(parse({"--option1=13", "-xfo"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_TRUE(example);
|
||||
EXPECT_THAT(integer_option, Eq(123));
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"-xzf"}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(),
|
||||
StrEq("ERROR: Option '-z' (short for '--option2') requires a "
|
||||
"value to be provided and none was.\n"));
|
||||
|
||||
EXPECT_THAT(parse({"-xz=123"}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Cannot provide a value to the group of multiple short "
|
||||
"options '-xz=...'; values must be provided to a single option, "
|
||||
"using either the short or long spelling.\n"));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, PositionalArgs) {
|
||||
bool flag = false;
|
||||
llvm::StringRef string_option = "";
|
||||
llvm::StringRef source_string;
|
||||
llvm::StringRef dest_string;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddStringOption({.name = "option"},
|
||||
[&](auto& arg_b) { arg_b.Set(&string_option); });
|
||||
b.AddStringPositionalArg({.name = "source"}, [&](auto& arg_b) {
|
||||
arg_b.Set(&source_string);
|
||||
arg_b.Required(true);
|
||||
});
|
||||
b.AddStringPositionalArg({.name = "dest"}, [&](auto& arg_b) {
|
||||
arg_b.Set(&dest_string);
|
||||
arg_b.Required(true);
|
||||
});
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--flag", "--option=x"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Not all required positional arguments were provided. First "
|
||||
"missing and required positional argument: 'source'\n"));
|
||||
|
||||
EXPECT_THAT(parse({"src", "--flag", "--option=value", "dst"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(string_option, StrEq("value"));
|
||||
EXPECT_THAT(source_string, StrEq("src"));
|
||||
EXPECT_THAT(dest_string, StrEq("dst"));
|
||||
|
||||
EXPECT_THAT(parse({"src2", "--", "dst2"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(source_string, StrEq("src2"));
|
||||
EXPECT_THAT(dest_string, StrEq("dst2"));
|
||||
|
||||
EXPECT_THAT(parse({"-", "--", "-"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(source_string, StrEq("-"));
|
||||
EXPECT_THAT(dest_string, StrEq("-"));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, PositionalAppendArgs) {
|
||||
bool flag = false;
|
||||
llvm::StringRef string_option = "";
|
||||
llvm::SmallVector<llvm::StringRef> source_strings;
|
||||
llvm::SmallVector<llvm::StringRef> dest_strings;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddStringOption({.name = "option"},
|
||||
[&](auto& arg_b) { arg_b.Set(&string_option); });
|
||||
b.AddStringPositionalArg({.name = "sources"}, [&](auto& arg_b) {
|
||||
arg_b.Append(&source_strings);
|
||||
arg_b.Required(true);
|
||||
});
|
||||
b.AddStringPositionalArg({.name = "dest"}, [&](auto& arg_b) {
|
||||
arg_b.Append(&dest_strings);
|
||||
arg_b.Required(true);
|
||||
});
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--flag", "--option=x"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Not all required positional arguments were provided. First "
|
||||
"missing and required positional argument: 'sources'\n"));
|
||||
|
||||
EXPECT_THAT(
|
||||
parse({"src1", "--flag", "src2", "--option=value", "--", "--dst--"},
|
||||
llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(string_option, StrEq("value"));
|
||||
EXPECT_THAT(source_strings, ElementsAre(StrEq("src1"), StrEq("src2")));
|
||||
EXPECT_THAT(dest_strings, ElementsAre(StrEq("--dst--")));
|
||||
|
||||
source_strings.clear();
|
||||
dest_strings.clear();
|
||||
EXPECT_THAT(
|
||||
parse({"--", "--src1--", "--src2--", "--", "dst1", "dst2"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(source_strings,
|
||||
ElementsAre(StrEq("--src1--"), StrEq("--src2--")));
|
||||
EXPECT_THAT(dest_strings, ElementsAre(StrEq("dst1"), StrEq("dst2")));
|
||||
|
||||
source_strings.clear();
|
||||
dest_strings.clear();
|
||||
EXPECT_THAT(parse({"--", "--", "dst1", "dst2"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(source_strings, ElementsAre());
|
||||
EXPECT_THAT(dest_strings, ElementsAre(StrEq("dst1"), StrEq("dst2")));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, BasicSubcommands) {
|
||||
bool flag = false;
|
||||
llvm::StringRef option1 = "";
|
||||
llvm::StringRef option2 = "";
|
||||
|
||||
TestSubcommand subcommand;
|
||||
bool subsub_command = false;
|
||||
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddSubcommand({.name = "sub1"}, [&](auto& sub_b) {
|
||||
sub_b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
sub_b.AddStringOption({.name = "option"},
|
||||
[&](auto& arg_b) { arg_b.Set(&option1); });
|
||||
sub_b.Do([&] { subcommand = TestSubcommand::Sub1; });
|
||||
});
|
||||
b.AddSubcommand({.name = "sub2"}, [&](auto& sub_b) {
|
||||
sub_b.AddStringOption({.name = "option"},
|
||||
[&](auto& arg_b) { arg_b.Set(&option1); });
|
||||
sub_b.AddSubcommand({.name = "subsub"}, [&](auto& subsub_b) {
|
||||
subsub_b.AddStringOption({.name = "option"},
|
||||
[&](auto& arg_b) { arg_b.Set(&option2); });
|
||||
subsub_b.Do([&] { subsub_command = true; });
|
||||
});
|
||||
sub_b.Do([&] { subcommand = TestSubcommand::Sub2; });
|
||||
});
|
||||
b.RequiresSubcommand();
|
||||
});
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: No subcommand specified. Available "
|
||||
"subcommands: 'sub1', 'sub2', or 'help'\n"));
|
||||
|
||||
EXPECT_THAT(parse({"--flag"}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Unknown option '--flag'\n"));
|
||||
|
||||
EXPECT_THAT(parse({"sub3"}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Invalid subcommand 'sub3'. Available "
|
||||
"subcommands: 'sub1', 'sub2', or 'help'\n"));
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "sub1"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Unknown option '--flag'\n"));
|
||||
|
||||
EXPECT_THAT(parse({"sub1", "--flag"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(subcommand, Eq(TestSubcommand::Sub1));
|
||||
EXPECT_TRUE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"sub2", "--option=value"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(subcommand, Eq(TestSubcommand::Sub2));
|
||||
EXPECT_THAT(option1, StrEq("value"));
|
||||
|
||||
EXPECT_THAT(parse({"sub2", "--option=abc", "subsub42", "--option=xyz"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(),
|
||||
StrEq("ERROR: Invalid subcommand 'subsub42'. Available "
|
||||
"subcommands: 'subsub', or 'help'\n"));
|
||||
|
||||
EXPECT_THAT(
|
||||
parse({"sub2", "--option=abc", "subsub", "--option=xyz"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_TRUE(subsub_command);
|
||||
EXPECT_THAT(option1, StrEq("abc"));
|
||||
EXPECT_THAT(option2, StrEq("xyz"));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, Appending) {
|
||||
llvm::SmallVector<int> integers;
|
||||
llvm::SmallVector<llvm::StringRef> strings;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddIntegerOption({.name = "int"},
|
||||
[&](auto& arg_b) { arg_b.Append(&integers); });
|
||||
b.AddStringOption({.name = "str"},
|
||||
[&](auto& arg_b) { arg_b.Append(&strings); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({}, llvm::errs()), Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(integers, ElementsAre());
|
||||
EXPECT_THAT(strings, ElementsAre());
|
||||
|
||||
EXPECT_THAT(parse({"--int=1", "--int=2", "--int=3"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(integers, ElementsAre(Eq(1), Eq(2), Eq(3)));
|
||||
EXPECT_THAT(strings, ElementsAre());
|
||||
|
||||
EXPECT_THAT(parse({"--str=a", "--str=b", "--str=c"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(integers, ElementsAre(Eq(1), Eq(2), Eq(3)));
|
||||
EXPECT_THAT(strings, ElementsAre(StrEq("a"), StrEq("b"), StrEq("c")));
|
||||
|
||||
EXPECT_THAT(parse({"--str=d", "--int=4", "--str=e"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(integers, ElementsAre(Eq(1), Eq(2), Eq(3), Eq(4)));
|
||||
EXPECT_THAT(strings, ElementsAre(StrEq("a"), StrEq("b"), StrEq("c"),
|
||||
StrEq("d"), StrEq("e")));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, PositionalAppending) {
|
||||
llvm::SmallVector<llvm::StringRef> option_strings;
|
||||
llvm::SmallVector<llvm::StringRef> strings1;
|
||||
llvm::SmallVector<llvm::StringRef> strings2;
|
||||
llvm::SmallVector<llvm::StringRef> strings3;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddStringOption({.name = "opt"},
|
||||
[&](auto& arg_b) { arg_b.Append(&option_strings); });
|
||||
b.AddStringPositionalArg({.name = "s1"},
|
||||
[&](auto& arg_b) { arg_b.Append(&strings1); });
|
||||
b.AddStringPositionalArg({.name = "s2"},
|
||||
[&](auto& arg_b) { arg_b.Append(&strings2); });
|
||||
b.AddStringPositionalArg({.name = "s3"},
|
||||
[&](auto& arg_b) { arg_b.Append(&strings3); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"a", "--opt=x", "b", "--opt=y", "--", "c", "--opt=z", "d",
|
||||
"--", "e", "f"},
|
||||
llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(option_strings, ElementsAre(StrEq("x"), StrEq("y")));
|
||||
EXPECT_THAT(strings1, ElementsAre(StrEq("a"), StrEq("b")));
|
||||
EXPECT_THAT(strings2, ElementsAre(StrEq("c"), StrEq("--opt=z"), StrEq("d")));
|
||||
EXPECT_THAT(strings3, ElementsAre(StrEq("e"), StrEq("f")));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, OneOfOption) {
|
||||
int value = 0;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddOneOfOption({.name = "option"}, [&](auto& arg_b) {
|
||||
arg_b.SetOneOf(
|
||||
{
|
||||
arg_b.OneOfValue("x", 1),
|
||||
arg_b.OneOfValue("y", 2),
|
||||
arg_b.OneOfValue("z", 3),
|
||||
},
|
||||
&value);
|
||||
});
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"--option=x"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(value, Eq(1));
|
||||
|
||||
EXPECT_THAT(parse({"--option=y"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(value, Eq(2));
|
||||
|
||||
EXPECT_THAT(parse({"--option=z"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(value, Eq(3));
|
||||
|
||||
constexpr const char* ErrorStr =
|
||||
"ERROR: Option '--option={0}' has an invalid value '{0}'; valid values "
|
||||
"are: 'x', 'y', or 'z'\n";
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--option=a"}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "a")));
|
||||
|
||||
EXPECT_THAT(parse({"--option="}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "")));
|
||||
|
||||
EXPECT_THAT(parse({"--option=xx"}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "xx")));
|
||||
|
||||
EXPECT_THAT(parse({"--option=\xFF"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "\\FF")));
|
||||
|
||||
EXPECT_THAT(parse({llvm::StringRef("--option=\0", 10)}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "\\00")));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, OneOfOptionAppending) {
|
||||
llvm::SmallVector<int> values;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddOneOfOption({.name = "option"}, [&](auto& arg_b) {
|
||||
arg_b.AppendOneOf(
|
||||
{
|
||||
arg_b.OneOfValue("x", 1),
|
||||
arg_b.OneOfValue("y", 2),
|
||||
arg_b.OneOfValue("z", 3),
|
||||
},
|
||||
&values);
|
||||
});
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({}, llvm::errs()), Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(values, ElementsAre());
|
||||
|
||||
EXPECT_THAT(parse({"--option=x", "--option=y", "--option=z"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(values, ElementsAre(Eq(1), Eq(2), Eq(3)));
|
||||
|
||||
EXPECT_THAT(parse({"--option=y", "--option=y", "--option=x"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(values, ElementsAre(Eq(1), Eq(2), Eq(3), Eq(2), Eq(2), Eq(1)));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, RequiredArgs) {
|
||||
int integer_option;
|
||||
llvm::StringRef string_option;
|
||||
|
||||
TestSubcommand subcommand;
|
||||
int integer_sub_option;
|
||||
llvm::StringRef string_sub_option;
|
||||
TestEnum enum_sub_option;
|
||||
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddIntegerOption({.name = "opt1"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&integer_option);
|
||||
});
|
||||
b.AddStringOption({.name = "opt2"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&string_option);
|
||||
});
|
||||
b.AddSubcommand({.name = "sub1"}, [&](auto& sub_b) {
|
||||
sub_b.AddIntegerOption({.name = "sub-opt1"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&integer_sub_option);
|
||||
});
|
||||
sub_b.AddStringOption({.name = "sub-opt2"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&string_sub_option);
|
||||
});
|
||||
sub_b.Do([&] { subcommand = TestSubcommand::Sub1; });
|
||||
});
|
||||
b.AddSubcommand({.name = "sub2"}, [&](auto& sub_b) {
|
||||
sub_b.AddIntegerOption({.name = "sub-opt1"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&integer_sub_option);
|
||||
});
|
||||
sub_b.AddStringOption({.name = "sub-opt2"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&string_sub_option);
|
||||
});
|
||||
sub_b.AddOneOfOption({.name = "sub-opt3"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.SetOneOf(
|
||||
{
|
||||
arg_b.OneOfValue("a", TestEnum::Val1),
|
||||
arg_b.OneOfValue("b", TestEnum::Val2),
|
||||
},
|
||||
&enum_sub_option);
|
||||
});
|
||||
sub_b.Do([&] { subcommand = TestSubcommand::Sub2; });
|
||||
});
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
constexpr const char* ErrorStr =
|
||||
"ERROR: Required option '{0}' not provided.\n";
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({}, os), Eq(CommandLine::ParseResult::Error));
|
||||
auto errors = os.TakeStr();
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--opt1")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--opt2")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz"}, os), Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "--opt1")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42"}, llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("xyz"));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
errors = os.TakeStr();
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt1")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt2")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt3")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2", "--sub-opt3=b"}, os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
errors = os.TakeStr();
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt1")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt2")));
|
||||
EXPECT_THAT(errors, Not(HasSubstr(llvm::formatv(ErrorStr, "--sub-opt3"))));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2", "--sub-opt3=b",
|
||||
"--sub-opt1=13"},
|
||||
os),
|
||||
Eq(CommandLine::ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "--sub-opt2")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2", "--sub-opt3=b",
|
||||
"--sub-opt1=13", "--sub-opt2=abc"},
|
||||
llvm::errs()),
|
||||
Eq(CommandLine::ParseResult::Success));
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("xyz"));
|
||||
EXPECT_THAT(subcommand, Eq(TestSubcommand::Sub2));
|
||||
EXPECT_THAT(integer_sub_option, Eq(13));
|
||||
EXPECT_THAT(string_sub_option, StrEq("abc"));
|
||||
EXPECT_THAT(enum_sub_option, Eq(TestEnum::Val2));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, HelpAndVersion) {
|
||||
bool flag = false;
|
||||
int storage = -1;
|
||||
llvm::StringRef string = "";
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse( // Force line break.
|
||||
args, s, s,
|
||||
{
|
||||
.name = "test",
|
||||
.version = "Test Command -- version 1.2.3",
|
||||
.build_info = R"""(
|
||||
Build timestamp: )""" __TIMESTAMP__ R"""(
|
||||
Build config: test-config-info
|
||||
)""",
|
||||
.help = R"""(
|
||||
A test command.
|
||||
|
||||
Lots more information about the test command.
|
||||
)""",
|
||||
.help_epilogue = R"""(
|
||||
Closing remarks.
|
||||
)""",
|
||||
},
|
||||
[&](auto& b) {
|
||||
b.AddFlag(
|
||||
{
|
||||
.name = "flag",
|
||||
.short_name = "f",
|
||||
.help = R"""(
|
||||
A boolean flag.
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddFlag(
|
||||
{
|
||||
.name = "hidden_flag",
|
||||
.help = R"""(
|
||||
A *hidden* boolean flag.
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) {
|
||||
arg_b.HelpHidden(true);
|
||||
arg_b.Set(&flag);
|
||||
});
|
||||
b.AddSubcommand(
|
||||
{
|
||||
.name = "edit",
|
||||
.help = R"""(
|
||||
Edit the widget.
|
||||
|
||||
This will take the provided widgets and edit them.
|
||||
)""",
|
||||
.help_epilogue = R"""(
|
||||
That's all.
|
||||
)""",
|
||||
},
|
||||
[&](auto& sub_b) {
|
||||
sub_b.AddIntegerOption(
|
||||
{
|
||||
.name = "option",
|
||||
.short_name = "o",
|
||||
.help = R"""(
|
||||
An integer option.
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) { arg_b.Set(&storage); });
|
||||
sub_b.Do([] {});
|
||||
});
|
||||
b.AddSubcommand(
|
||||
{
|
||||
.name = "run",
|
||||
.help = R"""(
|
||||
Run wombats across the screen.
|
||||
|
||||
This will cause several wombats to run across your screen.
|
||||
)""",
|
||||
.help_epilogue = R"""(
|
||||
Or it won't, who knows.
|
||||
)""",
|
||||
},
|
||||
[&](auto& sub_b) {
|
||||
sub_b.AddStringOption(
|
||||
{
|
||||
.name = "option",
|
||||
.short_name = "o",
|
||||
.help = R"""(
|
||||
A string option.
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) { arg_b.Set(&string); });
|
||||
sub_b.AddOneOfOption(
|
||||
{
|
||||
.name = "one-of-option",
|
||||
.help = R"""(
|
||||
A one-of option.
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) {
|
||||
arg_b.SetOneOf(
|
||||
{
|
||||
arg_b.OneOfValue("x", 1),
|
||||
arg_b.OneOfValue("y", 2),
|
||||
arg_b.OneOfValue("z", 3),
|
||||
},
|
||||
&storage);
|
||||
});
|
||||
sub_b.Do([] {});
|
||||
});
|
||||
b.AddSubcommand(
|
||||
{
|
||||
.name = "hidden",
|
||||
.help = R"""(
|
||||
A hidden subcommand.
|
||||
)""",
|
||||
},
|
||||
[&](auto& sub_b) {
|
||||
sub_b.HelpHidden(true);
|
||||
sub_b.Do([] {});
|
||||
});
|
||||
b.RequiresSubcommand();
|
||||
});
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--flag", "--help"}, os),
|
||||
Eq(CommandLine::ParseResult::MetaSuccess));
|
||||
std::string help_flag_output = os.TakeStr();
|
||||
EXPECT_THAT(help_flag_output, StrEq(llvm::StringRef(R"""(
|
||||
Test Command -- version 1.2.3
|
||||
|
||||
A test command.
|
||||
|
||||
Lots more information about the test command.
|
||||
|
||||
Build info:
|
||||
Build timestamp: )""" __TIMESTAMP__ R"""(
|
||||
Build config: test-config-info
|
||||
|
||||
Usage:
|
||||
test [-f] edit [--option=...]
|
||||
test [-f] run [OPTIONS]
|
||||
|
||||
Subcommands:
|
||||
edit
|
||||
Edit the widget.
|
||||
|
||||
This will take the provided widgets and edit them.
|
||||
|
||||
run
|
||||
Run wombats across the screen.
|
||||
|
||||
This will cause several wombats to run across your screen.
|
||||
|
||||
help
|
||||
Prints help information for the command, including a description, command line usage, and details of each subcommand and option that can be provided.
|
||||
|
||||
version
|
||||
Prints the version of this command.
|
||||
|
||||
Command options:
|
||||
-f, --flag
|
||||
A boolean flag.
|
||||
|
||||
Closing remarks.
|
||||
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
EXPECT_THAT(parse({"help"}, os), Eq(CommandLine::ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(help_flag_output));
|
||||
|
||||
EXPECT_THAT(parse({"--version"}, os),
|
||||
Eq(CommandLine::ParseResult::MetaSuccess));
|
||||
std::string version_flag_output = os.TakeStr();
|
||||
EXPECT_THAT(version_flag_output, StrEq(llvm::StringRef(R"""(
|
||||
Test Command -- version 1.2.3
|
||||
|
||||
Build timestamp: )""" __TIMESTAMP__ R"""(
|
||||
Build config: test-config-info
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
EXPECT_THAT(parse({"version"}, os),
|
||||
Eq(CommandLine::ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(version_flag_output));
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "edit", "--option=42", "--help"}, os),
|
||||
Eq(CommandLine::ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::StringRef(R"""(
|
||||
Edit the widget.
|
||||
|
||||
This will take the provided widgets and edit them.
|
||||
|
||||
Subcommand 'edit' usage:
|
||||
test [-f] edit [--option=...]
|
||||
|
||||
Subcommand 'edit' options:
|
||||
-o, --option=...
|
||||
An integer option.
|
||||
|
||||
--help[=(full|short)]
|
||||
Prints help information for the subcommand, including a description, command line usage, and details of each option that can be provided.
|
||||
|
||||
Possible values:
|
||||
- full (default)
|
||||
- short
|
||||
|
||||
That's all.
|
||||
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "run", "--option=abc", "--help"}, os),
|
||||
Eq(CommandLine::ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::StringRef(R"""(
|
||||
Run wombats across the screen.
|
||||
|
||||
This will cause several wombats to run across your screen.
|
||||
|
||||
Subcommand 'run' usage:
|
||||
test [-f] run [OPTIONS]
|
||||
|
||||
Subcommand 'run' options:
|
||||
-o, --option=...
|
||||
A string option.
|
||||
|
||||
--one-of-option=(x|y|z)
|
||||
A one-of option.
|
||||
|
||||
Possible values:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
|
||||
--help[=(full|short)]
|
||||
Prints help information for the subcommand, including a description, command line usage, and details of each option that can be provided.
|
||||
|
||||
Possible values:
|
||||
- full (default)
|
||||
- short
|
||||
|
||||
Or it won't, who knows.
|
||||
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, HelpMarkdownLike) {
|
||||
bool flag = false;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return CommandLine::Parse( // Force line break.
|
||||
args, s, s, {.name = "test"}, [&](auto& b) {
|
||||
b.AddFlag(
|
||||
{
|
||||
.name = "flag",
|
||||
.help = R"""(
|
||||
A boolean flag.
|
||||
|
||||
Preformatted
|
||||
code....
|
||||
........
|
||||
|
||||
But
|
||||
here
|
||||
lines
|
||||
are
|
||||
collapsed.
|
||||
|
||||
```
|
||||
x
|
||||
y
|
||||
z
|
||||
```
|
||||
)""",
|
||||
},
|
||||
[&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--help"}, os), Eq(CommandLine::ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::StringRef(R"""(
|
||||
Usage:
|
||||
test [--flag]
|
||||
|
||||
Options:
|
||||
--flag
|
||||
A boolean flag.
|
||||
|
||||
Preformatted
|
||||
code....
|
||||
........
|
||||
|
||||
But here lines are collapsed.
|
||||
|
||||
```
|
||||
x
|
||||
y
|
||||
z
|
||||
```
|
||||
|
||||
--help[=(full|short)]
|
||||
Prints help information for the command, including a description, command line usage, and details of each option that can be provided.
|
||||
|
||||
Possible values:
|
||||
- full (default)
|
||||
- short
|
||||
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace Carbon::Testing
|
||||
Reference in New Issue
Block a user