org.wildfly.swarm.cli.Option Maven / Gradle / Ivy
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
*
* Licensed 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 org.wildfly.swarm.cli;
import java.io.File;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.function.Supplier;
import javax.enterprise.inject.Vetoed;
import org.wildfly.swarm.internal.SwarmMessages;
/**
* A single option specification.
*
* An option may have a short (single-dash) or long (double-dash) variant.
*
* Each option may optionally take a value, which may or may not be allowed
* to be separated from the option.
*
* For instance, if {@link #valueMayBeSeparate(boolean)} is specified as false,
* then only -Dwhatever
would be allowed, while -D whatever
* would not.
*
* Upon successful match, each Option
has an {@link Action} associated
* which is called with the value (if any) and the state-holding {@link CommandLine}
* object.
*
* @author Bob McWhirter
*/
@Vetoed
public class Option {
private static final String EQUALS = "=";
private static final String HYPHEN = "-";
private static final String SPACE = " ";
private static final String DOUBLE_HYPHEN = "--";
private static final int MAX_LINE_LENGTH = 50;
/**
* Construct an empty option.
*/
public Option() {
}
/**
* Associate an action with this option.
*
* @param action The action to call when matched.
* @return This Option
object.
*/
public Option then(Action action) {
this.action = action;
return this;
}
/**
* Display formatted help associated with this option.
*
* @param out The output stream to display the help upon.
*/
public void displayHelp(PrintStream out) {
out.println(merge(summary(), description()));
}
private List summary() {
List list = new ArrayList<>();
if (this.shortArg != null) {
if (hasValue()) {
if (this.valueDescription.contains(EQUALS)) {
list.add(HYPHEN + this.shortArg + this.valueDescription);
} else {
list.add(HYPHEN + this.shortArg + EQUALS + this.valueDescription);
}
if (this.valueMayBeSeparate) {
if (this.valueDescription.contains(EQUALS)) {
list.add(HYPHEN + this.shortArg + this.valueDescription.replace(EQUALS, SPACE));
} else {
list.add(HYPHEN + this.shortArg + SPACE + this.valueDescription);
}
}
} else {
list.add(HYPHEN + this.shortArg);
}
}
if (this.longArg != null) {
if (hasValue()) {
if (this.valueDescription.contains(EQUALS)) {
list.add(DOUBLE_HYPHEN + this.longArg + this.valueDescription);
} else {
list.add(DOUBLE_HYPHEN + this.longArg + EQUALS + this.valueDescription);
}
if (this.valueMayBeSeparate) {
if (this.valueDescription.contains(EQUALS)) {
list.add(DOUBLE_HYPHEN + this.longArg + this.valueDescription.replace(EQUALS, SPACE));
} else {
list.add(DOUBLE_HYPHEN + this.longArg + SPACE + this.valueDescription);
}
}
} else {
list.add(DOUBLE_HYPHEN + this.longArg);
}
}
return list;
}
private List description() {
List list = new ArrayList<>();
StringTokenizer tokens = new StringTokenizer(this.description);
StringBuilder line = new StringBuilder();
while (tokens.hasMoreElements()) {
String token = tokens.nextToken();
if (line.length() + (SPACE + token).length() > MAX_LINE_LENGTH) {
list.add(line.toString());
line = new StringBuilder();
}
if (line.length() != 0) {
line.append(SPACE);
}
line.append(token);
}
list.add(line.toString());
return list;
}
private String merge(List summary, List desc) {
if (summary.size() > desc.size()) {
int diff = summary.size() - desc.size();
for (int i = 0; i < diff; ++i) {
desc.add("");
}
}
if (desc.size() > summary.size()) {
int diff = desc.size() - summary.size();
for (int i = 0; i < diff; ++i) {
summary.add("");
}
}
StringBuilder txt = new StringBuilder();
for (int i = 0; i < summary.size(); ++i) {
txt.append(String.format(" %-30s %s", summary.get(i), desc.get(i)));
txt.append("\n");
}
return txt.toString();
}
/**
* Specify a short (single-dash) variant.
*
* A single character following a single dash, such as -c
.
*
* @param shortArg The character.
* @return This Option
object.
*/
public Option withShort(Character shortArg) {
this.shortArg = shortArg;
return this;
}
/**
* Specify a long (double-dash) variant.
*
* A string following a double-dash, such as --help
.
*
* @param longArg The string (without dashes).
* @return This Option
object.
*/
public Option withLong(String longArg) {
this.longArg = longArg;
return this;
}
/**
* Set a human-readable description of this option.
*
* @param description The description.
* @return This Option
object.
*/
public Option withDescription(String description) {
this.description = description;
return this;
}
/**
* Specify that this option takes an argument value.
*
* A non-null string passed to this method will be used to signify
* that this option may take an argument, and to describe its format.
*
* For instance a string of name[=value]
would indicate
* that a name
is expected, with an optional value
* which should follow an equal sign.
*
* @param valueDescription
* @return
*/
public Option hasValue(String valueDescription) {
this.valueDescription = valueDescription;
return this;
}
private boolean hasValue() {
return this.valueDescription != null;
}
/**
* Indicate if the value to the option may be separated by whitespace. (defaults to true
).
*
* In some cases, because of ambiguity, it may be required to indicate that an
* argument may not be separate from the option. If an argument is to be
* provided, it must immediately follow the option without whitespace.
*
* @param valueMayBeSeparate true
(the default) if value may be separate, or false
to indicate it must be conjoined.
* @return This Option
object.
*/
public Option valueMayBeSeparate(boolean valueMayBeSeparate) {
this.valueMayBeSeparate = valueMayBeSeparate;
return this;
}
/**
* Specify a default value supplier for this option.
*
* @param supplier The supplier.
* @return This Option
object.
*/
public Option withDefault(Supplier supplier) {
this.supplier = supplier;
return this;
}
T defaultValue() {
if (this.supplier == null) {
return null;
}
return this.supplier.get();
}
boolean parse(ParseState state, CommandLine commandLine) throws Exception {
String cur = state.la();
if (cur == null) {
return false;
}
String value = null;
String matchedArg = null;
if (this.shortArg != null && cur.startsWith(HYPHEN + this.shortArg)) {
matchedArg = HYPHEN + this.shortArg;
if (hasValue() && cur.length() >= 3) {
if (cur.charAt(2) == '=') {
value = cur.substring(3);
} else {
value = cur.substring(2);
}
}
} else if (this.longArg != null && cur.startsWith(DOUBLE_HYPHEN + this.longArg)) {
matchedArg = DOUBLE_HYPHEN + this.longArg;
if (hasValue() && cur.length() >= this.longArg.length() + 3) {
if (cur.charAt(this.longArg.length() + 2) == '=') {
value = cur.substring(this.longArg.length() + 3);
} else {
value = cur.substring(this.longArg.length() + 2);
}
}
} else {
return false;
}
state.consume();
if (value != null && value.trim().isEmpty()) {
value = null;
}
if (hasValue() && value == null && !this.valueMayBeSeparate) {
throw SwarmMessages.MESSAGES.argumentRequired(matchedArg);
}
if (hasValue() && value == null) {
if (state.la() == null) {
throw SwarmMessages.MESSAGES.argumentRequired(matchedArg);
}
value = state.consume();
}
this.action.set(commandLine, this, value);
return true;
}
public String toString() {
return "[Option: short=" + this.shortArg + "; long=" + this.longArg + "; valueDescription=" + this.valueDescription + "]";
}
/**
* Helper to attempt forming a URL from a String in a sensible fashion.
*
* @param value The input string.
* @return The correct URL, if possible.
*/
public static URL toURL(String value) throws MalformedURLException {
try {
URL url = new URL(value);
return url;
} catch (MalformedURLException e) {
try {
return new File(value).toURI().toURL();
} catch (MalformedURLException e2) {
// throw the original
throw e;
}
}
}
private Character shortArg;
private String longArg;
private String valueDescription;
private boolean valueMayBeSeparate = true;
private Action action;
private String description;
private Supplier supplier;
/**
* Callback functional interface for matched options.
*/
public interface Action {
/**
* Perform some action, being passed the value and the CommandLine
.
*
* @param commandLine The state-holding CommandLine
object.
* @param option The matched option.
* @param value The value to the option, if any. Possibly null
.
* @throws if an error occurs
*/
void set(CommandLine commandLine, Option option, String value) throws Exception;
}
}