【问题标题】:How to print subcommand result automatically?如何自动打印子命令结果?
【发布时间】:2020-02-13 14:40:08
【问题描述】:

我有基于 cliche 库的 Java CLI 应用程序,我想将其迁移到 picocli。

我的应用程序基于陈词滥调,因此我有很多带有 asg.cliche.Command 注释的方法,它们会返回一些结果。 cliche 会自动打印命令方法的结果,因此结果是在命令行中打印的。我用 picocli.CommandLine.Command 替换了 asg.cliche.Command 注释,我看到 picocli 不打印命令方法的结果。 我有以下课程:

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.修改各个方法打印结果而不是返回结果。

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" );
    }
}

我对这个解决方案不满意。我不喜欢修改我的方法。

  1. 执行后检索结果。
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、array、collection。第二个问题是在执行所有子命令后打印结果。如果第二个子命令抛出异常,那么我首先会看到异常堆栈跟踪,然后我会看到第一个子命令的结果。

  1. 在 stackoverflow 上询问是否有更好的解决方案。我不相信 picocli 中没有任何配置选项可以启用结果打印。

【问题讨论】:

    标签: picocli


    【解决方案1】:

    就个人而言,我最喜欢您的第一个解决方案,它简单且易于维护。也许为打印和格式化引入一个辅助方法,因此命令方法可以如下所示:

        @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();
        }
    }
    

    【讨论】:

    • 感谢您的帮助。我选择了第一个解决方案。
    • 我不确定是否应该接受您的回答。我询问了比我提出的解决方案 1 和 2 更好的解决方案。总的来说,我想找到最好的解决方案。我希望有一些我没有注意到的配置参数。您的最终提案没有解决我在问题中提到的问题(格式和 NullPointerException)。必须将 Null、集合和数组检查添加到您的代码中。
    • 另一方面,您提出了如何在不更改方法的情况下打印结果的解决方案,所以也许我应该接受您的回答。
    • 最初第一个解决方案可能不是您的首选解决方案,但现在我们已经更深入地探索了解决方案空间,您已经能够做出更明智的决定。对于它的价值,在所有选择中,我相信您更改应用程序的第一个解决方案是最简单且最容易维护的。
    猜你喜欢
    • 2021-10-19
    • 1970-01-01
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多