首页 > 解决方案 > 如何自动打印子命令结果?

问题描述

我有基于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

解决方案


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

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

推荐阅读