如何自动打印子命令结果?
How to print subcommand result automatically?
我有 Java 基于 cliche 库的 CLI 应用程序,我想将它迁移到 picocli。
我的应用程序基于陈词滥调,所以我有很多带有 asg.cliche.Command 注释的方法,其中 return 一些结果。陈词滥调自动打印命令方法的结果,因此结果在命令行中打印。我用 picocli.CommandLine.Command 替换了 asg.cliche.Command 注释,我看到 picocli 不打印命令方法的结果。
我有以下 class:
import picocli.CommandLine;
@CommandLine.Command(subcommandsRepeatable = true)
public class Foo
{
public static void main( String[] args )
{
new CommandLine( new Foo() ).execute( args );
}
@CommandLine.Command
public String sayHello()
{
return "Hello";
}
@CommandLine.Command
public String sayGoodbye()
{
return "GoodBye";
}
}
当我调用 java -cp myJar.jar Foo sayHello sayGoodbye
时,我没有看到任何输出。
我看到三种解决方案:
1.修改每个方法打印结果而不是return它。
import picocli.CommandLine;
@CommandLine.Command( subcommandsRepeatable = true )
public class Foo2
{
public static void main( String[] args )
{
new CommandLine( new Foo2() ).execute( args );
}
@CommandLine.Command
public void sayHello()
{
System.out.println( "Hello" );
}
@CommandLine.Command
public void sayGoodbye()
{
System.out.println( "GoodBye" );
}
}
我对这个解决方案不满意。我不想修改我的方法。
- 执行后检索结果。
public static void main( String[] args )
{
final CommandLine commandLine = new CommandLine( new Foo() );
commandLine.execute( args );
CommandLine.ParseResult parseResult = commandLine.getParseResult();
for( CommandLine.ParseResult pr : parseResult.subcommands() )
{
System.out.println( pr.commandSpec().commandLine()
.getExecutionResult()
.toString() );
}
}
我发现此解决方案存在一些问题。主要问题是格式化。执行结果可以是null、数组、集合。第二个问题是在执行所有子命令后打印结果。如果第二个子命令抛出异常,那么我首先看到异常堆栈跟踪,然后我看到第一个子命令的结果。
- 如果有更好的解决方案,请在 Whosebug 上询问。我不相信 picocli 中没有任何启用结果打印的配置选项。
就个人而言,我最喜欢您的第一个解决方案,它简单易维护。也许引入一个用于打印和格式化的辅助方法,这样命令方法就可以如下所示:
@CommandLine.Command
public String sayGoodbye()
{
return printValue("GoodBye");
}
您已经找到了CommandLine.getParseResult
方法;也许辅助方法也可以帮助格式化那里。
还有第三种选择,但不幸的是,它要复杂得多:您可以创建一个自定义 IExecutionStrategy
,在执行每条命令后打印其结果。它涉及从 picocli 内部复制大量代码,这不是一个真正现实的解决方案;我只是为了完整性而提到它。
// extend RunLast to handle requests for help/version and exit code stuff
class PrintingExecutionStrategy extends CommandLine.RunLast {
@Override
protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
// Simplified: executes only the last subcommand (so no repeating subcommands).
// Look at RunLast.executeUserObjectOfLastSubcommandWithSameParent if you need repeating subcommands.
List<CommandLine> parsedCommands = parseResult.asCommandLineList();
CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
return execute(last, new ArrayList<Object>());
}
// copied from CommandLine.executeUserObject,
// modified to print the execution result
private List<Object> execute(CommandLine cmd, List<Object> executionResultList) throws Exception {
Object command = parsed.getCommand();
if (command instanceof Runnable) {
try {
((Runnable) command).run();
parsed.setExecutionResult(null); // 4.0
executionResultList.add(null); // for compatibility with picocli 2.x
return executionResultList;
} catch (ParameterException ex) {
throw ex;
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex);
}
} else if (command instanceof Callable) {
try {
@SuppressWarnings("unchecked") Callable<Object> callable = (Callable<Object>) command;
Object executionResult = callable.call();
System.out.println(executionResult); <-------- print result
parsed.setExecutionResult(executionResult);
executionResultList.add(executionResult);
return executionResultList;
} catch (ParameterException ex) {
throw ex;
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex);
}
} else if (command instanceof Method) {
try {
Method method = (Method) command;
Object[] parsedArgs = parsed.getCommandSpec().argValues();
Object executionResult;
if (Modifier.isStatic(method.getModifiers())) {
executionResult = method.invoke(null, parsedArgs); // invoke static method
} else if (parsed.getCommandSpec().parent() != null) {
executionResult = method.invoke(parsed.getCommandSpec().parent().userObject(), parsedArgs);
} else {
executionResult = method.invoke(parsed.factory.create(method.getDeclaringClass()), parsedArgs);
}
System.out.println(executionResult); <-------- print result
parsed.setExecutionResult(executionResult);
executionResultList.add(executionResult);
return executionResultList;
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
if (t instanceof ParameterException) {
throw (ParameterException) t;
} else if (t instanceof ExecutionException) {
throw (ExecutionException) t;
} else {
throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + t, t);
}
} catch (Exception ex) {
throw new ExecutionException(parsed, "Unhandled error while calling command (" + command + "): " + ex, ex);
}
}
throw new ExecutionException(parsed, "Parsed command (" + command + ") is not a Method, Runnable or Callable");
}
}
这样使用:
public static void main(String... args) {
new CommandLine(new Foo())
.setExecutionStrategy(new PrintingExecutionStrategy())
.execute(args);
}
以上我不推荐。
更新:我想到了另一个,第四个选项(实际上是你的第二个解决方案的变体)。您可以指定一个不打印堆栈跟踪的自定义 IExecutionExceptionHandler
,而是存储异常,以便您可以在打印命令结果后打印堆栈跟踪。像这样:
class MyHandler extends IExecutionExceptionHandler() {
Exception exception;
public int handleExecutionException(Exception ex,
CommandLine commandLine,
ParseResult parseResult) {
//ex.printStackTrace(); // no stack trace
exception = ex;
}
}
这样使用:
public static void main(String... args) {
MyHandler handler = new MyHandler();
CommandLine cmd = new CommandLine(new Foo())
.setExecutionExceptionHandler(handler);
cmd.execute(args);
ParseResult parseResult = cmd.getParseResult();
for( ParseResult pr : parseResult.subcommands() )
{
System.out.println( pr.commandSpec().commandLine()
.getExecutionResult()
.toString() );
}
if (handler.exception != null) {
handler.exception.printStackTrace();
}
}
我有 Java 基于 cliche 库的 CLI 应用程序,我想将它迁移到 picocli。
我的应用程序基于陈词滥调,所以我有很多带有 asg.cliche.Command 注释的方法,其中 return 一些结果。陈词滥调自动打印命令方法的结果,因此结果在命令行中打印。我用 picocli.CommandLine.Command 替换了 asg.cliche.Command 注释,我看到 picocli 不打印命令方法的结果。 我有以下 class:
import picocli.CommandLine;
@CommandLine.Command(subcommandsRepeatable = true)
public class Foo
{
public static void main( String[] args )
{
new CommandLine( new Foo() ).execute( args );
}
@CommandLine.Command
public String sayHello()
{
return "Hello";
}
@CommandLine.Command
public String sayGoodbye()
{
return "GoodBye";
}
}
当我调用 java -cp myJar.jar Foo sayHello sayGoodbye
时,我没有看到任何输出。
我看到三种解决方案:
1.修改每个方法打印结果而不是return它。
import picocli.CommandLine;
@CommandLine.Command( subcommandsRepeatable = true )
public class Foo2
{
public static void main( String[] args )
{
new CommandLine( new Foo2() ).execute( args );
}
@CommandLine.Command
public void sayHello()
{
System.out.println( "Hello" );
}
@CommandLine.Command
public void sayGoodbye()
{
System.out.println( "GoodBye" );
}
}
我对这个解决方案不满意。我不想修改我的方法。
- 执行后检索结果。
public static void main( String[] args ) { final CommandLine commandLine = new CommandLine( new Foo() ); commandLine.execute( args ); CommandLine.ParseResult parseResult = commandLine.getParseResult(); for( CommandLine.ParseResult pr : parseResult.subcommands() ) { System.out.println( pr.commandSpec().commandLine() .getExecutionResult() .toString() ); } }
我发现此解决方案存在一些问题。主要问题是格式化。执行结果可以是null、数组、集合。第二个问题是在执行所有子命令后打印结果。如果第二个子命令抛出异常,那么我首先看到异常堆栈跟踪,然后我看到第一个子命令的结果。
- 如果有更好的解决方案,请在 Whosebug 上询问。我不相信 picocli 中没有任何启用结果打印的配置选项。
就个人而言,我最喜欢您的第一个解决方案,它简单易维护。也许引入一个用于打印和格式化的辅助方法,这样命令方法就可以如下所示:
@CommandLine.Command
public String sayGoodbye()
{
return printValue("GoodBye");
}
您已经找到了CommandLine.getParseResult
方法;也许辅助方法也可以帮助格式化那里。
还有第三种选择,但不幸的是,它要复杂得多:您可以创建一个自定义 IExecutionStrategy
,在执行每条命令后打印其结果。它涉及从 picocli 内部复制大量代码,这不是一个真正现实的解决方案;我只是为了完整性而提到它。
// extend RunLast to handle requests for help/version and exit code stuff
class PrintingExecutionStrategy extends CommandLine.RunLast {
@Override
protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
// Simplified: executes only the last subcommand (so no repeating subcommands).
// Look at RunLast.executeUserObjectOfLastSubcommandWithSameParent if you need repeating subcommands.
List<CommandLine> parsedCommands = parseResult.asCommandLineList();
CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
return execute(last, new ArrayList<Object>());
}
// copied from CommandLine.executeUserObject,
// modified to print the execution result
private List<Object> execute(CommandLine cmd, List<Object> executionResultList) throws Exception {
Object command = parsed.getCommand();
if (command instanceof Runnable) {
try {
((Runnable) command).run();
parsed.setExecutionResult(null); // 4.0
executionResultList.add(null); // for compatibility with picocli 2.x
return executionResultList;
} catch (ParameterException ex) {
throw ex;
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex);
}
} else if (command instanceof Callable) {
try {
@SuppressWarnings("unchecked") Callable<Object> callable = (Callable<Object>) command;
Object executionResult = callable.call();
System.out.println(executionResult); <-------- print result
parsed.setExecutionResult(executionResult);
executionResultList.add(executionResult);
return executionResultList;
} catch (ParameterException ex) {
throw ex;
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex);
}
} else if (command instanceof Method) {
try {
Method method = (Method) command;
Object[] parsedArgs = parsed.getCommandSpec().argValues();
Object executionResult;
if (Modifier.isStatic(method.getModifiers())) {
executionResult = method.invoke(null, parsedArgs); // invoke static method
} else if (parsed.getCommandSpec().parent() != null) {
executionResult = method.invoke(parsed.getCommandSpec().parent().userObject(), parsedArgs);
} else {
executionResult = method.invoke(parsed.factory.create(method.getDeclaringClass()), parsedArgs);
}
System.out.println(executionResult); <-------- print result
parsed.setExecutionResult(executionResult);
executionResultList.add(executionResult);
return executionResultList;
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
if (t instanceof ParameterException) {
throw (ParameterException) t;
} else if (t instanceof ExecutionException) {
throw (ExecutionException) t;
} else {
throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + t, t);
}
} catch (Exception ex) {
throw new ExecutionException(parsed, "Unhandled error while calling command (" + command + "): " + ex, ex);
}
}
throw new ExecutionException(parsed, "Parsed command (" + command + ") is not a Method, Runnable or Callable");
}
}
这样使用:
public static void main(String... args) {
new CommandLine(new Foo())
.setExecutionStrategy(new PrintingExecutionStrategy())
.execute(args);
}
以上我不推荐。
更新:我想到了另一个,第四个选项(实际上是你的第二个解决方案的变体)。您可以指定一个不打印堆栈跟踪的自定义 IExecutionExceptionHandler
,而是存储异常,以便您可以在打印命令结果后打印堆栈跟踪。像这样:
class MyHandler extends IExecutionExceptionHandler() {
Exception exception;
public int handleExecutionException(Exception ex,
CommandLine commandLine,
ParseResult parseResult) {
//ex.printStackTrace(); // no stack trace
exception = ex;
}
}
这样使用:
public static void main(String... args) {
MyHandler handler = new MyHandler();
CommandLine cmd = new CommandLine(new Foo())
.setExecutionExceptionHandler(handler);
cmd.execute(args);
ParseResult parseResult = cmd.getParseResult();
for( ParseResult pr : parseResult.subcommands() )
{
System.out.println( pr.commandSpec().commandLine()
.getExecutionResult()
.toString() );
}
if (handler.exception != null) {
handler.exception.printStackTrace();
}
}