Java CLI Parser

Я знаю, что этот вопрос уже задавался, но я ищу Java-анализатор с определенной функциональностью. Я хочу, чтобы он мог определять дерево командной строки, используя при этом подкоманды (и глубину более одного уровня). Таким образом, у меня может быть 3-4 уровня команд, прежде чем я доберусь до опций. И эти подкоманды являются взаимоисключающими. Спасибо

4 ответа

Может быть сделано с JCommander. каждый JCommander Объект по своей сути является командой с произвольным числом параметров и / или произвольным количеством вложенных подкоманд, где верх JCommander Объект является корневой командой. Параметры команды всегда зависят от команды, для которой они были объявлены, и не влияют на параметры других команд. Интерфейс для добавления подкоманды не очень интуитивно понятен, но возможен (см. addCommand () метод)

Вот тестовый класс для проверки концепции:

public class Test{

@Test
public void nestedSubCommandTest() {
    GeneralOptions generalOpts = new GeneralOptions();
    JCommander jc = new JCommander(generalOpts);

    Command command = new Command();
    JCommander jc_command = addCommand(jc, "command", command);

    SubCommand1 subcommand1 = new SubCommand1();
    JCommander jc_subcommand1 = addCommand(jc_command, "subcommand1",
            subcommand1);

    SubCommand2 subcommand2 = new SubCommand2();
    JCommander jc_subcommand2 = addCommand(jc_subcommand1, "subcommand2",
            subcommand2);

    SubCommand3 subcommand3 = new SubCommand3();
    addCommand(jc_subcommand2, "subcommand3", subcommand3);

    jc.parse("--general-opt", 
        "command", "--opt", 
        "subcommand1",
        "subcommand2", "--sub-opt2", 
        "subcommand3", "--sub-opt3");

    assertTrue(generalOpts.opt);// --general-opt was set
    assertTrue(command.opt);// command --opt was set
    assertFalse(subcommand1.opt);// subcommand1 --sub-opt1 was not set
    assertTrue(subcommand2.opt);// subcommand2 --sub-opt2 was set
    assertTrue(subcommand3.opt);// subcommand3 --sub-opt3 was set
}

private static JCommander addCommand(JCommander parentCommand,
        String commandName, Object commandObject) {
    parentCommand.addCommand(commandName, commandObject);
    return parentCommand.getCommands().get(commandName);
}

public static class GeneralOptions {
    @Parameter(names = "--general-opt")
    public boolean opt;
}

@Parameters
public static class Command {
    @Parameter(names = "--opt")
    public boolean opt;
}

@Parameters
public static class SubCommand1 {
    @Parameter(names = "--sub-opt1")
    public boolean opt;
}

@Parameters
public static class SubCommand2 {
    @Parameter(names = "--sub-opt2")
    public boolean opt;
}

@Parameters
public static class SubCommand3 {
    @Parameter(names = "--sub-opt3")
    public boolean opt;
}
}

Редактировать: Как повторно использовать команды.

Решение 1, используйте наследование:

  public class CommonArgs{
    @Parameter(names="--common-opt")
    public boolean isCommonOpt;
  }

  @Parameters(description = "my command 1")
  public class MyCommand1 extends CommonArgs{}

  @Parameters(description = "my command 2")
  public class MyCommand2 extends CommonArgs{}

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

Решение 2, используя шаблон композиции (см. Документ здесь):

  public class CommonArgs{
    @Parameter(names="--common-opt")
    public boolean isCommonOpt;
  }

  @Parameters(description = "my command 1")
  public class MyCommand1{
    @ParametersDelegate
    public CommonArgs commonArgs = new CommonArgs();
  }

  @Parameters(description = "my command 2")
  public class MyCommand2{
    @ParametersDelegate
    public CommonArgs commonArgs = new CommonArgs();
  }

Здесь вложенный commonArgs параметры классов будут обрабатываться так, как если бы они были прямыми параметрами командного класса. Вы можете добавить столько делегатов, сколько пожелаете, или даже вкладывать делегатов в другие делегаты и так далее. Чтобы получить значение делегированной опции после разбора, просто выполните myCommand1.commonArgs.isCommonOpt, так далее.

picocli поддерживает вложенные подкоманды на произвольную глубину.

CommandLine commandLine = new CommandLine(new MainCommand())
        .addSubcommand("cmd1", new ChildCommand1()) // 1st level
        .addSubcommand("cmd2", new ChildCommand2())
        .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // 2nd level
                .addSubcommand("cmd3sub1", new GrandChild3Command1())
                .addSubcommand("cmd3sub2", new GrandChild3Command2())
                .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // 3rd
                        .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
                        .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
                                // etc
                )
        );

Вам также может понравиться его использование со стилями и цветами ANSI.

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

Справка по использованию легко настраивается с помощью аннотаций.

  • аннотации на основе
  • подкоманды в стиле git
  • вложенные подкоманды
  • строго типизированные параметры
  • строго типизированные позиционные параметры
  • настраиваемое преобразование типов
  • многозначные варианты
  • интуитивно понятная модель для того, сколько аргументов потребляет поле
  • свободный API
  • Кластерные короткие опции в стиле POSIX
  • Длинные варианты в стиле GNU
  • позволяет любой префикс
  • ANSI цвета в использовании помогают
  • настраиваемая справка по использованию
  • один исходный файл: включите в качестве источника, чтобы сохранить приложение в одной банке

Если ваше командное выражение сложное, то вы можете определить синтаксис, написать его BNF и использовать библиотеки, такие как JavaCC или AntLR, для создания собственного синтаксического анализатора.

Я думаю, что будет лучше разбить эту многоуровневую команду на несколько CLI-инструментов.

Вместо:
program cmd sub-cmd sub-sub-cmd -option1 argument -option2 argument

Добавьте один или два уровня к имени программы:
program-cmd-sub-cmd sub-sub-cmd -option1 argument -option2 argument

Пример реального слова:
svn-add -q -N foo.c

Вы можете хранить все необходимые классы в одном JAR-файле (и использовать их сколько угодно), просто добавив несколько "основных" точек входа. Для базового CLI-разбора я также рекомендую JCommander,

Другие вопросы по тегам