package com.laytonsmith.PureUtilities;
import com.laytonsmith.PureUtilities.ArgumentParser.ArgumentParserResults;
import com.laytonsmith.PureUtilities.ArgumentParser.ResultUseException;
import com.laytonsmith.PureUtilities.ArgumentParser.ValidationException;
import com.laytonsmith.PureUtilities.Common.ArrayUtils;
import com.laytonsmith.PureUtilities.Common.StreamUtils;
import com.laytonsmith.PureUtilities.Common.StringUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* An ArgumentSuite is an ArgumentParser that supports "modes".
*
* A mode is a required parameter that causes a fully separate argument parser to
* be used to parse the remaining arguments. This allows for finer control over
* mutually exclusive parameters in both the documentation and the validation, as
* well as wider support for traditional use cases.
*
*/
public class ArgumentSuite {
private Map<String, ArgumentParser> suite;
private Map<String, String> aliases;
private String description;
public ArgumentSuite(){
suite = new LinkedHashMap<String, ArgumentParser>();
aliases = new LinkedHashMap<String, String>();
}
/**
* Adds a new mode. A mode name may contain dashes, which would look like normal
* argument flags, but would actually be a mode. This is useful especially for a
* --help command, which shows the ArgumentSuite's help.
* @param modeName The name of this mode. This may not contain spaces.
* @param mode The sub-ArgumentParser that will be used when in this mode.
* @throws IllegalArgumentException if the name of the mode contains spaces
*/
public ArgumentSuite addMode(String modeName, ArgumentParser mode){
validateModeName(modeName);
suite.put(modeName, mode);
return this;
}
/**
* Adds a mode alias. This is the recommended behavior instead of adding the
* same mode with a different name, because the built description is aware
* of the difference between an alias and the real mode. All the same rules
* apply to aliases that apply to mode names. The realModeName doesn't
* strictly need to exist yet.
* @param alias
* @param realModeName
* @return
*/
public ArgumentSuite addModeAlias(String alias, String realModeName){
validateModeName(alias);
aliases.put(alias, realModeName);
return this;
}
private void validateModeName(String modeName){
if(modeName.contains(" ")){
throw new IllegalArgumentException("The mode name may not contain a space.");
}
}
/**
* Adds a description, which is used in {@see #getBuiltDescription}
* @param description
* @return
*/
public ArgumentSuite addDescription(String description){
this.description = description;
return this;
}
/**
* Returns a mode that was previously registered.
* @param name The name of the mode to get
* @return The mode registered under the provided name
* @throws IllegalArgumentException if the mode is not registered
*/
public ArgumentParser getMode(String name){
if(suite.containsKey(name)){
return suite.get(name);
} else {
throw new IllegalArgumentException("No mode by the name \"" + name + "\" has been registered.");
}
}
/**
* Selects the appropriate mode, and calls match on that ArgumentParser.
* @param args The pre-parsed arguments
* @param defaultMode The default mode, which will be used only if no arguments were passed in.
* @return
* @throws ResultUseException if the mode cannot be found, or if the sub-ArgumentParser
* throws an exception.
*/
public ArgumentSuiteResults match(String [] args, String defaultMode) throws ResultUseException, ValidationException {
String [] nonModeArgs = new String[0];
String mode;
if(args.length > 1){
mode = args[0];
nonModeArgs = ArrayUtils.cast(ArrayUtils.slice(args, 1, args.length - 1), String[].class);
} else if(args.length == 1){
mode = args[0];
} else {
//0 argsm, use the defaultMode.
mode = defaultMode;
}
if(aliases.containsKey(mode)){
mode = aliases.get(mode);
}
if(suite.containsKey(mode)){
return new ArgumentSuiteResults(mode, suite.get(mode), suite.get(mode).match(nonModeArgs));
} else {
throw new ResultUseException("Mode " + mode + " was not found.");
}
}
/**
* Selects the appropriate mode, and calls match on that ArgumentParser.
* @param args The unparsed arguments
* @param defaultMode The default mode, which will be used only if no arguments were passed in.
* @return
* @throws ResultUseException if the mode cannot be found, or if the sub-ArgumentParser
* throws an exception.
*/
public ArgumentSuiteResults match(String args, String defaultMode) throws ResultUseException, ValidationException {
//We're going to use ArgumentParser's parse method to get a string list, then
//pass that to the other match
return match(ArgumentParser.lex(args).toArray(new String[]{}), defaultMode);
}
/**
* Returns a built description of this ArgumentSuite, which would be appropriate
* to display if no arguments are passed in (or the mode name is help, -help, --help, etc)
* @return
*/
public String getBuiltDescription(){
StringBuilder b = new StringBuilder();
if(description != null){
b.append(description).append("\n\n");
}
b.append("Modes: (a mode must be the first argument) \n");
for(String mode : suite.keySet()){
b.append("\t").append(TermColors.BOLD).append(mode);
if(aliases.containsValue(mode)){
List<String> keys = new ArrayList<>();
for(String alias : aliases.keySet()){
if(aliases.get(alias).equals(mode)){
keys.add(alias);
}
}
b.append(TermColors.RESET).append(" (Alias");
if(keys.size() != 1){
b.append("es");
}
b.append(": ").append(StringUtils.Join(keys, ", ")).append(")");
}
b.append(TermColors.RESET).append(": ").append(suite.get(mode).getDescription()).append("\n");
}
return b.toString();
}
/**
* A convenience method to get the real mode name registered for this
* alias, or null if no such alias exists. If the alias is actually a mode,
* it is simply returned. Useful for perhaps a help mode, to resolve the actual
* mode named. If {@code alias} is null, null is returned.
* @param alias
* @return
*/
public String getModeFromAlias(String alias){
if(alias == null){
return null;
}
if(suite.containsKey(alias)){
return alias;
} else if(aliases.containsKey(alias)){
return aliases.get(alias);
} else {
return null;
}
}
/**
* A convenience method to get the underlying ArgumentParser
* based on the mode name given. Aliases will not suffice, but you
* may call getModeFromAlias to resolve the mode name first. Null
* is returned if no mode exists with that name. Useful for perhaps
* a help mode, to generically display a mode's help. If {@see mode}
* is null, null is returned.
* @param mode
* @return
*/
public ArgumentParser getModeFromName(String mode){
if(mode == null){
return null;
}
if(suite.containsKey(mode)){
return suite.get(mode);
} else {
return null;
}
}
public static class ArgumentSuiteResults{
private ArgumentParser mode;
private ArgumentParserResults results;
private String modeName;
private ArgumentSuiteResults(String modeName, ArgumentParser mode, ArgumentParserResults results){
this.modeName = modeName;
this.mode = mode;
this.results = results;
}
/**
* Returns the name of the mode that was selected. (Not the alias)
* @return
*/
public String getModeName(){
return modeName;
}
/**
* The ArgumentParser for the given mode. This will be a reference
* to the mode passed in, so you can do == on it.
* @return
*/
public ArgumentParser getMode(){
return mode;
}
/**
* The ArgumentParserResults for the ArgumentParser mode
* @return
*/
public ArgumentParserResults getResults(){
return results;
}
}
public static void main(String [] args){
ArgumentSuite suite = new ArgumentSuite();
ArgumentParser mode1 = ArgumentParser.GetParser();
ArgumentParser mode2 = ArgumentParser.GetParser();
mode1.addArgument("arg1", ArgumentParser.Type.STRING, "Argument 1", "arg1", false);
mode1.addArgument("arg2", ArgumentParser.Type.STRING, "Argument 2", "arg2", false);
mode1.addDescription("A description of Mode 1");
mode2.addArgument("arg1", ArgumentParser.Type.STRING, "Argument 1", "arg1", false);
mode2.addArgument("arg2", ArgumentParser.Type.STRING, "Argument 2", "arg2", false);
mode2.addDescription("A description of Mode 2");
suite.addModeAlias("mode-2", "mode2");
suite.addModeAlias("mode--2", "mode2");
ArgumentParser help = ArgumentParser.GetParser();
help.addDescription("Displays the help for a mode, then exits.")
.addArgument("The mode name", "mode name", true);
suite.addDescription("A description of the suite")
.addMode("help", help)
.addMode("mode1", mode1)
.addMode("mode2", mode2);
try {
ArgumentSuiteResults results = suite.match("wat", "help");
ArgumentParser mode = results.getMode();
ArgumentParserResults modeResults = results.getResults();
if(mode == help){
String modeHelp = modeResults.getStringArgument();
ArgumentParser selectedMode = suite.getModeFromName(suite.getModeFromAlias(modeHelp));
if(selectedMode != null){
StreamUtils.GetSystemOut().println(selectedMode.getBuiltDescription());
System.exit(0);
} else {
showHelp(suite);
}
} else if(mode == mode1 || mode == mode2){
String argument;
if((argument = modeResults.getStringArgument("arg1")) != null){
StreamUtils.GetSystemOut().println("You selected " + argument + " for arg1");
}
if((argument = modeResults.getStringArgument("arg2")) != null){
StreamUtils.GetSystemOut().println("You selected " + argument + " for arg2");
}
System.exit(0);
}
} catch (ResultUseException ex) {
showHelp(suite);
} catch (ValidationException ex) {
showHelp(suite);
}
}
private static void showHelp(ArgumentSuite suite){
StreamUtils.GetSystemOut().println(suite.getBuiltDescription());
System.exit(1);
}
}