Статьи

Picocli 2.0: больше с меньшими затратами

Вступление

Picocli — это структура синтаксического анализа командной строки, состоящая из одного файла, которая позволяет создавать приложения командной строки практически без кода. Аннотируйте поля в вашем приложении с помощью @Option или @Parameters , и picocli заполнит эти поля параметрами командной строки и позиционными параметрами соответственно. Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Command(name = "Greet", header = "%n@|green Hello world demo|@")
class Greet implements Runnable {
 
  @Option(names = {"-u", "--user"}, required = true, description = "The user name.")
  String userName;
 
  public void run() {
    System.out.println("Hello, " + userName);
  }
 
  public static void main(String... args) {
    CommandLine.run(new Greet(), System.err, args);
  }
}

Когда мы выполняем эту программу, picocli анализирует командную строку и заполняет поле userName перед вызовом метода run :

1
2
3
$ java Greet -u picocli
 
Hello, picocli

Picocli создает справочные сообщения об использовании цветов и стилей Ansi . Если мы запустим вышеупомянутую программу с неверным вводом (пропуская обязательную опцию имени пользователя), picocli напечатает ошибку и сообщение помощи использования:

Picocli может сгенерировать сценарий автозаполнения, который позволяет конечным пользователям использовать завершение командной строки <TAB> чтобы определить, какие параметры и подкоманды доступны. Вам также может понравиться поддержка picocli для подкоманд и вложенных подкоманд до любого уровня глубины.

Руководство пользователя подробно описывает функциональные возможности Picocli. В этой статье рассматриваются новые и заслуживающие внимания функции, представленные в выпуске picocli 2.0.

Параметры микширования с позиционными параметрами

Парсер был улучшен, и позиционные параметры теперь можно смешивать с опциями в командной строке.

Ранее позиционные параметры должны были следовать параметрам. В этом выпуске любой аргумент командной строки, который не является опцией или подкомандой, интерпретируется как позиционный параметр.

Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
class MixDemo implements Runnable {
  @Option(names = "-o")
  List<String> options;
 
  @Parameters
  List<String> positional;
 
  public void run() {
    System.out.println("positional: " + positional);
    System.out.println("options   : " + options);
  }
 
  public static void main(String[] args) {
    CommandLine.run(new MixDemo(), System.err, args);
  }
}

Запуск вышеуказанного класса со смесью опций и позиционных параметров показывает, что неопционные параметры распознаются как позиционные параметры. Например:

1
2
3
4
$ java MixDemo param0 -o AAA param1 param2 -o BBB param3
 
positional: [param0, param1, param2, param3]
options   : [AAA, BBB]

Для поддержки параметров смешивания с позиционными параметрами, синтаксический анализатор был изменен. В Picocli 2.0 многозначные параметры (поля массива, списка и карты) больше не являются жадными по умолчанию . Примечания к выпуску 2.0 подробно описывают это изменение и другие потенциальные критические изменения .

Открытие типов коллекций

Picocli выполняет автоматическое преобразование типов аргументов командной строки в тип аннотированного поля. И именованные параметры, и позиционные параметры могут быть строго типизированы.

До версии 2.0 Picocli требовал аннотирования полей « Collection и « Map type атрибутом type чтобы можно было выполнять преобразование типов. Для полей других типов, например полей массива и полей с одним значением, таких как поля int или java.io.File , picocli автоматически определяет тип цели по типу поля, но для коллекций и карт требуется более подробная аннотация. Например:

1
2
3
4
5
6
7
class Before {
    @Option(names = "-u", type = {TimeUnit.class, Long.class})
    Map<TimeUnit, Long> timeout;
 
    @Parameters(type = File.class)
    List<File> files;
}

Начиная с версии 2.0, атрибут type больше не требуется для полей Collection и Map : picocli будет выводить тип элемента коллекции из универсального типа. Атрибут type прежнему работает, как и раньше, в большинстве случаев он не является обязательным.

Пропуск атрибута type удаляет некоторое дублирование и приводит к более простому и чистому коду:

1
2
3
4
5
6
7
class Current {
    @Option(names = "-u")
    Map<TimeUnit, Long> timeout;
 
    @Parameters
    List<File> files;
}

В приведенном выше примере picocli 2.0 может автоматически обнаруживать, что аргументы командной строки необходимо преобразовать в File перед добавлением их в список, а для карты эти ключи необходимо преобразовать в TimeUnit а значения в Long .

Автоматическая справка

Picocli предоставляет ряд удобных методов, таких как run и call которые анализируют аргументы командной строки, заботятся об обработке ошибок и вызывают интерфейсный метод для выполнения приложения.

В этом выпуске вспомогательные методы также автоматически usageHelp об использовании и информацию о версии, когда пользователь указывает параметр, usageHelp атрибутом usageHelp или usageHelp в командной строке.

Пример программы ниже демонстрирует автоматическую помощь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Command(version = "Help demo v1.2.3", header = "%nAutomatic Help Demo%n",
         description = "Prints usage help and version help when requested.%n")
class AutomaticHelpDemo implements Runnable {
 
    @Option(names = "--count", description = "The number of times to repeat.")
    int count;
 
    @Option(names = {"-h", "--help"}, usageHelp = true,
            description = "Print usage help and exit.")
    boolean usageHelpRequested;
 
    @Option(names = {"-V", "--version"}, versionHelp = true,
            description = "Print version information and exit.")
    boolean versionHelpRequested;
 
    public void run() {
        // NOTE: code like below is no longer required:
        //
        // if (usageHelpRequested) {
        //     new CommandLine(this).usage(System.err);
        // } else if (versionHelpRequested) {
        //     new CommandLine(this).printVersionHelp(System.err);
        // } else { ... the business logic
 
        for (int i = 0; i < count; i++) {
            System.out.println("Hello world");
        }
    }
 
    public static void main(String... args) {
        CommandLine.run(new AutomaticHelpDemo(), System.err, args);
    }
}

При выполнении с -h или --help программа выводит справку об использовании:

Аналогично, при выполнении с -V или --version программа выводит информацию о версии:

Методы, которые автоматически выводят справку:

  • CommandLine :: вызов
  • CommandLine :: бежать
  • CommandLine :: parseWithHandler (со встроенными обработчиками Run…)
  • CommandLine :: parseWithHandlers (со встроенными обработчиками Run…)

Методы, которые не выводят справку автоматически:

  • CommandLine :: синтаксического
  • CommandLine :: populateCommand

Лучшая поддержка подкоманд

В этом выпуске добавлены новые CommandLine::parseWithHandler . Эти методы предлагают ту же простоту использования, что и методы run и call , но с большей гибкостью и лучшей поддержкой вложенных подкоманд.

Рассмотрим, что должно делать приложение с подкомандами:

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

Picocli предоставляет некоторые строительные блоки для достижения этой цели, но приложение должно было соединить их вместе. Эта проводка, по сути, является стандартной и очень похожа между приложениями. Например, ранее приложение с подкомандами обычно содержало такой код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void main() {
    // 1. parse the command line
    CommandLine top = new CommandLine(new YourApp());
    List<CommandLine> parsedCommands;
    try {
        parsedCommands = top.parse(args);
    } catch (ParameterException ex) {
        // 2. handle incorrect user input for one of the subcommands
        System.err.println(ex.getMessage());
        ex.getCommandLine().usage(System.err);
        return;
    }
    // 3. check if the user requested help
    for (CommandLine parsed : parsedCommands) {
        if (parsed.isUsageHelpRequested()) {
            parsed.usage(System.err);
            return;
        } else if (parsed.isVersionHelpRequested()) {
            parsed.printVersionHelp(System.err);
            return;
        }
    }
    // 4. execute the most specific subcommand
    Object last = parsedCommands.get(parsedCommands.size() - 1).getCommand();
    if (last instanceof Runnable) {
        ((Runnable) last).run();
    } else if (last instanceof Callable) {
        Object result = ((Callable) last).call();
        // ... do something with result
    } else {
        throw new ExecutionException("Not a Runnable or Callable");
    }
}

Это довольно много стандартного кода. Picocli 2.0 предоставляет удобный метод, который позволяет вам свести все вышеперечисленное к одной строке кода, чтобы вы могли сосредоточиться на бизнес-логике вашего приложения:

1
2
3
4
5
6
7
8
public static void main() {
    // This handles all of the above in one line:
    // 1. parse the command line
    // 2. handle incorrect user input for one of the subcommands
    // 3. automatically print help if requested
    // 4. execute one or more subcommands
    new CommandLine(new YourApp()).parseWithHandler(new RunLast(), System.err, args);
}

Новый удобный метод — parseWithHandler . Вы можете создать свой собственный обработчик или использовать один из встроенных обработчиков. Picocli предоставляет реализации обработчиков для некоторых распространенных случаев использования.

Встроенными обработчиками являются RunFirst , RunLast и RunAll . Все они предоставляют автоматическую справку: если пользователь запрашивает UseHelp или VersionHelp, запрашиваемая информация печатается, и обработчик возвращается без дальнейшей обработки. Обработчики ожидают, что все команды реализуют либо java.lang.Runnable либо java.util.concurrent.Callable .

  • RunLast выполняет наиболее конкретную команду или подкоманду. Например, если пользователь вызвал java Git commit -m "commit message" , picocli считает Git командой верхнего уровня и передаст подкоманду. В этом примере подкоманда commit является наиболее конкретной командой, поэтому RunLast будет выполнять только эту подкоманду. Если подкоманд нет, выполняется команда верхнего уровня. RunLast теперь используется внутри Picocli для реализации существующих CommandLine::run и CommandLine::call удобства.
  • RunFirst выполняет только команду первого , верхнего уровня и игнорирует подкоманды.
  • RunAll выполняет команду верхнего уровня и все подкоманды, которые появились в командной строке.

Существует также метод parseWithHandlers , который аналогичен, но дополнительно позволяет указать пользовательский обработчик для неправильного ввода пользователя.

Улучшены методы run и call

CommandLine::call и CommandLine::run теперь поддерживают подкоманды и будут выполнять последнюю подкоманду, указанную пользователем. Ранее подкоманды были проигнорированы, и была выполнена только команда верхнего уровня.

Улучшенные исключения

Наконец, в этом выпуске все исключения picocli предоставляют метод getCommandLine который возвращает команду или подкоманду, если синтаксический анализ или выполнение не удалось. Ранее, если пользователь предоставлял неверный ввод для приложений с подкомандами, было трудно точно определить, какая именно подкоманда не смогла проанализировать ввод.

Вывод

Если вы уже используете Picocli, v2.0 является существенным обновлением. Если вы ранее не пользовались picocli, надеюсь, вышеизложенное заинтересовало вас попробовать.

Многие из этих улучшений возникли в результате отзывов пользователей и последующих обсуждений. Пожалуйста, не стесняйтесь задавать вопросы, запрашивать функции или давать другие отзывы о системе отслеживания проблем picocli.

Пожалуйста, пометьте проект на GitHub, если вам это нравится, и расскажите об этом друзьям!

Смотрите оригинальную статью здесь: Picocli 2.0: больше с меньшими затратами

Мнения, высказанные участниками Java Code Geeks, являются их собственными.