src/main/java/com/sleekbyte/tailor/utils/CLIArgumentParser.java
package com.sleekbyte.tailor.utils;
import com.sleekbyte.tailor.common.ConstructLengths;
import com.sleekbyte.tailor.common.Messages;
import com.sleekbyte.tailor.common.Severity;
import com.sleekbyte.tailor.format.Format;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
/**
* Parse command line options and arguments.
*/
public final class CLIArgumentParser {
private Options options;
private CommandLine cmd;
/**
* Exception thrown when option parsing fails.
*/
public static class CLIArgumentParserException extends Exception {
public CLIArgumentParserException(String message) {
super(message);
}
}
/**
* Parse command line options/flags and arguments.
*/
public CommandLine parseCommandLine(String[] args) throws ParseException {
addOptions();
cmd = new DefaultParser().parse(this.options, args);
return cmd;
}
/**
* Check if "-h" or "--help" option was specified.
*/
public boolean shouldPrintHelp() {
return cmd != null && cmd.hasOption(Messages.HELP_SHORT_OPT);
}
/**
* Check if "-v" or "--version" option was specified.
*/
public boolean shouldPrintVersion() {
return cmd != null && cmd.hasOption(Messages.VERSION_SHORT_OPT);
}
/**
* Check if "--show-rules" option was specified.
*/
public boolean shouldPrintRules() {
return cmd != null && cmd.hasOption(Messages.SHOW_RULES_OPT);
}
/**
* Check if "--list-files" option was specified.
*/
public boolean shouldListFiles() {
return cmd != null && cmd.hasOption(Messages.LIST_FILES_OPT);
}
/**
* Check if "--purge" option was specified.
*/
public boolean shouldClearDFAs() {
return cmd != null && cmd.hasOption(Messages.PURGE_OPT);
}
/**
* Returns number specified with --purge option, or 0 if not specified.
*/
public int numberOfFilesBeforePurge() throws CLIArgumentParserException {
return getIntegerArgument(Messages.PURGE_OPT);
}
/**
* Print usage message with flag descriptions to STDOUT.
*/
public void printHelp() {
HelpFormatter helpFormatter = new HelpFormatter();
helpFormatter.setSyntaxPrefix(Messages.SYNTAX_PREFIX);
String newLine = helpFormatter.getNewLine();
String header = newLine + Messages.TAILOR_DESC + newLine + newLine + Messages.TAILOR_ARGS_INFO + newLine
+ newLine + Messages.OPTIONS_PREFIX;
helpFormatter.setLongOptSeparator("=");
helpFormatter.printHelp(Messages.HELP_WIDTH, Messages.CMD_LINE_SYNTAX, header, this.options, "");
}
/**
* Parse construct length flags into ConstructLengths object.
*/
public ConstructLengths parseConstructLengths() throws CLIArgumentParserException {
ConstructLengths constructLengths = new ConstructLengths();
constructLengths.setMaxClassLength(getIntegerArgument(Messages.MAX_CLASS_LENGTH_OPT));
constructLengths.setMaxClosureLength(getIntegerArgument(Messages.MAX_CLOSURE_LENGTH_OPT));
constructLengths.setMaxFileLength(getIntegerArgument(Messages.MAX_FILE_LENGTH_OPT));
constructLengths.setMaxFunctionLength(getIntegerArgument(Messages.MAX_FUNCTION_LENGTH_OPT));
constructLengths.setMaxLineLength(getIntegerArgument(Messages.MAX_LINE_LENGTH_LONG_OPT));
constructLengths.setMaxNameLength(getIntegerArgument(Messages.MAX_NAME_LENGTH_OPT));
constructLengths.setMaxStructLength(getIntegerArgument(Messages.MAX_STRUCT_LENGTH_OPT));
constructLengths.setMinNameLength(getIntegerArgument(Messages.MIN_NAME_LENGTH_OPT));
return constructLengths;
}
private void addOptions() {
options = new Options();
options.addOption(createNoArgOpt(Messages.HELP_SHORT_OPT, Messages.HELP_LONG_OPT, Messages.HELP_DESC));
options.addOption(createNoArgOpt(Messages.VERSION_SHORT_OPT, Messages.VERSION_LONG_OPT, Messages.VERSION_DESC));
String argName = "0-999";
options.addOption(createSingleArgOpt(Messages.MAX_CLASS_LENGTH_OPT, argName, Messages.MAX_CLASS_LENGTH_DESC));
options.addOption(createSingleArgOpt(Messages.MAX_CLOSURE_LENGTH_OPT, argName,
Messages.MAX_CLOSURE_LENGTH_DESC));
options.addOption(createSingleArgOpt(Messages.MAX_FILE_LENGTH_OPT, argName, Messages.MAX_FILE_LENGTH_DESC));
options.addOption(createSingleArgOpt(Messages.MAX_FUNCTION_LENGTH_OPT, argName,
Messages.MAX_FUNCTION_LENGTH_DESC));
options.addOption(createSingleArgOpt(
Messages.MAX_LINE_LENGTH_SHORT_OPT, Messages.MAX_LINE_LENGTH_LONG_OPT, argName,
Messages.MAX_LINE_LENGTH_DESC));
options.addOption(createSingleArgOpt(Messages.MAX_NAME_LENGTH_OPT, argName, Messages.MAX_NAME_LENGTH_DESC));
options.addOption(createSingleArgOpt(Messages.MAX_STRUCT_LENGTH_OPT, argName, Messages.MAX_STRUCT_LENGTH_DESC));
argName = "1-999";
options.addOption(createSingleArgOpt(Messages.MIN_NAME_LENGTH_OPT, argName, Messages.MIN_NAME_LENGTH_DESC));
options.addOption(createSingleArgOpt(Messages.PURGE_OPT, argName, Messages.PURGE_DESC));
argName = "error|warning (default)";
options.addOption(createSingleArgOpt(Messages.MAX_SEVERITY_OPT, argName, Messages.MAX_SEVERITY_DESC));
argName = "rule1,rule2,...";
options.addOption(createMultiArgOpt(Messages.ONLY_OPT, argName, Messages.ONLY_SPECIFIC_RULES_DESC));
options.addOption(createMultiArgOpt(Messages.EXCEPT_OPT, argName, Messages.EXCEPT_RULES_DESC));
argName = "path/to/project.xcodeproj";
options.addOption(createSingleArgOpt(Messages.XCODE_INTEGRATION_OPT, argName, Messages.XCODE_INTEGRATION_DESC));
options.addOption(createNoArgOpt(Messages.DEBUG_OPT, Messages.DEBUG_DESC));
options.addOption(createNoArgOpt(Messages.NO_COLOR_OPT, Messages.NO_COLOR_DESC));
options.addOption(createNoArgOpt(Messages.INVERT_COLOR_OPT, Messages.INVERT_COLOR_DESC));
options.addOption(createNoArgOpt(Messages.SHOW_RULES_OPT, Messages.SHOW_RULES_DESC));
argName = "path/to/.tailor.yml";
options.addOption(createSingleArgOpt(Messages.CONFIG_SHORT_OPT, Messages.CONFIG_LONG_OPT, argName,
Messages.CONFIG_FILE_DESC));
options.addOption(createNoArgOpt(Messages.LIST_FILES_OPT, Messages.LIST_FILES_DESC));
argName = Format.getFormats();
options.addOption(createSingleArgOpt(Messages.FORMAT_SHORT_OPT, Messages.FORMAT_LONG_OPT, argName,
Messages.FORMAT_DESC));
}
/**
* Create command line option with short name, long name, and no argument.
*
* @param shortOpt short version of option
* @param longOpt long version of option
* @param desc description of option
*/
private Option createNoArgOpt(String shortOpt, String longOpt, String desc) {
return Option.builder(shortOpt).longOpt(longOpt).desc(desc).build();
}
/**
* Create command line option with only long name and no argument.
*
* @param longOpt long version of option
* @param desc description of option
*/
private Option createNoArgOpt(String longOpt, String desc) {
return Option.builder().longOpt(longOpt).desc(desc).build();
}
/**
* Create command line option with short name, long name, and only one argument.
*
* @param shortOpt short version of option
* @param longOpt long version of option
* @param argName name of argument for help message
* @param desc description of option
*/
private Option createSingleArgOpt(String shortOpt, String longOpt, String argName, String desc) {
return Option.builder(shortOpt).longOpt(longOpt).hasArg().argName(argName).desc(desc).build();
}
/**
* Create command line option with only long name and only one argument.
*
* @param longOpt long version of option
* @param argName name of argument for help message
* @param desc description of option
*/
private Option createSingleArgOpt(String longOpt, String argName, String desc) {
return Option.builder().longOpt(longOpt).hasArg().argName(argName).desc(desc).build();
}
/**
* Create command line option with only long name and multiple arguments.
* Multiple arguments can be separated by comma or by space.
*
* @param longOpt long version of option
* @param argName name of argument for help message
* @param desc description of option
*/
private Option createMultiArgOpt(String longOpt, String argName, String desc) {
return Option.builder().longOpt(longOpt).hasArgs().argName(argName).valueSeparator(',').desc(desc).build();
}
private int getIntegerArgument(String opt) throws CLIArgumentParserException {
try {
return Integer.parseInt(this.cmd.getOptionValue(opt, Messages.DEFAULT_INT_ARG));
} catch (NumberFormatException e) {
throw new CLIArgumentParserException("Invalid value provided for integer argument " + opt + ".");
}
}
public Set<String> getOnlySpecificRules() {
return cmd.hasOption(Messages.ONLY_OPT) ? new HashSet<>(Arrays.asList(cmd.getOptionValues(Messages.ONLY_OPT)))
: new HashSet<>();
}
/**
* Get list of disabled rules.
*
* @return Excluded rules
*/
public Set<String> getExcludedRules() {
return cmd.hasOption(Messages.EXCEPT_OPT)
? new HashSet<>(Arrays.asList(cmd.getOptionValues(Messages.EXCEPT_OPT)))
: new HashSet<>();
}
/*
* Retrieve Xcode project path specified for --xcode.
*
* @return path of Xcode project
*/
public String getXcodeprojPath() {
return cmd != null ? cmd.getOptionValue(Messages.XCODE_INTEGRATION_OPT) : null;
}
/*
* Retrieve .tailor.yml config file path specified for --config.
*
* @return path of config file
*/
public Optional<String> getConfigFilePath() {
return cmd != null ? Optional.ofNullable(cmd.getOptionValue(Messages.CONFIG_LONG_OPT)) : Optional.empty();
}
/**
* Returns maximum severity configured by user or 'warning' if not specified.
*
* @return Maximum severity
* @throws CLIArgumentParserException if invalid value specified for --max-severity
*/
public Severity getMaxSeverity() throws CLIArgumentParserException {
try {
return Severity.parseSeverity(this.cmd.getOptionValue(Messages.MAX_SEVERITY_OPT, Messages.WARNING));
} catch (Severity.IllegalSeverityException ex) {
throw new CLIArgumentParserException(Messages.INVALID_OPTION_VALUE + Messages.MAX_SEVERITY_OPT + ".");
}
}
public boolean debugFlagSet() throws CLIArgumentParserException {
return cmd != null && cmd.hasOption(Messages.DEBUG_OPT);
}
public boolean shouldColorOutput() {
return cmd != null && !cmd.hasOption(Messages.NO_COLOR_OPT);
}
public boolean shouldInvertColorOutput() {
return cmd != null && cmd.hasOption(Messages.INVERT_COLOR_OPT);
}
public boolean formatOptionSet() {
return cmd != null && cmd.hasOption(Messages.FORMAT_LONG_OPT);
}
/**
* Returns format specified by user, defaults to Xcode format.
*
* @return Format type
*/
public Format getFormat() throws CLIArgumentParserException {
if (cmd != null) {
try {
return Format.parseFormat(cmd.getOptionValue(Messages.FORMAT_LONG_OPT, Format.XCODE.getName()));
} catch (Format.IllegalFormatException e) {
throw new CLIArgumentParserException(Messages.INVALID_OPTION_VALUE + Messages.FORMAT_LONG_OPT + "."
+ " Options are <" + Format.getFormats() + ">.");
}
}
return Format.XCODE;
}
}