ValueParser.java
/*
* Copyright (c) 2016, Stein Eldar Johnsen
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.terminal.args;
import net.morimekta.terminal.args.impl.ArgumentImpl;
import net.morimekta.terminal.args.impl.OptionImpl;
import net.morimekta.terminal.args.impl.PropertyImpl;
import net.morimekta.terminal.args.parser.BigDecimalParser;
import net.morimekta.terminal.args.parser.BigIntegerParser;
import net.morimekta.terminal.args.parser.ByteParser;
import net.morimekta.terminal.args.parser.DirParser;
import net.morimekta.terminal.args.parser.DoubleParser;
import net.morimekta.terminal.args.parser.DurationParser;
import net.morimekta.terminal.args.parser.EnumParser;
import net.morimekta.terminal.args.parser.FileParser;
import net.morimekta.terminal.args.parser.FloatParser;
import net.morimekta.terminal.args.parser.IntegerParser;
import net.morimekta.terminal.args.parser.LongParser;
import net.morimekta.terminal.args.parser.OutputDirParser;
import net.morimekta.terminal.args.parser.OutputFileParser;
import net.morimekta.terminal.args.parser.PathParser;
import net.morimekta.terminal.args.parser.ShortParser;
import net.morimekta.terminal.args.parser.UnsignedIntegerParser;
import net.morimekta.terminal.args.parser.UnsignedLongParser;
import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Optional;
import java.util.function.Consumer;
/**
* Value parser interface. It converts from a string value (usually the
* CLI argument value) and converts it into a specific value type. Essentially
* this interface is meant to bridge two {@link Consumer}s, the target consumer
* (usually the value setter or adder), and the string consumer that the
* {@link OptionImpl} or {@link ArgumentImpl} needs.
* <p>
* E.g., the following code will create an option '--timestamp' that parses the
* argument value as a long (i64) and calls <code>bean.setTimestamp(long)</code>
* with that value:
* <code>
* new Option("--timestamp", null, "The timestamp", i64(bean::setTimestamp));
* </code>
*/
@FunctionalInterface
public interface ValueParser<T> {
/**
* A typed putter interface used to put key value pairs into maps, properties
* etc.
*
* @param <T> The value type when putting.
*/
@FunctionalInterface
interface TypedPutter<T> {
/**
* Put a typed value
*
* @param key The property key.
* @param value The property value.
*/
void put(String key, T value);
}
/**
* Parse the value into a typed instance.
*
* @param value The string value.
* @return The typed instance.
* @throws ArgException If the parsing failed.
*/
T parse(String value);
/**
* Make a string consumer to typed value consumer out of the converter.
*
* @param consumer The consumer to wrap.
* @return The string consumer.
*/
default Consumer<String> andApply(Consumer<T> consumer) {
return s -> consumer.accept(parse(s));
}
/**
* Make a property putter that calls a typed putter with the parsed value.
*
* @param putter The typed putter.
* @return The property putter.
*/
default PropertyImpl.Putter andPut(TypedPutter<T> putter) {
return (k, v) -> putter.put(k, parse(v));
}
/**
* Make a consumer that puts a specific value with the typed putter.
*
* @param putter the typed putter.
* @param key The property key.
* @return The string consumer.
*/
default Consumer<String> andPutAs(TypedPutter<T> putter, String key) {
return s -> putter.put(key, parse(s));
}
// *********************************
// *** STATIC HELPER METHODS ***
// *********************************
/**
* Get the default parser for the given type.
*
* @param type The type to get parser for.
* @return The value parser.
*/
@SuppressWarnings("rawtypes,unchecked")
static Optional<ValueParser<?>> defaultParser(Class<?> type) {
if (type == String.class) {
return Optional.of(str -> str);
}
if (type == Byte.class || type == Byte.TYPE) {
return Optional.of(i8());
}
if (type == Short.class || type == Short.TYPE) {
return Optional.of(i16());
}
if (type == Integer.class || type == Integer.TYPE) {
return Optional.of(i32());
}
if (type == Long.class || type == Long.TYPE) {
return Optional.of(i64());
}
if (type == BigInteger.class) {
return Optional.of(bigInt());
}
if (type == Float.class || type == Float.TYPE) {
return Optional.of(flt());
}
if (type == Double.class || type == Double.TYPE) {
return Optional.of(dbl());
}
if (type == BigDecimal.class) {
return Optional.of(bigDecimal());
}
if (type == Duration.class) {
return Optional.of(duration());
}
if (type.isEnum()) {
return Optional.of(oneOf((Class) type));
}
if (type == Path.class) {
return Optional.of(path());
}
if (type == File.class) {
return Optional.of(val -> path().parse(val).toFile());
}
return Optional.empty();
}
/**
* Convenience method to put a specific value into a putter.
*
* @param putter The putter.
* @param key The key to put.
* @return The string consumer.
*/
static Consumer<String> putAs(PropertyImpl.Putter putter, String key) {
return s -> putter.put(key, s);
}
/**
* Make a 32-bit integer parser.
*
* @return The parser.
*/
static ValueParser<Byte> i8() {
return new ByteParser();
}
/**
* Make a 32-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> i8(Consumer<Byte> target) {
return i8().andApply(target);
}
/**
* Make a 32-bit integer parser.
*
* @return The parser.
*/
static ValueParser<Short> i16() {
return new ShortParser();
}
/**
* Make a 32-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> i16(Consumer<Short> target) {
return i16().andApply(target);
}
/**
* Make a 32-bit integer parser.
*
* @return The parser.
*/
static ValueParser<Integer> i32() {
return new IntegerParser();
}
/**
* Make a 32-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> i32(Consumer<Integer> target) {
return i32().andApply(target);
}
/**
* Make a 32-bit integer parser.
*
* @return The parser.
*/
static ValueParser<Integer> ui32() {
return new UnsignedIntegerParser();
}
/**
* Make a 32-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> ui32(Consumer<Integer> target) {
return ui32().andApply(target);
}
/**
* Make a 64-bit integer parser.
*
* @return The parser.
*/
static ValueParser<Long> i64() {
return new LongParser();
}
/**
* Make a 64-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> i64(Consumer<Long> target) {
return i64().andApply(target);
}
/**
* Make a 64-bit integer parser.
*
* @return The parser.
*/
static ValueParser<Long> ui64() {
return new UnsignedLongParser();
}
/**
* Make a 64-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> ui64(Consumer<Long> target) {
return ui64().andApply(target);
}
/**
* Make a big integer parser.
*
* @return The parser.
*/
static ValueParser<BigInteger> bigInt() {
return new BigIntegerParser();
}
/**
* Make a big integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> bigInt(Consumer<BigInteger> target) {
return bigInt().andApply(target);
}
/**
* Make a double parser.
*
* @return The parser.
*/
static ValueParser<Float> flt() {
return new FloatParser();
}
/**
* Make a 64-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> flt(Consumer<Float> target) {
return flt().andApply(target);
}
/**
* Make a double parser.
*
* @return The parser.
*/
static ValueParser<Double> dbl() {
return new DoubleParser();
}
/**
* Make a 64-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> dbl(Consumer<Double> target) {
return dbl().andApply(target);
}
/**
* Make a double parser.
*
* @return The parser.
*/
static ValueParser<BigDecimal> bigDecimal() {
return new BigDecimalParser();
}
/**
* Make a 64-bit integer parsing consumer.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> bigDecimal(Consumer<BigDecimal> target) {
return bigDecimal().andApply(target);
}
/**
* Make an enum value parsing consumer.
*
* @param klass The enum class.
* @param <E> The enum type.
* @return The parser.
*/
static <E extends Enum<E>> ValueParser<E> oneOf(Class<E> klass) {
return new EnumParser<>(klass);
}
/**
* Make a file parsing consumer that refers to an existing file.
*
* @param klass The enum class.
* @param target The target consumer.
* @param <E> The enum type.
* @return The consumer wrapper.
*/
static <E extends Enum<E>> Consumer<String> oneOf(Class<E> klass, Consumer<E> target) {
return oneOf(klass).andApply(target);
}
/**
* Make a file parser that refers to an existing file.
*
* @return The parser.
*/
static ValueParser<Path> file() {
return new FileParser();
}
/**
* Make a file parsing consumer that refers to an existing file.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> file(Consumer<Path> target) {
return file().andApply(target);
}
/**
* Make a file parser that refers to an existing directory.
*
* @return The consumer wrapper.
*/
static ValueParser<Path> dir() {
return new DirParser();
}
/**
* Make a file parsing consumer that refers to an existing directory.
*
* @param target The target consumer.
* @return The parser.
*/
static Consumer<String> dir(Consumer<Path> target) {
return dir().andApply(target);
}
/**
* Make a file parser that refers either to a non-existing entry or an
* existing file, but not a directory or special device.
*
* @return The parser.
*/
static ValueParser<Path> outputFile() {
return new OutputFileParser();
}
/**
* Make a file parsing consumer that refers either to a non-existing entry or an
* existing file, but not a directory or special device.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> outputFile(Consumer<Path> target) {
return outputFile().andApply(target);
}
/**
* Make a parser that refers either to a non-existing entry or an
* existing directory, but not a file or special device.
*
* @return The parser.
*/
static ValueParser<Path> outputDir() {
return new OutputDirParser();
}
/**
* Make a parsing consumer that refers either to a non-existing entry or an
* existing directory, but not a file or special device.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> outputDir(Consumer<Path> target) {
return outputDir().andApply(target);
}
/**
* Make a parser that parses a path.
*
* @return The parser.
*/
static ValueParser<Path> path() {
return new PathParser();
}
/**
* Make a parsing consumer that parses a path.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> path(Consumer<Path> target) {
return path().andApply(target);
}
/**
* Make a parser that parses a duration.
*
* @return The parser.
*/
static ValueParser<Duration> duration() {
return new DurationParser();
}
/**
* Make a parsing consumer that parses a duration.
*
* @param target The target consumer.
* @return The consumer wrapper.
*/
static Consumer<String> duration(Consumer<Duration> target) {
return duration().andApply(target);
}
}