Picocli: лучший способ указать параметр с необязательным значением, которое печатает текущее значение, когда значение не указано

Я пишу REPL (поэтому я использую picocli внутри для анализа команд, введенных в приложении, а не для анализа аргументов командной строки), и у меня есть команда с опцией, которую я хочу вести следующим образом:

> cmd --myopt
Myopt value = 5
> cmd --myopt 4
> cmd --myopt
Myopt value = 4

То есть, если опция указана без значения, печатается текущее значение параметра, а если указано со значением, то значение устанавливается. Я думал сделать это так:

int value = 1; // default
@Option(names = {"-e", "--epsilon"}, arity = "0..1",
        description = "Acceptable values: [0, 1] default: ${DEFAULT-VALUE}")
void setValue(String strValue) {
    if (strValue == "") {
        printValue();
    } else {
        try {
            value = Integer.parseInt(strValue);
            // validate value
        } catch (NumberFormatException e) {
            // print help for this option
        }
    }
}

Это лучший способ? Есть ли другой способ зафиксировать значение по умолчанию в описании, при этом позволяя setValue знать, что значение не указано?

(См. также https://github.com/remkop/picocli/issues/490< /а> )


person Remko Popma    schedule 21.09.2018    source источник


Ответы (2)


На самом деле я выбрал другой подход; для моего приложения полезно иметь возможность напрямую назначать поле фактического типа (поскольку мы разрабатываем функцию, с помощью которой вы можете «обнаружить» команды, которые принимают аргументы различных типов, поэтому наличие поля фактического типа делает это обратный поиск проще).

Итак, я сделал это:

static class DoubleConverter implements ITypeConverter<Double> {
    public Double convert(String value) throws Exception {
        if(value.isEmpty()) return Double.NaN; // this is a special value that indicates the option was present without a value
        return Double.valueOf(value);
    }
}

@Option(names = {"-e", "--epsilon"}, arity="0..1", description="Acceptable values: [0, 1] default: 0.1", converter=DoubleConverter.class)
Double epsilon;

По сути, я использую преобразователь для хранения специального значения (в данном случае NaN, потому что в итоге мы использовали двойное), чтобы указать, что параметр присутствует без значения (что отличается от его отсутствия вообще, и в этом случае будет нуль).

Затем проверка и другое поведение выполняются в методе run(), как вы предложили:

@Override
public void run() {
    // null indicates the option was not present, so do nothing
    if(epsilon != null) {
        // NaN indicates the option was present but with no value, which means we should print the current value
        if(epsilon.equals(Double.NaN)) {
            // print current value from the application
            printEpsilonValue();
        }
        else {
            // validate value
            if(epsilon < 0.0 || epsilon > 1.0) {
                throw new ParameterException(spec.commandLine(), "Invalid parameter value");
            } else {
                // set the value in the application
                setEpsilonValue(episilon);
            }
        }
    }       
}

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

Я понимаю, что это необычный случай, но было бы неплохо поддерживать такую ​​опцию (не логическую с арностью 0..n) без необходимости прибегать к специальным значениям. Возможно, возможность указать другое поле, которое будет служить логическим значением, указывающим, присутствовала ли опция или нет. Тогда не было бы необходимости и в пользовательском преобразователе, и, возможно, все еще можно было бы указать значение по умолчанию (т. е. поле Double в этом случае было бы установлено по умолчанию, но если параметр не присутствовал, соответствующее логическое поле было бы быть ложным, чтобы приложение знало, что не следует использовать значение Double).

person marinier    schedule 21.09.2018
comment
Это напоминает мне github.com/remkop/picocli/issues/280 . Предлагаемое имя атрибута в этом тикете было implicit, так что вы могли бы объявить параметр вроде @Option(names = "-e", arity = "0..1", implicit = "NaN") Double epsilon; Это то, что вы имеете в виду? - person Remko Popma; 21.09.2018
comment
Я думаю, что это было бы эквивалентно тому, что я реализовал здесь через пользовательский конвертер. Однако мое предложение состоит в том, чтобы на самом деле предоставить механизм, который бы вообще избегал необходимости указывать специальное значение (в конце концов, могут быть случаи, когда специальное значение недоступно, например, с предопределенным перечислением). Примером механизма может быть указание другого логического поля, которое получает значение true, когда указана опция, и значение false в противном случае. Возможно, это поле можно указать через другой параметр в аннотации, или его можно аннотировать отдельно. - person marinier; 23.09.2018
comment
Это уже доступно: объект ParseResult имеет несколько методов, таких как hasMatchedOption, которые возвращают, соответствует ли параметр в командной строке (см. picocli.info/ ). Удовлетворит ли это ваши потребности? - person Remko Popma; 23.09.2018
comment
Я так думаю, но потенциальная трудность заключается в том, что я надеялся использовать parseWithHandlers с методом RunLast(), так как это помогает избежать большого количества шаблонов, но вызываемый Runnable не получает объект ParseResult. Есть ли способ получить объект ParseResult из моего Runnable? - person marinier; 24.09.2018
comment
Итак, я смог получить ParseResult в своем Runnable, внедрив CommandSpec через аннотацию @Spec, а затем в своем Runnable выполнив ParseResult result = spec.getCommandLine().getParseResult();. Однако это сообщает, что параметр присутствует, даже если он не имеет значения, что я и пытаюсь отличить. - person marinier; 24.09.2018
comment
Наконец-то я смог заставить это работать следующим образом: boolean present = !result.matchedOption("--epsilon").originalStringValues().get(0).isEmpty(); Я не уверен, что это очень понятно, хотя его можно было бы обернуть во вспомогательный метод с понятным именем, например hasValue("--epsilon"). Возможно, это путь. - person marinier; 24.09.2018
comment
Я думаю, что API аннотаций — это то место, где дополнительный атрибут может иметь наибольшее значение. Что-то вроде @Option(names=“-e”, arity=“0..1”, implicit=“1”) или, возможно, @Option(..., valueWhenOmitted=“1”) будет лучшим именем. - person Remko Popma; 25.09.2018
comment
Отличается ли это от указания значения по умолчанию? Было бы лучше полностью избегать значений по умолчанию, так как в некоторых случаях может не быть хорошего значения по умолчанию (например, с перечислением, где каждое значение имеет смысл). Лучше иметь возможность получить доступ к логическому значению где-то, где явно указано, имеет ли параметр значение или нет. По сути, это достижимо с помощью ParseResult, как обсуждалось выше, но решение с аннотациями было бы проще. Возможно, @Option(names="-e", arity="0..1", valueSetBoolean="myBooleanField"), где valueSetBoolean относится к другому полю того же класса, что и поле опции. - person marinier; 26.09.2018
comment
defaultValue — это значение параметра, когда параметр вообще не был указан в командной строке. implicitValue будет значением параметра, если имя параметра указано без значения. Я не уверен, что согласен с тем, что не может быть хорошего неявного значения, когда тип является аргументом перечисления: если значение параметра является необязательным, вам нужно значение перечисления, которое выражает случай, когда значение было опущено. В противном случае перечисление не является правильным типом для этой опции (или значение не должно быть необязательным). Мне не нравится идея одной аннотации, изменяющей 2 поля... - person Remko Popma; 26.09.2018
comment
Это правда, что если бы я создавал перечисление специально для параметра, я мог бы добавить значение для неустановленного. Однако, если я использую существующее перечисление, определяемое приложением, такого значения может не быть. Возможно, мне не следует этого делать, но это избавляет от необходимости переводить из одного типа перечисления в другой. Другой пример: если бы значение было целым числом. Может случиться так, что все целые числа допустимы, и, следовательно, нет подходящего целого числа для использования в качестве неявного значения. Но, вероятно, в большинстве случаев мы можем пожертвовать одним целым числом (например, MAX_VALUE). - person marinier; 28.09.2018
comment
К вашему сведению, меня все еще раздражает, что нам по существу необходимо использовать специальные значения для представления семантики отсутствующего значения. Наличие отдельного логического значения (или прямая проверка модели) напрямую представляет то, что мы на самом деле хотим знать. Возможно, мы могли бы ввести новый тип, например, OptionalValue‹type›, который действует как необязательный параметр с тремя состояниями. Значение по умолчанию будет отсутствовать, а затем преобразователь может указать, что оно присутствует, но не имеет значения, или установить фактическое значение. - person marinier; 28.09.2018
comment
Интересная идея! Также укажите это здесь github.com/remkop/picocli/issues/280 so @ pditommaso может присоединиться к обсуждению. - person Remko Popma; 28.09.2018

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

В качестве альтернативы можно изменить тип поля на String, поместить в поле аннотацию @Option и вызвать эту логику (то есть в методе setValue выше) из вашего метода run или call.

person Remko Popma    schedule 21.09.2018