Перейти к основному содержимому

Анализ параметров командной строки с помощью авиакомпании

· 6 мин. чтения

1. Введение

В этом руководстве мы познакомимся с Airline — библиотекой Java на основе аннотаций для создания интерфейсов командной строки (CLI).

2. Сценарий

При создании приложения командной строки естественно создать простой интерфейс, позволяющий пользователю формировать вывод по мере необходимости. Почти все играли с Git CLI и могут понять, насколько он мощный, но простой. Увы, мало какие инструменты пригодятся при построении такого интерфейса.

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

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

3. Настройка

Для начала добавим зависимость Airline к нашему pom.xm l:

<dependency>
<groupId>com.github.rvesse</groupId>
<artifactId>airline</artifactId>
<version>2.7.2</version>
</dependency>

4. Простой интерфейс командной строки

Создадим нашу точку входа в приложение — класс CommandLine :

@Cli(name = "foreach-cli",
description = "ForEach Airline Tutorial",
defaultCommand = Help.class)
public class CommandLine {
public static void main(String[] args) {
Cli<Runnable> cli = new Cli<>(CommandLine.class);
Runnable cmd = cli.parse(args);
cmd.run();
}
}

С помощью простой аннотации @Cli мы определили команду по умолчанию, которая будет выполняться в нашем приложении — команда Help .

Класс Help входит в состав библиотеки Airline и предоставляет команду справки по умолчанию с использованием параметров -h или –help .

Таким образом, базовая настройка завершена.

5. Наша первая команда

Давайте реализуем нашу первую команду, простой класс LoggingCommand , который будет контролировать уровень детализации наших журналов. Мы аннотируем класс с помощью @Command , чтобы убедиться, что при вызове пользователем setup-log применяется правильная команда :

@Command(name = "setup-log", description = "Setup our log")
public class LoggingCommand implements Runnable {

@Inject
private HelpOption<LoggingCommand> help;

@Option(name = { "-v", "--verbose" },
description = "Set log verbosity on/off")
private boolean verbose = false;

@Override
public void run() {
if (!help.showHelpIfRequested())
System.out.println("Verbosity: " + verbose);
}
}
}

Давайте подробнее рассмотрим команду из нашего примера.

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

Затем мы объявили логическую переменную verbose и аннотировали ее с помощью @Option , чтобы дать ей имя, описание, а также псевдоним -v/–verbose для представления нашей опции командной строки для управления многословием.

Наконец, внутри метода run мы указали нашей команде останавливаться всякий раз, когда пользователь просит о помощи.

Все идет нормально. Теперь нам нужно добавить нашу новую команду в основной интерфейс, изменив аннотацию @Cli :

@Cli(name = "foreach-cli",
description = "ForEach Airline Tutorial",
defaultCommand = Help.class,
commands = { LoggingCommand.class, Help.class })
public class CommandLine {
public static void main(String[] args) {
Cli<Runnable> cli = new Cli<>(CommandLine.class);
Runnable cmd = cli.parse(args);
cmd.run();
}
}

Теперь, если мы передадим setup-log -v нашей программе, она запустит нашу логику.

6. Ограничения и многое другое

Мы видели, как Airline безупречно генерирует CLI, но… это еще не все!

Мы можем указать ограничения (или ограничения) для наших параметров, чтобы обрабатывать допустимые значения, требования или зависимости и многое другое.

Мы собираемся создать класс DatabaseSetupCommand , который будет отвечать на команду setup-db ; так же, как мы делали ранее, но мы добавим немного специй.

Во-первых, мы запросим тип базы данных, приняв только 3 допустимых значения через @AllowedRawValues :

@AllowedRawValues(allowedValues = { "mysql", "postgresql", "mongodb" })
@Option(type = OptionType.COMMAND,
name = {"-d", "--database"},
description = "Type of RDBMS.",
title = "RDBMS type: mysql|postgresql|mongodb")
protected String rdbmsMode;

При использовании подключения к базе данных, без сомнения, пользователи должны указать конечную точку и некоторые учетные данные для доступа к ней. Мы позволим CLI обрабатывать это с помощью одного ( режим URL) или нескольких параметров ( режим хоста ). Для этого воспользуемся аннотацией @MutuallyExclusiveWith , пометив каждый параметр одним и тем же тегом:

@Option(type = OptionType.COMMAND,
name = {"--rdbms:url", "--url"},
description = "URL to use for connection to RDBMS.",
title = "RDBMS URL")
@MutuallyExclusiveWith(tag="mode")
@Pattern(pattern="^(http://.*):(d*)(.*)u=(.*)&p=(.*)")
protected String rdbmsUrl = "";

@Option(type = OptionType.COMMAND,
name = {"--rdbms:host", "--host"},
description = "Host to use for connection to RDBMS.",
title = "RDBMS host")
@MutuallyExclusiveWith(tag="mode")
protected String rdbmsHost = "";

Обратите внимание, что мы использовали декоратор @Pattern , который помогает нам определить формат строки URL.

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

Наконец, если пользователь выбрал режим хоста, мы должны попросить его предоставить свои учетные данные. Таким образом, один вариант зависит от другого. Мы можем добиться такого поведения с помощью аннотации @RequiredOnlyIf :

@RequiredOnlyIf(names={"--rdbms:host", "--host"})
@Option(type = OptionType.COMMAND,
name = {"--rdbms:user", "-u", "--user"},
description = "User for login to RDBMS.",
title = "RDBMS user")
protected String rdbmsUser;

@RequiredOnlyIf(names={"--rdbms:host", "--host"})
@Option(type = OptionType.COMMAND,
name = {"--rdbms:password", "--password"},
description = "Password for login to RDBMS.",
title = "RDBMS password")
protected String rdbmsPassword;

Что, если нам нужно использовать некоторые драйверы для обработки соединения с БД? А также, предположим, нам нужно получить более одного значения в одном параметре. Мы можем просто изменить тип опции на OptionType.ARGUMENTS или, что еще лучше, принять список значений:

@Option(type = OptionType.COMMAND,
name = {"--driver", "--jars"},
description = "List of drivers",
title = "--driver <PATH_TO_YOUR_JAR> --driver <PATH_TO_YOUR_JAR>")
protected List<String> jars = new ArrayList<>();

Теперь давайте не забудем добавить команду настройки базы данных в наш основной класс. В противном случае он будет недоступен в CLI.

7. Беги

Мы сделали это! Мы закончили наш проект, и теперь мы можем его запустить.

Как и ожидалось, без передачи каких-либо параметров вызывается справка :

$ foreach-cli

usage: foreach-cli <command> [ <args> ]

Commands are:
help Display help information
setup-db Setup our database
setup-log Setup our log

See 'foreach-cli help <command>' for more information on a specific command.

Если мы вместо этого выполним setup-log –help , мы получим:

$ foreach-cli setup-log --help

NAME
foreach-cli setup-log - Setup our log

SYNOPSIS
foreach-cli setup-log [ {-h | --help} ] [ {-v | --verbose} ]

OPTIONS
-h, --help
Display help information

-v, --verbose
Set log verbosity on/off

Наконец, предоставление параметров этим командам запустит правильную бизнес-логику.

8. Заключение

В этой статье мы создали простой, но мощный интерфейс командной строки с очень небольшим количеством кода.

Библиотека Airline с ее мощными функциями упрощает интерфейс командной строки, предоставляя нам общую, чистую и многоразовую инфраструктуру . Это позволяет нам, разработчикам, сосредоточиться на нашей бизнес-логике, а не тратить время на проектирование того, что должно быть тривиальным.

Как всегда, код можно найти на GitHub .