Просмотр исходного кода

Add help information printing for a specific subcommand (#3699)

The `command_library` provides functionality for end users to query
documentation and help information about commands through the command
line interface itself. At present, this is implemented as:

* A flag, `--help`, on every command
* A meta subcommand, `help`, on every command that has other subcommands

To match with common functionality in other command line interfaces,
this PR amends the `help` meta subcommand to accept an optional
positional string argument. This argument specifies which subcommand to
print help information for. When not specified, the present behavior is
maintained, printing the help information for its parent command.

A potentially unexpected consequence of this implementation is that you
can query for help on meta subcommands as well: `help help` is a valid
input which prints the help information about the `help` meta subcommand
(e.g., same for `version`). I don't see any harm in this behavior so I
chose not to explicitly prevent it, but I'm open to preventing it if
deemed unwanted. (I'm also happy to workshop the strings in this PR.)

Closes #3694.

#### Before

```
$ carbon help compile
```
```
ERROR: Found unexpected positional argument or subcommand: 'compile'
```

#### After

```
$ carbon help compile
```
```
Compile Carbon source code.

This subcommand runs the Carbon compiler over input source code, checking it for errors and producing the requested output.

Error messages are written to the standard error stream.

Different phases of the compiler can be selected to run, and intermediate state can be written to standard output as these phases progress.

Subcommand 'compile' usage:
  carbon [-v] compile [OPTIONS] <FILE>...

...
```
Calvin 2 лет назад
Родитель
Сommit
9305d9888b
2 измененных файлов с 48 добавлено и 5 удалено
  1. 35 1
      common/command_line.cpp
  2. 13 4
      common/command_line_test.cpp

+ 35 - 1
common/command_line.cpp

@@ -92,6 +92,8 @@ class MetaPrinter {
   void RegisterWithCommand(const Command& command, CommandBuilder& builder);
 
   void PrintHelp(const Command& command) const;
+  void PrintHelpForSubcommandName(const Command& command,
+                                  llvm::StringRef subcommand_name) const;
   void PrintVersion(const Command& command) const;
   void PrintSubcommands(const Command& command) const;
 
@@ -153,6 +155,13 @@ line usage, and details of each option that can be provided.
       .help_short = SubHelpCommandInfo.help_short,
   };
 
+  static constexpr ArgInfo HelpSubcommandArgInfo = {
+      .name = "subcommand",
+      .help = R"""(
+Which subcommand to print help information for.
+)""",
+  };
+
   static constexpr CommandInfo VersionCommandInfo = {
       .name = "version",
       .help = R"""(
@@ -198,6 +207,9 @@ Prints the version of this command.
   // A flag that may be configured during command line parsing to select between
   // long and short form help output.
   bool short_help_ = false;
+
+  // The requested subcommand to print help information for.
+  llvm::StringRef help_subcommand_;
 };
 
 void MetaPrinter::RegisterWithCommand(const Command& command,
@@ -212,7 +224,16 @@ void MetaPrinter::RegisterWithCommand(const Command& command,
     builder.AddSubcommand(
         is_subcommand ? SubHelpCommandInfo : HelpCommandInfo,
         [&](CommandBuilder& sub_b) {
-          sub_b.Meta([this, &command]() { PrintHelp(command); });
+          sub_b.AddStringPositionalArg(HelpSubcommandArgInfo, [&](auto& arg_b) {
+            arg_b.Set(&help_subcommand_);
+          });
+          sub_b.Meta([this, &command]() {
+            if (help_subcommand_.empty()) {
+              PrintHelp(command);
+            } else {
+              PrintHelpForSubcommandName(command, help_subcommand_);
+            }
+          });
         });
 
     // Only add version printing support if there is a version string
@@ -280,6 +301,19 @@ void MetaPrinter::PrintHelp(const Command& command) const {
   out_ << "\n";
 }
 
+void MetaPrinter::PrintHelpForSubcommandName(
+    const Command& command, llvm::StringRef subcommand_name) const {
+  for (const auto& subcommand : command.subcommands) {
+    if (subcommand->info.name == subcommand_name) {
+      PrintHelp(*subcommand);
+      return;
+    }
+  }
+
+  out_ << "ERROR: Could not find a subcommand named '" << subcommand_name
+       << "'.\n";
+}
+
 void MetaPrinter::PrintVersion(const Command& command) const {
   CARBON_CHECK(!command.info.version.empty())
       << "Printing should not be enabled without a version string configured.";

+ 13 - 4
common/command_line_test.cpp

@@ -737,6 +737,7 @@ A hidden subcommand.
   };
 
   TestRawOstream os;
+
   EXPECT_THAT(parse({"--flag", "--help"}, os), Eq(ParseResult::MetaSuccess));
   std::string help_flag_output = os.TakeStr();
   EXPECT_THAT(help_flag_output, StrEq(llvm::StringRef(R"""(
@@ -796,7 +797,8 @@ Build config: test-config-info
 
   EXPECT_THAT(parse({"--flag", "edit", "--option=42", "--help"}, os),
               Eq(ParseResult::MetaSuccess));
-  EXPECT_THAT(os.TakeStr(), StrEq(llvm::StringRef(R"""(
+  std::string edit_help_output = os.TakeStr();
+  EXPECT_THAT(edit_help_output, StrEq(llvm::StringRef(R"""(
 Edit the widget.
 
 This will take the provided widgets and edit them.
@@ -818,11 +820,15 @@ Subcommand 'edit' options:
 That's all.
 
 )""")
-                                      .ltrim('\n')));
+                                          .ltrim('\n')));
+
+  EXPECT_THAT(parse({"help", "edit"}, os), Eq(ParseResult::MetaSuccess));
+  EXPECT_THAT(os.TakeStr(), StrEq(edit_help_output));
 
   EXPECT_THAT(parse({"--flag", "run", "--option=abc", "--help"}, os),
               Eq(ParseResult::MetaSuccess));
-  EXPECT_THAT(os.TakeStr(), StrEq(llvm::StringRef(R"""(
+  std::string run_help_output = os.TakeStr();
+  EXPECT_THAT(run_help_output, StrEq(llvm::StringRef(R"""(
 Run wombats across the screen.
 
 This will cause several wombats to run across your screen.
@@ -852,7 +858,10 @@ Subcommand 'run' options:
 Or it won't, who knows.
 
 )""")
-                                      .ltrim('\n')));
+                                         .ltrim('\n')));
+
+  EXPECT_THAT(parse({"help", "run"}, os), Eq(ParseResult::MetaSuccess));
+  EXPECT_THAT(os.TakeStr(), StrEq(run_help_output));
 }
 
 TEST(ArgParserTest, HelpMarkdownLike) {