Статьи

Миграция с Commons CLI на picocli

Apache Commons CLI, первоначально выпущенный в 2002 году, является, пожалуй, наиболее широко используемым анализатором командной строки java, но его API показывает его возраст. Приложения, ищущие современный подход с минимумом стандартного кода, могут быть заинтересованы в picocli . Почему стоит мигрировать, и как вы переносите приложение на основе CLI Commons в picocli? Picocli предлагает свободный API с надежной типизацией, справкой по использованию цветов ANSI, автозаполнением и множеством других функций. Давайте посмотрим, используя Checkstyle в качестве примера.

Зачем мигрировать?

Стоит ли переходить с Commons CLI на picocli? В чем выгода перехода от одного анализатора командной строки к другому? Это больше, чем просто косметический ремонт нашего приложения?

Опыт конечного пользователя

Commons CLI

Каковы преимущества для конечных пользователей?

Завершение командной строки . Приложения на основе Picocli могут иметь завершение командной строки в оболочках bash и zsh, а также в приложениях интерактивной оболочки на основе JLine.

Красивые, хорошо читаемые сообщения об использовании . Справка по использованию, сгенерированная Commons CLI, немного минималистична. Из коробки Picocli генерирует справку, в которой для контраста используются стили и цвета ANSI, чтобы подчеркнуть важную информацию, такую ​​как команды, параметры и параметры. Макет справочного сообщения легко настроить с помощью аннотаций. Кроме того, есть справочный API, если вы хотите что-то другое. Смотрите скриншоты README для некоторых примеров скриншотов.

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

Опыт разработчика

Commons CLI

Каковы преимущества для вас как разработчика?

Как правило, приложение picocli будет иметь гораздо меньше кода, чем эквивалент CLI Commons. Аннотации picocli позволяют приложениям определять опции и позиционные параметры декларативным способом, когда вся информация находится в одном месте. Кроме того, picocli предлагает ряд удобств, таких как преобразование типов и автоматическая справка, которые заботятся о некоторых механизмах, чтобы приложение могло больше сосредоточиться на бизнес-логике. Остальная часть этой статьи покажет это более подробно.

Документация : picocli имеет обширное руководство пользователя и подробный Javadoc .

Устранение неисправностей Picocli имеет встроенную функцию трассировки для облегчения поиска неисправностей. Конечные пользователи могут использовать системное свойство picocli.trace для контроля уровня трассировки. Поддерживаемые уровни: OFF , WARN , INFO и DEBUG . Уровень трассировки по умолчанию — WARN .

Будущее расширение

Commons CLI

Наконец, кроме немедленной отдачи, есть ли какие-либо будущие выгоды, которые вы получите, перейдя от Commons CLI к picocli?

Picocli имеет много дополнительных функций . Ваше приложение может еще не использовать эти функции, но если вы хотите расширить свое приложение в будущем, Picocli имеет поддержку вложенных подкоманд (и подкоманд до любой глубины), смесей для повторного использования, может легко интегрироваться с контейнерами Dependency Injection, и растущая структура инструмента для генерации исходного кода, документации и файлов конфигурации из модели Commandocpec picocli.

Наконец, Picocli активно поддерживается , в то время как CLI Commons, похоже, почти бездействует с 6 выпусками за 16 лет.

Пример миграции: CheckStyle

Commons CLI

Приложение командной строки должно сделать три вещи:

  1. Определите поддерживаемые параметры
  2. Разбор аргументов командной строки
  3. Обработать результаты

Давайте сравним, как это делается в Commons CLI и в picocli, используя в качестве примера утилиту командной строки CheckStyle com.puppycrawl.tools.checkstyle.Main .

Полный исходный код до и после миграции находится на GitHub.

Определение параметров и позиционных параметров

Определение параметров с помощью Commons CLI

CLI Commons имеет несколько способов определения параметров: Options.addOption , создание new Options(…​) и вызов методов для этого объекта, устаревший класс OptionBuilder и рекомендуемый класс Option.Builder .

Main класс Options.addOption использует метод Options.addOption . Он начинается с определения количества констант для имен опций:

01
02
03
04
05
06
07
08
09
10
/** Name for the option 's'. */
private static final String OPTION_S_NAME = "s";
 
/** Name for the option 't'. */
private static final String OPTION_T_NAME = "t";
 
/** Name for the option '--tree'. */
private static final String OPTION_TREE_NAME = "tree";
 
... // and more. Checkstyle Main has 26 options in total.

Метод Main.buildOptions использует эти константы для создания и возврата объекта Options командной строки Commons, который определяет поддерживаемые параметры:

01
02
03
04
05
06
07
08
09
10
11
private static Options buildOptions() {
    final Options options = new Options();
    options.addOption(OPTION_C_NAME, true, "Sets the check configuration file to use.");
    options.addOption(OPTION_O_NAME, true, "Sets the output file. Defaults to stdout");
    ...
    options.addOption(OPTION_V_NAME, false, "Print product version and exit");
    options.addOption(OPTION_T_NAME, OPTION_TREE_NAME, false,
            "Print Abstract Syntax Tree(AST) of the file");
    ...
    return options;
}

Определение параметров с помощью Picocli

В picocli вы можете определять поддерживаемые опции либо программно со сборщиками, аналогично подходу CLI Commons, либо декларативно с аннотациями.

Программный API Picocli может быть полезен для динамических приложений, где не все параметры известны заранее. Если вас интересует программный подход, взгляните на классы CommandSpec , OptionSpec и PositionalParamSpec . Смотрите также Programmatic API для более подробной информации.

В этой статье мы будем использовать аннотации picocli. Для примера CheckStyle это будет выглядеть примерно так:

01
02
03
04
05
06
07
08
09
10
11
@Option(names = "-c", description = "Sets the check configuration file to use.")
private File configurationFile;
 
@Option(names = "-o", description = "Sets the output file. Defaults to stdout")
private File outputFile;
 
@Option(names = "-v", versionHelp = true, description = "Print product version and exit")
private boolean versionHelpRequested;
 
@Option(names = {"-t", "--tree"}, description = "Print Abstract Syntax Tree(AST) of the file")
private boolean printAST;

сравнение

декларативный

Commons CLI

С помощью Commons CLI вы создаете спецификацию, вызывая метод со значениями String. Одним из недостатков такого API-интерфейса является то, что хороший стиль вынуждает клиентский код определять константы, чтобы избежать «магических значений», как покорно делает класс Checkstyle Main .

С picocli вся информация в одном месте. Аннотации принимают только строковые литералы, поэтому определение и использование автоматически помещаются вместе без необходимости объявления констант. Это приводит к более чистому и меньшему количеству кода.

Сильно Типизированный

Commons CLI

CLI Commons использует логический флаг, чтобы указать, принимает ли параметр аргумент или нет.

Picocli позволяет напрямую использовать типы. В зависимости от типа picocli «знает», сколько аргументов требуется для опции: boolean поля не имеют аргументов, поля Collection , Map и array могут иметь ноль для любого количества аргументов, а любой другой тип означает, что опции принимают один аргумент. аргумент. Это может быть настроено (см. arity ), но в большинстве случаев значение по умолчанию достаточно хорошее.

Picocli рекомендует использовать типы enum для опций или позиционных параметров с ограниченным набором допустимых значений. Picocli не только проверит ввод для вас, вы также можете отобразить все значения в справочном сообщении об использовании с помощью @Option(description = "Valid values: ${COMPLETION-CANDIDATES}") . Перечисления также позволяют завершение командной строки, чтобы предложить кандидатов на завершение для значений параметра.

Меньше кода

Commons CLI

Picocli преобразует значение параметра String в тип поля. Это не только спасает приложение от выполнения этой работы, но также обеспечивает некоторую минимальную проверку пользовательского ввода. Если преобразование заканчивается неудачей, ParameterException с удобным сообщением об ошибке.

Давайте посмотрим на пример, чтобы увидеть, насколько это полезно. Класс Checkstyle Main определяет параметр -x , --exclude-regexp который позволяет пользователям использовать ряд регулярных выражений для исключаемых каталогов.

С помощью Commons CLI вам необходимо преобразовать значения String, которые были сопоставлены в командной строке, в объекты java.util.regex.Pattern в приложении:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
/**
 * Gets the list of exclusions from the parse results.
 * @param commandLine object representing the result of parsing the command line
 * @return List of exclusion patterns.
 */
private static List<Pattern> getExclusions(CommandLine commandLine) {
    final List<Pattern> result = new ArrayList<>();
 
    if (commandLine.hasOption(OPTION_X_NAME)) {
        for (String value : commandLine.getOptionValues(OPTION_X_NAME)) {
            result.add(Pattern.compile(value));
        }
    }
    return result;
}

По контракту в picocli вы просто объявите опцию в поле List<Pattern> (или массива Pattern[] ). Поскольку в picocli есть встроенный конвертер для java.util.regex.Pattern , все, что нужно, это объявить опцию. Код конверсии полностью исчезает. Picocli будет создавать и заполнять список, если в командной строке указан один или несколько параметров -x .

1
2
3
4
/** Option that allows users to specify a regex of paths to exclude. */
@Option(names = {"-x", "--exclude-regexp"},
        description = "Regular expression of directory to exclude from CheckStyle")
private List<Pattern> excludeRegex;

Имена опций

Commons CLI

CLI Commons поддерживает «короткие» и «длинные» опции, такие как -t и --tree . Это не всегда то, что вы хотите.

Picocli позволяет опции иметь любое количество имен с любым префиксом. Например, это было бы прекрасно в picocli:

1
@Option(names = {"-cp", "-classpath", "--class-path"})

Позиционные параметры

Commons CLI

В Commons CLI вы не можете определять позиционные параметры заранее. Вместо этого в его CommandLine результатов анализа CommandLine есть метод getArgs который возвращает позиционные параметры в виде массива строк. Класс Checkstyle Main использует это для создания списка объектов File для обработки.

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

Например, классу Checkstyle Main необходим список файлов для обработки, поэтому мы объявляем поле и аннотируем его с помощью @Parameters . Атрибут arity arity = "1..*" означает, что должен быть указан хотя бы один файл, иначе picocli покажет сообщение об ошибке об отсутствующем аргументе.

1
2
@Parameters(paramLabel = "file", arity = "1..*", description = "The files to process")
private List<File> filesToProcess;

Параметры справки

Commons CLI

В Commons CLI на удивление сложно создать приложение с обязательной опцией, которая также имеет опцию --help . CLI Commons не имеет специальной обработки для параметров справки и будет жаловаться на отсутствующую обязательную опцию, когда пользователь указывает <command> --help .

Picocli имеет встроенную поддержку общих (и пользовательских) параметров справки .

Разбор аргументов командной строки

Commons CLI

CLI Commons имеет интерфейс CommandLineParser с методом parse который возвращает CommandLine представляющий результат разбора. Затем приложение вызывает CommandLine.hasOption(String) чтобы увидеть, был ли установлен флаг, или CommandLine.getOptionValue(String) чтобы получить значение параметра.

Picocli заполняет аннотированные поля при анализе аргументов командной строки. Методы Picocli parse…​ также возвращают ParseResult который можно запросить, какие параметры были указаны и какое значение они имели, но большинству приложений на самом деле не нужно использовать класс ParseResult поскольку они могут просто проверять значение, введенное в аннотированный поля при разборе.

Обработка результатов

Commons CLI

Бизнес-концепция, изолированная на белом

Когда синтаксический анализатор сделан, приложение должно запустить свою бизнес-логику, но сначала нужно проверить некоторые вещи:

  • Была ли запрошена информация о версии или использовании? Если это так, распечатайте запрошенную информацию и выйдите.
  • Был ли пользовательский ввод недействительным? Распечатайте сообщение об ошибке с подробной информацией, распечатайте справку по использованию и выйдите.
  • Наконец, запустите бизнес-логику и исправьте ошибки, возникающие в бизнес-логике

С Commons CLI это выглядит примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
int exitStatus;
try {
    CommandLine commandLine = new DefaultParser().parse(buildOptions(), args);
 
    if (commandLine.hasOption(OPTION_VERSION)) { // --version
        System.out.println("Checkstyle version: " + version());
        exitStatus = 0;
    } else if (commandLine.hasOption(OPTION_HELP)) { // --help
        printUsage(System.out);
        exitStatus = 0;
    } else {
        exitStatus = runBusinessLogic(); // business logic
    }
} catch (ParseException pex) { // invalid input
    exitStatus = EXIT_WITH_CLI_VIOLATION;
    System.err.println(pex.getMessage());
    printUsage(System.err);
} catch (CheckstyleException ex) { // business logic exception
    exitStatus = EXIT_WITH_CHECKSTYLE_EXCEPTION_CODE;
    ex.printStackTrace();
}
System.exit(exitStatus);

Picocli предлагает несколько удобных методов, которые заботятся о большинстве из вышеперечисленного Благодаря тому, что ваша команда реализует Runnable или Callable , приложение может сосредоточиться на бизнес-логике. В простейшем случае это может выглядеть примерно так:

1
2
3
4
5
6
7
8
9
public class Main implements Callable<Integer> {
    public static void main(String[] args) {
        CommandLine.call(new Main(), args);
    }
 
    public Integer call() throws CheckstyleException {
        // business logic here
    }
}

Класс Checkstyle Main должен контролировать код завершения и предъявляет некоторые строгие внутренние требования к обработке ошибок, поэтому мы в итоге не использовали вспомогательные методы и сохранили обработку результатов анализа очень похожей на ту, что была с Commons CLI. Улучшение этой области находится в списке задач Picocli.

Справка по использованию

Picocli использует цвета и стили ANSI в сообщении помощи по использованию на поддерживаемых платформах. Это не только хорошо выглядит, но и снижает когнитивную нагрузку на пользователя: контрастность выделяет важную информацию, такую ​​как команды, опции и параметры, из окружающего текста.

Приложения могут также использовать цвета и стили ANSI в описании или других разделах справочного сообщения об использовании с простой разметкой, такой как @|bg(red) text with red background|@ . См. Соответствующий раздел руководства пользователя.

Для CheckStyle мы сохранили его до минимума, и результирующий вывод для CheckStyle выглядит следующим образом:

Commons CLI

Подведение итогов: последний совет

Имейте в виду, что синтаксический анализатор Commons CLI по умолчанию распознает длинные и одинарные ( -- ) и длинные ( -- ) дефисы, хотя в справке по использованию будут отображаться только опции с двойными дефисами. Вам нужно решить, стоит ли продолжать поддерживать это.

В picocli вы можете использовать @Option(names = "-xxx", hidden = true) для объявления длинных опций с одним дефисом, если вы хотите имитировать то же поведение, что и CLI Commons: скрытые опции в picocli не отображаются при использовании сообщение помощи.

Вывод

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

Обновление: проект CheckStyle принял пул-запрос с изменениями в этой статье. Начиная с CheckStyle 8.15 его инструменты командной строки будут использовать picocli. Похоже, что сопровождающие CheckStyle были довольны результатом:

Checkstyle перенесен из Apache CLI в @picocli (будет выпущен в 8.15), наконец, документация по аргументам CLI теперь хорошо организована декларативным способом в коде, а CLI checkstyle следует рекомендациям CLI.

— сопровождающий CheckStyle Роман Иванов

Смотрите оригинальную статью здесь: Миграция от CLI Commons к picocli

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