/**
* $Id$
* $Date$
*
*/
package org.xmlsh.core;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xmlsh.util.StringPair;
import org.xmlsh.util.Util;
public class Options implements Cloneable {
/*
* A single option is of the form [+]short[=long][:[+]] Multiple options are
* separated by ","
*
* [+] If option starts with a "+" then it is a boolean option that at
* runtime can start with a + or -. for example cmd +opt short The short
* form of the option. Typically a single letter =long The long form of the
* option. Typically a word [:[+]] If followed by a ":" then the option is
* required to have a value which is taken from the next arg If followed by
* a ":+" then the option can be specified multiple times
*
*
* Examples
*
* a Single optional option "-a" a=all Long form accepted either "-a" or
* "-all" +v=verbose Long or short form may be specified with - or + e.g. -v
* or +verbose i: Option requires a value. e.g -i inputfile i:+ Option may
* be specified multiple times with values. e.g. -i input1 -i input2
*/
public static class OptionDef implements Cloneable {
private String name; // short name typically 1 letter
private String longname; // long name/alias
private boolean expectsArg; // expects an argument
private boolean multiple; // may occur multiple times
private boolean flag; // may be preceeded by +
public OptionDef(String name, String longname, boolean arg,
boolean multi, boolean plus) {
setName(name);
setLongname(longname);
setExpectsArg(arg);
setMultiple(multi);
setFlag(plus);
}
OptionDef() {
name = longname = "-";
expectsArg = false;
multiple = false;
flag = false;
}
// Clone
public OptionDef(OptionDef that) {
this(that.name, that.longname, that.expectsArg, that.multiple, that.flag);
}
public OptionDef copy() {
return new OptionDef(this);
}
@Override
public Object clone() {
return copy();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLongname() {
return longname;
}
public void setLongname(String longname) {
this.longname = longname;
}
public boolean isExpectsArg() {
return expectsArg;
}
public void setExpectsArg(boolean expectsArg) {
this.expectsArg = expectsArg;
}
public boolean isMultiple() {
return multiple;
}
public void setMultiple(boolean multiple) {
this.multiple = multiple;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public boolean equals(Object that) {
if(!(that instanceof OptionDef))
return false;
OptionDef othat = (OptionDef) that;
if(this == that)
return true;
return name.equals(othat.name) && longname.equals(othat.longname)
&& expectsArg == othat.expectsArg
&& multiple == othat.multiple && flag == othat.flag;
}
public boolean isOption(String str) {
assert (str != null);
return Util.isEqual(str, name) || Util.isEqual(str, longname);
}
}
static OptionDef mDashDash = new OptionDef();
public static class OptionValue {
private OptionDef option;
private boolean optflag = true; // true if '-' , false if '+'
private XValue value = null;
OptionValue() {
option = mDashDash;
}
OptionValue(OptionDef def, boolean flag) {
option = def;
optflag = flag;
}
// Set a single value
void setValue(XValue v) throws UnexpectedException,
InvalidArgumentException {
if(option.isExpectsArg())
value = v;
else if(option.isFlag())
optflag = v.toBoolean() ? true : false;
else
throw new UnexpectedException("Unexpected use of option: "
+ option.name);
}
/**
* @return the option
*/
public OptionDef getOptionDef() {
return option;
}
/**
* @return the arg
*/
public XValue getValue() {
return option.isExpectsArg() ? value : XValue.newXValue(optflag);
}
public boolean getFlag() {
return optflag;
}
public String toStringValue() {
return getValue().toString();
}
}
@SuppressWarnings("serial")
public static class OptionDefs extends ArrayList<OptionDef>
implements Cloneable {
public OptionDefs() {
super();
}
public OptionDefs(OptionDefs that) {
that.forEach(o -> super.add(o.copy()));
}
public OptionDefs(String sdefs) {
this(parseDefs(sdefs));
}
public OptionDefs(List<? extends OptionDef> c) {
for(OptionDef d : c)
addOptionDef(d);
}
public OptionDefs(OptionDef o) {
super.add(o); // Safe - only 1 option
}
public OptionDefs addOptionDef(OptionDef def) {
return addOptionDef(def, false);
}
public OptionDefs addOptionDef(OptionDef def, boolean ifAbsent) {
OptionDef exists = getOptionDef(def.getName());
if(exists != null) {
if(ifAbsent)
return this;
mLogger.warn("Redefined option def: {}", def.getName());
remove(exists);
}
add(def);
return this;
}
public OptionDefs(OptionDef... defs) {
addOptionDefs(defs);
}
public OptionDefs addOptionDefs(OptionDefs defs) {
return addOptionDefs(defs, false);
}
public OptionDefs addOptionDefsIfAbsent(OptionDefs defs) {
return addOptionDefs(defs, true);
}
public OptionDefs addOptionDefs(OptionDefs defs, boolean ifAbsent) {
for(OptionDef def : defs)
addOptionDef(def, ifAbsent);
return this;
}
public OptionDefs addOptionDefsIfAbsent(OptionDef... defs) {
return addOptionDefs(true, defs);
}
public OptionDefs addOptionDefs(boolean ifAbsent, OptionDef... defs) {
for(OptionDef od : defs)
addOptionDef(od, ifAbsent);
return this;
}
public OptionDefs addOptionDefs(OptionDef... defs) {
for(OptionDef od : defs)
addOptionDef(od);
return this;
}
public OptionDef getOptionDef(String str) {
for(OptionDef opt : this) {
if(Util.isEqual(str, opt.getName())
|| Util.isEqual(str, opt.getLongname()))
return opt;
}
return null;
}
public OptionDefs withOption(OptionDef def) {
return addOptionDef(def);
}
public OptionDefs withOption(OptionDef def, boolean ifAbsent) {
return addOptionDef(def, ifAbsent);
}
public OptionDefs withOptions(OptionDefs defs, boolean ifAbsent) {
return addOptionDefs(defs, ifAbsent);
}
public OptionDefs withOptions(OptionDefs defs) {
return addOptionDefs(defs);
}
public OptionDefs withOptions(String sdefs) {
return withOptions(parseDefs(sdefs));
}
public OptionDefs withOptions(String sdefs, boolean ifAbsent) {
return withOptions(parseDefs(sdefs), ifAbsent);
}
public static OptionDef parseDef(String sdef) {
boolean bHasArgs = false;
boolean bHasMulti = false;
boolean bPlus = false;
if(sdef.startsWith("+")) {
bPlus = true;
sdef = sdef.substring(1);
}
else
if(sdef.endsWith(":")) {
sdef = sdef.substring(0, sdef.length() - 1);
bHasArgs = true;
}
else if(sdef.endsWith(":+")) {
sdef = sdef.substring(0, sdef.length() - 2);
bHasArgs = true;
bHasMulti = true;
}
// Check for optional long-name
// a=longer
StringPair pair = new StringPair(sdef, '=');
if(pair.hasDelim())
return new OptionDef(pair.getLeft(), pair
.getRight(), bHasArgs, bHasMulti, bPlus);
else
return new OptionDef(sdef, null, bHasArgs,
bHasMulti, bPlus);
}
public static OptionDefs parseDefs(String sdefs) {
OptionDefs defs = new OptionDefs();
String[] adefs = sdefs.trim().split("\\s*,\\s*");
for(String sdef : adefs) {
defs.addOptionDef(parseDef(sdef));
}
return defs;
}
public static OptionDefs parseDefs(String defs1, String... defsv) {
OptionDefs defs = parseDefs(defs1);
for(String sd : defsv)
defs.addOptionDef(parseDef(sd));
return defs;
}
}
private OptionDefs mDefs;
private List<XValue> mRemainingArgs;
private List<OptionValue> mOptions;
static Logger mLogger = LogManager.getLogger();
/*
* Parse a string list shorthand for options defs "a,b:,cde:" =>
* ("a",false),("b",true),("cde",true)
*/
public boolean hasDashDash() {
return hasOpt(mDashDash);
}
public static OptionDefs parseDefs(String sdefs) {
return OptionDefs.parseDefs(sdefs);
}
public Options(String options) {
this(parseDefs(options));
}
public Options(OptionDefs... options) {
mDefs = new OptionDefs();
for(OptionDefs o : options) {
mDefs.addOptionDefs(o);
}
}
// @Depreciated
public Options(String option_str, OptionDefs option_list) {
this(parseDefs(option_str).withOptions(option_list));
}
// Default constructor
public Options() {
mDefs = new OptionDefs();
}
public OptionDefs addOptionDefs(String option_str, boolean ifAbsent) {
OptionDefs option_list = parseDefs(option_str);
addOptionDefs(option_list, ifAbsent);
return option_list;
}
public OptionDef addOptionDef(String option_str, boolean ifAbsent) {
OptionDef opt = parseDef(option_str);
addOptionDef(opt, ifAbsent);
return opt;
}
private OptionDef parseDef(String option_str) {
return OptionDefs.parseDef(option_str);
}
public Options addOptionDefs(OptionDefs option_list, boolean ifAbsent) {
mDefs.addOptionDefs(option_list, ifAbsent);
return this;
}
public Options withOptionDef(OptionDef def, boolean ifAbsent) {
return addOptionDef(def, ifAbsent);
}
public Options withOptionDefs(OptionDefs def, boolean ifAbsent) {
return addOptionDefs(def, ifAbsent);
}
public Options addOptionDef(OptionDef def, boolean ifAbsent) {
mDefs.addOptionDef(def, ifAbsent);
return this;
}
public OptionDef getOptDef(String str) {
assert (mDefs != null);
return mDefs.getOptionDef(str);
}
public Options withDefaultOptions(List<OptionValue> defaults) {
defaults.forEach(
v -> {
OptionValue ev = this.getOpt(v.getOptionDef());
if(ev == null)
mOptions.add(v);
});
return this;
}
public Options withDefaultArgs(List<XValue> defaults)
throws UnexpectedException, InvalidArgumentException, UnknownOption {
if(defaults == null || defaults.isEmpty())
return this;
return withDefaultOptions(
parseValues(mDefs, defaults, new ArrayList<XValue>(), true));
}
public Options parse(List<XValue> args, List<XValue> defaults)
throws UnknownOption, UnexpectedException, InvalidArgumentException {
return parse(args).withDefaultArgs(defaults);
}
public Options parse(List<XValue> args)
throws UnknownOption, UnexpectedException, InvalidArgumentException {
return parse(args, false);
}
public Options parse(List<XValue> args, boolean stopOnUnknown)
throws UnexpectedException, InvalidArgumentException, UnknownOption {
mOptions = parseValues(mDefs, args,
mRemainingArgs = new ArrayList<XValue>(), stopOnUnknown);
return this;
}
// Parse but do not store into mOptions
public static List<OptionValue> parseValues(OptionDefs defs,
List<XValue> args,
List<XValue> remainingArgs, boolean stopOnUnknown) throws UnknownOption,
UnexpectedException, InvalidArgumentException {
List<OptionValue> options = new ArrayList<>();
Iterator<XValue> I = args.iterator();
while(I.hasNext()) {
XValue arg = I.next();
String sarg = (arg.isAtomic() ? arg.toString() : null);
if(sarg != null && (sarg.startsWith("-") || sarg.startsWith("+"))
&& !sarg.equals("--") && !Util.isInt(sarg, true)) {
String a = sarg.substring(1);
char flag = sarg.charAt(0);
OptionDef def = defs.getOptionDef(a);
if(def == null) {
if(stopOnUnknown) {
remainingArgs.add(arg);
break;
}
throw new UnknownOption("Unknown option: " + a);
}
if(flag == '+' && !def.isFlag())
throw new UnknownOption("Option : " + a
+ " cannot start with +");
boolean bRepeat = hasOpt(options, def);
if(bRepeat && !def.isMultiple())
throw new UnknownOption(
"Unexpected multiple use of option: " + arg);
OptionValue ov = new OptionValue(def, flag == '-');
if(def.isExpectsArg()) {
if(!I.hasNext())
throw new UnknownOption("Option has no args: " + arg);
ov.setValue(I.next());
}
options.add(ov);
}
else {
if(arg.isAtomic() && arg.equals("--")) {
arg = null;
options.add(new OptionValue());
}
if(arg != null)
remainingArgs.add(arg);
break;
}
}
while(I.hasNext())
remainingArgs.add(I.next());
return options;
}
public List<OptionValue> getOpts() {
return mOptions;
}
private static OptionValue getOpt(List<OptionValue> opts, OptionDef def) {
assert (def != null);
assert (opts != null);
for(OptionValue ov : opts) {
if(ov.option.equals(def))
return ov;
}
return null;
}
public OptionValue getOpt(OptionDef def) {
return getOpt(mOptions, def);
}
private static boolean hasOpt(List<OptionValue> opts, OptionDef def) {
return getOpt(opts, def) != null;
}
public boolean hasOpt(OptionDef def) {
return getOpt(mOptions, def) != null;
}
public OptionValue getOpt(String opt) {
for(OptionValue ov : mOptions) {
if(ov.getOptionDef().isOption(opt))
return ov;
}
return null;
}
public boolean hasOpt(String opt) {
return getOpt(opt) != null;
}
public boolean getOptFlag(String opt, boolean defValue) {
OptionValue value = getOpt(opt);
if(value == null)
return defValue;
else
return value.getFlag();
}
public String getOptString(String opt, String defValue) {
OptionValue value = getOpt(opt);
if(value != null)
return value.toStringValue();
else
return defValue;
}
public String getOptStringRequired(String opt)
throws InvalidArgumentException {
OptionValue value = getOpt(opt);
if(value != null)
return value.getValue().toString();
throw new InvalidArgumentException("Required option: -" + opt);
}
public boolean getOptBool(String opt, boolean defValue) {
OptionValue value = getOpt(opt);
if(value != null)
try {
return value.getValue().toBoolean();
} catch (Exception e) {
return false;
}
return defValue;
}
public List<XValue> getRemainingArgs() {
if(mRemainingArgs == null)
mRemainingArgs = new ArrayList<XValue>(0);
return mRemainingArgs;
}
public XValue getOptValue(String arg) {
OptionValue ov = getOpt(arg);
if(ov == null)
return null;
else if(ov.getOptionDef().isMultiple())
return XValue.newXValue(getOptValues(arg));
else
return ov.getValue();
}
public XValue getOptValueRequired(String arg)
throws InvalidArgumentException {
OptionValue ov = getOpt(arg);
if(ov == null)
throw new InvalidArgumentException("Required option: -" + arg);
if(ov.getOptionDef().isMultiple())
return XValue.newXValue(getOptValues(arg));
else
return ov.getValue();
}
public List<XValue> getOptValuesRequired(String arg)
throws InvalidArgumentException {
List<XValue> values = getOptValues(arg);
if(values == null || values.isEmpty())
throw new InvalidArgumentException("Required option: -" + arg);
return values;
}
public List<XValue> getOptValues(String arg) {
ArrayList<XValue> values = new ArrayList<>();
for(OptionValue ov : mOptions) {
if(ov.getOptionDef().isOption(arg))
values.add(ov.getValue());
}
return values.isEmpty() ? null : values;
}
public boolean hasRemainingArgs() {
return mRemainingArgs != null && !mRemainingArgs.isEmpty();
}
public double getOptDouble(String opt, double def) {
return Util.parseDouble(getOptString(opt, ""), def);
}
public int getOptInt(String opt, int def) {
return Util.parseInt(getOptString(opt, ""), def);
}
public long getOptLong(String opt, long l) {
return Util.parseLong(getOptString(opt, ""), l);
}
/**
* @return the defs
*/
public OptionDefs getOptDefs() {
return mDefs;
}
public static String joinOptions(String... sopts) {
if(sopts == null || sopts.length == 0)
return "";
StringBuilder sb = new StringBuilder();
for(String s : sopts) {
if(!Util.isBlank(s)) {
if(sb.length() > 0)
sb.append(",");
sb.append(s);
}
}
return sb.toString();
}
}
//
//
// Copyright (C) 2008-2014 David A. Lee.
//
// The contents of this file are subject to the "Simplified BSD License" (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.opensource.org/licenses/bsd-license.php
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is David A. Lee
//
// Portions created by (your name) are Copyright (C) (your legal entity). All
// Rights Reserved.
//
// Contributor(s): none.
//