带有 Picocli 的 CLI:在调用子命令之前调用主命令
CLI with Picocli: Call main command before sub command get called
由于子命令支持(和基于注释的声明),我从 Apache Commons CLI 切换到 Picocli。
考虑像 git
这样的命令行工具,以及像 push
这样的子命令。 Git 有一个主开关 --verbose
或 -v
用于在 all 子命令中启用详细模式。
如何实现在 任何子命令 之前执行的主开关?
这是我的测试
@CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Callable<Void> {
@Override
public Void call() throws Exception {
System.out.println("#PushCommand.call");
return null;
}
}
@CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp implements Callable<Void> {
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
@CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "--verbose", "push");
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
@Override
public Void call() throws Exception {
System.out.println("#GitApp.call");
return null;
}
}
输出是
#PushCommand.call
#GitApp.main after. verbose: true
我希望 GitApp.call
在子命令被调用之前被调用。但只有子命令被调用。
由于 Picocli 支持使用 Options 进行继承,因此我将 --help
和 --verbose
选项提取到抽象 class BaseCommand
中,并从中调用 super.call
子命令。
abstract class BaseCommand implements Callable<Void> {
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
@CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
@Override
public Void call() throws Exception {
if (verboseMode) {
setVerbose();
}
return null;
}
private void setVerbose() {
System.out.println("enter verbose mode");
}
}
@CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand extends BaseCommand {
@Override
public Void call() throws Exception {
super.call();
System.out.println("Execute push command");
return null;
}
}
@CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp extends BaseCommand {
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "push", "--verbose");
}
@Override
public Void call() throws Exception {
super.call();
System.out.println("GitApp.call called");
return null;
}
}
CommandLine.call
(和 CommandLine.run
)方法仅按设计调用 last subcommand,因此您在原始 post 中看到的是预期的行为。
call
和run
方法实际上是一个捷径。下面两行是等价的:
CommandLine.run(callable, args); // internally uses RunLast, equivalent to:
new CommandLine(callable).parseWithHandler(new RunLast(), args);
Update: from picocli 4.0, the above methods are deprecated, and replaced with new CommandLine(myapp).execute(args)
. The "handler" is now called the "execution strategy" (example below).
还有一个 RunAll
handler 运行 所有 匹配的命令。以下 main
方法给出了所需的行为:
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
// before picocli 4.0:
new CommandLine(app).parseWithHandler(new RunAll(), args);
// from picocli 4.0:
//new CommandLine(app).setExecutionStrategy(new RunAll()).execute(args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
输出:
#GitApp.call
#PushCommand.call
#GitApp.main after. verbose: true
您可能还对 @ParentCommand
注释感兴趣。这告诉 picocli 将父命令的实例注入到子命令中。然后您的子命令可以调用父命令的方法,例如检查 verbose
是否为真。例如:
Update: from picocli 4.0, use the setExecutionStrategy
method to specify RunAll
. The below example is updated to use the new picocli 4.0+ API.
import picocli.CommandLine;
import picocli.CommandLine.*;
@Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Runnable {
@ParentCommand // picocli injects the parent instance
private GitApp parentCommand;
public void run() {
System.out.printf("#PushCommand.call: parent.verbose=%s%n",
parentCommand.verboseMode); // use parent instance
}
}
@Command(description = "Version control",
mixinStandardHelpOptions = true, // auto-include --help and --version
subcommands = {PushCommand.class,
HelpCommand.class}) // built-in help subcommand
public class GitApp implements Runnable {
@Option(names = {"-v", "--verbose"},
description = "Verbose mode. Helpful for troubleshooting.")
boolean verboseMode;
public void run() {
System.out.println("#GitApp.call");
}
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
int exitCode = new CommandLine(app)
.setExecutionStrategy(new RunAll())
.execute(args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
System.exit(exitCode);
}
}
其他小改动:通过导入内部 类 使注释更加紧凑。您可能还喜欢有助于减少样板代码的 mixinStandardHelpOptions
attribute and the built-in help
子命令。
由于子命令支持(和基于注释的声明),我从 Apache Commons CLI 切换到 Picocli。
考虑像 git
这样的命令行工具,以及像 push
这样的子命令。 Git 有一个主开关 --verbose
或 -v
用于在 all 子命令中启用详细模式。
如何实现在 任何子命令 之前执行的主开关?
这是我的测试
@CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Callable<Void> {
@Override
public Void call() throws Exception {
System.out.println("#PushCommand.call");
return null;
}
}
@CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp implements Callable<Void> {
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
@CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "--verbose", "push");
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
@Override
public Void call() throws Exception {
System.out.println("#GitApp.call");
return null;
}
}
输出是
#PushCommand.call
#GitApp.main after. verbose: true
我希望 GitApp.call
在子命令被调用之前被调用。但只有子命令被调用。
由于 Picocli 支持使用 Options 进行继承,因此我将 --help
和 --verbose
选项提取到抽象 class BaseCommand
中,并从中调用 super.call
子命令。
abstract class BaseCommand implements Callable<Void> {
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
@CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
@Override
public Void call() throws Exception {
if (verboseMode) {
setVerbose();
}
return null;
}
private void setVerbose() {
System.out.println("enter verbose mode");
}
}
@CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand extends BaseCommand {
@Override
public Void call() throws Exception {
super.call();
System.out.println("Execute push command");
return null;
}
}
@CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp extends BaseCommand {
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "push", "--verbose");
}
@Override
public Void call() throws Exception {
super.call();
System.out.println("GitApp.call called");
return null;
}
}
CommandLine.call
(和 CommandLine.run
)方法仅按设计调用 last subcommand,因此您在原始 post 中看到的是预期的行为。
call
和run
方法实际上是一个捷径。下面两行是等价的:
CommandLine.run(callable, args); // internally uses RunLast, equivalent to:
new CommandLine(callable).parseWithHandler(new RunLast(), args);
Update: from picocli 4.0, the above methods are deprecated, and replaced with
new CommandLine(myapp).execute(args)
. The "handler" is now called the "execution strategy" (example below).
还有一个 RunAll
handler 运行 所有 匹配的命令。以下 main
方法给出了所需的行为:
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
// before picocli 4.0:
new CommandLine(app).parseWithHandler(new RunAll(), args);
// from picocli 4.0:
//new CommandLine(app).setExecutionStrategy(new RunAll()).execute(args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
输出:
#GitApp.call
#PushCommand.call
#GitApp.main after. verbose: true
您可能还对 @ParentCommand
注释感兴趣。这告诉 picocli 将父命令的实例注入到子命令中。然后您的子命令可以调用父命令的方法,例如检查 verbose
是否为真。例如:
Update: from picocli 4.0, use the
setExecutionStrategy
method to specifyRunAll
. The below example is updated to use the new picocli 4.0+ API.
import picocli.CommandLine;
import picocli.CommandLine.*;
@Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Runnable {
@ParentCommand // picocli injects the parent instance
private GitApp parentCommand;
public void run() {
System.out.printf("#PushCommand.call: parent.verbose=%s%n",
parentCommand.verboseMode); // use parent instance
}
}
@Command(description = "Version control",
mixinStandardHelpOptions = true, // auto-include --help and --version
subcommands = {PushCommand.class,
HelpCommand.class}) // built-in help subcommand
public class GitApp implements Runnable {
@Option(names = {"-v", "--verbose"},
description = "Verbose mode. Helpful for troubleshooting.")
boolean verboseMode;
public void run() {
System.out.println("#GitApp.call");
}
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
int exitCode = new CommandLine(app)
.setExecutionStrategy(new RunAll())
.execute(args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
System.exit(exitCode);
}
}
其他小改动:通过导入内部 类 使注释更加紧凑。您可能还喜欢有助于减少样板代码的 mixinStandardHelpOptions
attribute and the built-in help
子命令。