Как автоматически распечатать результат подкоманды? - PullRequest
1 голос
/ 13 февраля 2020

У меня есть Java CLI-приложение, основанное на библиотеке клише , и я хочу перенести его в picocli.

Мое приложение было основано на клише, поэтому у меня много методов с asg .cliche.Command аннотация, которая возвращает некоторый результат. clickhe печатает результат командных методов автоматически, чтобы результат был напечатан в командной строке. Я заменил аннотации asg.cliche.Command на picocli.CommandLine.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" );
    }
}

Я не доволен этим решением. Я предпочитаю не изменять мои методы.

Получение результатов после выполнения.
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() );
        }
    }

Я вижу несколько проблем с этим решением. Основная проблема - форматирование. Результат выполнения может быть нулевым, массивом, коллекцией. Вторая проблема заключается в том, что результаты печатаются после выполнения всех подкоманд. Если вторая подкоманда выдает исключение, я сначала вижу трассировку стека исключений, а после этого вижу результат первой подкоманды.

Спросите у stackoverflow, есть ли какое-нибудь лучшее решение. Я не верю, что в picocli нет параметров конфигурации, позволяющих печатать результаты.

1 Ответ

1 голос
/ 15 февраля 2020

Лично мне больше всего нравится ваше первое решение, оно простое и легкое в обслуживании. Возможно, добавьте вспомогательный метод для печати и форматирования, чтобы методы команд могли выглядеть следующим образом:

    @CommandLine.Command
    public String sayGoodbye()
    {
        return printValue("GoodBye");
    }

Вы уже нашли метод CommandLine.getParseResult; возможно, вспомогательный метод также может помочь с форматированием.

Существует третий вариант, но, к сожалению, он немного сложнее: вы можете создать пользовательский IExecutionStrategy, который печатает результат каждой команды после ее выполнения. Он включает в себя копирование большого количества кода из внутренних компонентов picocli, и это не совсем реалистичное решение c; Я просто упоминаю это для полноты.

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