Martin Peřina has uploaded a new change for review. Change subject: core: Adds cli parser supporting short and long args ......................................................................
core: Adds cli parser supporting short and long args Adds command line arguments parser that supports parsing short (-h) and long arguments (--help). Change-Id: Id7fe3bf761bd26c86d684ad1dc85751d4492239b Bug-Url: https://bugzilla.redhat.com/904029 Signed-off-by: Martin Perina <mper...@redhat.com> --- A backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/Argument.java A backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ArgumentBuilder.java A backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ExtendedCliParser.java A backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ArgumentBuilderTest.java A backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ExtendedCliParserTest.java 5 files changed, 770 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/95/23995/1 diff --git a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/Argument.java b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/Argument.java new file mode 100644 index 0000000..4d15785 --- /dev/null +++ b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/Argument.java @@ -0,0 +1,37 @@ +package org.ovirt.engine.core.utils.cli; + +/** + * Represents argument specification inside {@link ExtendedCliParser} + */ +class Argument { + private final String shortName; + + private final String longName; + + private final String destination; + + private final boolean valueRequired; + + Argument(String shortName, String longName, String destination, boolean valueRequired) { + this.shortName = shortName; + this.longName = longName; + this.destination = destination; + this.valueRequired = valueRequired; + } + + public String getShortName() { + return shortName; + } + + public String getLongName() { + return longName; + } + + public String getDestination() { + return destination; + } + + public boolean isValueRequied() { + return valueRequired; + } +} diff --git a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ArgumentBuilder.java b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ArgumentBuilder.java new file mode 100644 index 0000000..1215973 --- /dev/null +++ b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ArgumentBuilder.java @@ -0,0 +1,86 @@ +package org.ovirt.engine.core.utils.cli; + +import org.apache.commons.lang.StringUtils; + +/** + * Builder to create arguments for {@link ExtendedCliParser} + */ +public class ArgumentBuilder { + private String shortName; + private String longName; + private String destination; + private boolean valueRequired; + + public ArgumentBuilder() { + valueRequired = false; + } + + /** + * Sets short name of argument + * + * @param shortName + * short name of argument + * @returns builder instance + */ + public ArgumentBuilder shortName(String shortName) { + this.shortName = shortName; + return this; + } + + /** + * Sets long name of argument + * + * @param longName + * long name of argument + * @returns builder instance + */ + public ArgumentBuilder longName(String longName) { + this.longName = longName; + return this; + } + + /** + * Sets destination of argument + * + * @param destination + * destination of argument + * @returns builder instance + */ + public ArgumentBuilder destination(String destination) { + this.destination = destination; + return this; + } + + /** + * Sets indicator if value is required + * + * @param valueRequired + * indicator if value is required + * @returns builder instance + */ + public ArgumentBuilder valueRequied(boolean valueRequired) { + this.valueRequired = valueRequired; + return this; + } + + /** + * Builds argument. If destination is empty, it's set long name (or short name if long name is also empty). By + * default argument value is not required + */ + public Argument build() { + if (StringUtils.isBlank(shortName) + && StringUtils.isBlank(longName)) { + throw new IllegalArgumentException("Argument must have non-empty short or long name!"); + } + + if (StringUtils.isBlank(destination)) { + if (StringUtils.isNotBlank(longName)) { + destination = longName; + } else { + destination = shortName; + } + } + + return new Argument(shortName, longName, destination, valueRequired); + } +} diff --git a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ExtendedCliParser.java b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ExtendedCliParser.java new file mode 100644 index 0000000..79e172f --- /dev/null +++ b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/cli/ExtendedCliParser.java @@ -0,0 +1,234 @@ +package org.ovirt.engine.core.utils.cli; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; + +/** + * Implements argument parser, that supports parsing short and long arguments + */ +public class ExtendedCliParser { + /** + * Short argument prefix + */ + static final String PREFIX_SHORT = "-"; + + /** + * Long argument prefix + */ + static final String PREFIX_LONG = "--"; + + /** + * Long argument value separator + */ + static final String VALUE_SEP_LONG = "="; + + /** + * Map of argument names and their instances + */ + Map<String, Argument> argMap; + + /** + * Initializes set of arguments + */ + public ExtendedCliParser() { + argMap = new HashMap<>(); + } + + /** + * Adds argument to parser + * + * @param arg + * specified argument + */ + public void addArg(Argument arg) { + if (StringUtils.isNotBlank(arg.getShortName()) + && argMap.containsKey(arg.getShortName())) { + throw new IllegalArgumentException( + String.format("Argument with short name '%s' already exists!", arg.getShortName())); + } + + if (StringUtils.isNotBlank(arg.getLongName()) + && argMap.containsKey(arg.getLongName())) { + throw new IllegalArgumentException( + String.format("Argument with long name '%s' already exists!", arg.getLongName())); + } + + if (arg.getShortName() != null) { + argMap.put(arg.getShortName(), arg); + } + if (arg.getLongName() != null) { + argMap.put(arg.getLongName(), arg); + } + } + + /** + * Parses arguments in {@code args} and returns map with argument names and their values + * + * @param args + * array of arguments (usually command line arguments splitted by space) + * @throws IllegalArgumentException + * if {@code args} is {@code null} or arguments has invalid format + * @return map with argument names and their values + */ + public Map<String, String> parse(String[] args) { + if (args == null) { + throw new IllegalArgumentException("Argumens array cannot be null"); + } + + return parse(args, 0, args.length); + } + + /** + * Parses arguments in {@code args} between specified array indexes and returns map with argument names and their + * values + * + * @param args + * array of arguments (usually command line arguments splitted by space) + * @param from + * starting index + * @param to + * end index + * @throws IllegalArgumentException + * if {@code args} is {@code null} or arguments has invalid format + * @throws ArrayIndexOutOfBoundsException + * if indexes {@code from} or {@code to} are invalid + * @return map with argument names and their values + */ + public Map<String, String> parse(String[] args, int from, int to) { + if (args == null) { + throw new IllegalArgumentException("Argumens array cannot be null"); + } + + Map<String, String> result = new HashMap<>(); + // parse arguments + for (int i = from; i < to;) { + if (args[i] == null) { + // just to be sure + i++; + continue; + } + + String name = null; + String value = null; + + if (isLongArg(args[i])) { + // parse long argument + name = parseLongArgName(args[i]); + value = parseLongArgValue(args[i]); + } else if (isShortArg(args[i])) { + // parse short argument + name = args[i]; + if (i + 1 < args.length) { + if (!isShortArg(args[i + 1]) && !isLongArg(args[i + 1])) { + // argument has a value + value = args[i + 1]; + i++; + } + } + } else { + // invalid argument format + throw new IllegalArgumentException( + String.format("Invalid argument format '%s'!", args[i])); + } + + Argument arg = argMap.get(name); + if (arg == null) { + throw new IllegalArgumentException( + String.format("Unknown argument '%s'!", name)); + } + if (arg.isValueRequied() && StringUtils.isBlank(value)) { + throw new IllegalArgumentException( + String.format("Argument '%s' requires value!", name)); + } + result.put(arg.getDestination(), value); + i++; + } + return result; + } + + /** + * Tests if specified argument contains valid short argument name + * + * @param arg + * argument + * @return {@code true} if string contains valid short argument, otherwise {@code false} + */ + private boolean isShortArg(String arg) { + boolean result = true; + if (arg == null || arg.length() != 2) { + result = false; + } else if (!arg.startsWith(PREFIX_SHORT)) { + result = false; + } else if (!Character.isLetterOrDigit(arg.charAt(1))) { + result = false; + } + return result; + } + + /** + * Tests if specified argument contains valid long argument name. + * + * @param arg + * argument + * @return {@code true} if string contains valid long argument, otherwise {@code false} + */ + private boolean isLongArg(String arg) { + boolean result = true; + if (arg == null || arg.length() < 3) { + result = false; + } else if (!arg.startsWith(PREFIX_LONG)) { + result = false; + } else { + for (int i = 2; i < arg.length(); i++) { + if (i > 2 && i + 2 < arg.length() && VALUE_SEP_LONG.equals(arg.substring(i, i + 1))) { + // argument contains value + break; + } + char c = arg.charAt(i); + if (!Character.isLetterOrDigit(c) && c != '-') { + result = false; + break; + } + } + } + return result; + } + + /** + * Parses name of long argument from specified string. Method DOES NOT validate argument, it just creates + * substring from start to value separator (or to the end if separator is not present) and removes prefix. + * + * @param str + * specified string + * @returns long argument name + */ + private String parseLongArgName(String str) { + int idx = str.indexOf(VALUE_SEP_LONG); + if (idx == -1) { + // argument does not contain value + return str; + } else { + return str.substring(0, idx); + } + } + + /** + * Parses value of long argument from specified string. Method DOES NOT validate argument, it just creates + * substring value separator to the end or returns {@code null} if value separator is not present + * + * @param str + * specified string + * @returns long argument value or {@code null} + */ + private String parseLongArgValue(String str) { + int idx = str.indexOf(VALUE_SEP_LONG); + if (idx == -1) { + // arg does not contain value + return null; + } else { + return str.substring(idx + 1); + } + } +} diff --git a/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ArgumentBuilderTest.java b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ArgumentBuilderTest.java new file mode 100644 index 0000000..f2d72a3 --- /dev/null +++ b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ArgumentBuilderTest.java @@ -0,0 +1,142 @@ +package org.ovirt.engine.core.utils.cli; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for argument creation used by {@link ExtendedCliParser} + */ +@RunWith(JUnit4.class) +public class ArgumentBuilderTest { + /** + * Tests creating argument without value required specified + */ + @Test + public void createValidArgWithoutRequiredSpecified() { + String shortName = "-h"; + String longName = "--help"; + String destination = "dest"; + + Argument arg = new ArgumentBuilder() + .shortName(shortName) + .longName(longName) + .destination(destination) + .build(); + + assertEquals(shortName, arg.getShortName()); + assertEquals(longName, arg.getLongName()); + assertEquals(destination, arg.getDestination()); + assertFalse(arg.isValueRequied()); + } + + /** + * Tests creating argument with value required specified + */ + @Test + public void createValidArgWithRequiredSpecified() { + String shortName = "-h"; + String longName = "--help"; + String destination = "dest"; + + Argument arg = new ArgumentBuilder() + .shortName(shortName) + .longName(longName) + .destination(destination) + .valueRequied(true) + .build(); + + assertEquals(shortName, arg.getShortName()); + assertEquals(longName, arg.getLongName()); + assertEquals(destination, arg.getDestination()); + assertTrue(arg.isValueRequied()); + } + + /** + * Tests creating argument with only short name + */ + @Test + public void createValidArgWithOnlyShortName() { + String shortName = "-h"; + + Argument arg = new ArgumentBuilder() + .shortName(shortName) + .build(); + + assertEquals(shortName, arg.getShortName()); + } + + /** + * Tests creating argument with only long name + */ + @Test + public void createValidArgWithOnlyLongName() { + String longName = "--help"; + + Argument arg = new ArgumentBuilder() + .longName(longName) + .build(); + + assertEquals(longName, arg.getLongName()); + } + + /** + * Tests creating argument without short and long name + */ + @Test(expected = IllegalArgumentException.class) + public void createArgWithoutShortAndLongNames() { + new ArgumentBuilder().destination("dest").build(); + } + + /** + * Tests destination defined by destination option + */ + @Test + public void parseArgsWithDestDefinedByDest() { + String shortName = "-h"; + String longName = "--help"; + String destination = "dest"; + + Argument arg = new ArgumentBuilder() + .shortName(shortName) + .longName(longName) + .destination(destination) + .build(); + + assertEquals(destination, arg.getDestination()); + } + + /** + * Tests destination defined by long name option + */ + @Test + public void parseArgsWithDestDefinedByLongName() { + String shortName = "-h"; + String longName = "--help"; + + Argument arg = new ArgumentBuilder() + .shortName(shortName) + .longName(longName) + .build(); + + assertEquals(longName, arg.getDestination()); + } + + /** + * Tests destination defined by short name option + */ + @Test + public void parseArgsWithDestDefinedByShortName() { + String shortName = "-h"; + + Argument arg = new ArgumentBuilder() + .shortName(shortName) + .build(); + + assertEquals(shortName, arg.getDestination()); + } +} diff --git a/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ExtendedCliParserTest.java b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ExtendedCliParserTest.java new file mode 100644 index 0000000..1a23912 --- /dev/null +++ b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/cli/ExtendedCliParserTest.java @@ -0,0 +1,271 @@ +package org.ovirt.engine.core.utils.cli; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.ovirt.engine.core.utils.cli.ExtendedCliParser.VALUE_SEP_LONG; + +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Parsing short and long arguments with {@code ExtendedCliParser} tests + */ +@RunWith(JUnit4.class) +public class ExtendedCliParserTest { + /** + * Tests parsing empty arguments + */ + @Test + public void emptyArgs() { + Map<String, String> results; + ExtendedCliParser parser = new ExtendedCliParser(); + + final String[] args = {}; + + results = parser.parse(args); + + assertNotNull(results); + assertTrue(results.isEmpty()); + } + + /** + * Tests parsing short argument without value + */ + @Test + public void shortArgWithoutValue() { + Map<String, String> results; + ExtendedCliParser parser = new ExtendedCliParser(); + String argName = "-h"; + + final String[] args = { argName }; + + parser.addArg(new ArgumentBuilder().shortName(argName).build()); + results = parser.parse(args); + + assertNotNull(results); + assertFalse(results.isEmpty()); + + assertTrue(results.containsKey(argName)); + assertNull(results.get(argName)); + } + + /** + * Tests parsing short argument with value + */ + @Test + public void shortArgWithValue() { + Map<String, String> results; + ExtendedCliParser parser = new ExtendedCliParser(); + String argName = "-f"; + String argValue = "/tmp/xxx.txt"; + + final String[] args = { argName, argValue }; + + parser.addArg(new ArgumentBuilder().shortName(argName).build()); + results = parser.parse(args); + + assertNotNull(results); + assertFalse(results.isEmpty()); + + assertTrue(results.containsKey(argName)); + assertEquals(argValue, results.get(argName)); + } + + /** + * Tests parsing long argument without value + */ + @Test + public void longArgWithoutValue() { + Map<String, String> results; + ExtendedCliParser parser = new ExtendedCliParser(); + String argName = "--help"; + + final String[] args = { argName }; + + parser.addArg(new ArgumentBuilder().longName(argName).build()); + results = parser.parse(args); + + assertNotNull(results); + assertFalse(results.isEmpty()); + + assertTrue(results.containsKey(argName)); + assertNull(results.get(argName)); + } + + /** + * Tests parsing long argument with value + */ + @Test + public void longArgWithValue() { + Map<String, String> results; + ExtendedCliParser parser = new ExtendedCliParser(); + String argName = "--file"; + String argValue = "/tmp/xxx.txt"; + + final String[] args = { argName + VALUE_SEP_LONG + argValue }; + + parser.addArg(new ArgumentBuilder().longName(argName).build()); + results = parser.parse(args); + + assertNotNull(results); + assertFalse(results.isEmpty()); + + assertTrue(results.containsKey(argName)); + assertEquals(argValue, results.get(argName)); + } + + /** + * Tests parsing short argument with value delimited by long argument value separator + */ + @Test(expected = IllegalArgumentException.class) + public void shortArgWithLongValueSep() { + ExtendedCliParser parser = new ExtendedCliParser(); + String argName = "-f"; + String argValue = "/tmp/xxx.txt"; + + final String[] args = { argName + VALUE_SEP_LONG + argValue }; + + parser.addArg(new ArgumentBuilder().shortName(argName).build()); + parser.parse(args); + } + + /** + * Tests parsing long argument with value delimited by short argument value separator + */ + @Test(expected = IllegalArgumentException.class) + public void longArgWithShortValueSep() { + ExtendedCliParser parser = new ExtendedCliParser(); + String argName = "--file"; + String argValue = "/tmp/xxx.txt"; + + final String[] args = { argName, argValue }; + + parser.addArg(new ArgumentBuilder().longName(argName).build()); + parser.parse(args); + } + + /** + * Tries to parse argument with specified invalid name + * + * @param name + * invalid argument name + */ + protected void invalidArgName(String name) { + try { + ExtendedCliParser parser = new ExtendedCliParser(); + parser.parse(new String[] {name}); + fail("IllegalArgumentException expected for '" + name + "' argument!"); + } catch (IllegalArgumentException ex) { + } + } + + /** + * Tests arguments with invalid names + */ + @Test + public void invalidArgName() { + invalidArgName("\t "); + invalidArgName("A"); + invalidArgName("- "); + invalidArgName("-test"); + invalidArgName("--"); + invalidArgName("--?"); + invalidArgName("--force="); + } + + /** + * Tests parsing argument only in selected range + */ + @Test + public void parseArgsInRange() { + Map<String, String> results; + ExtendedCliParser parser = new ExtendedCliParser(); + String argName1 = "--help"; + String argName2 = "-f"; + String argName3 = "--output"; + String argValue3 = "/dev/null"; + String argName4 = "-u"; + String argValue4 = "root"; + + final String[] args = { + argName1, + argName2, + argName3 + VALUE_SEP_LONG + argValue3, + argName4, + argValue4 + }; + + parser.addArg(new ArgumentBuilder().shortName(argName2).build()); + parser.addArg(new ArgumentBuilder().longName(argName3).build()); + results = parser.parse(args, 1, 3); + + assertNotNull(results); + assertFalse(results.isEmpty()); + + assertTrue(results.containsKey(argName2)); + + assertTrue(results.containsKey(argName3)); + assertEquals(argValue3, results.get(argName3)); + } + + /** + * Tests parsing argument with missing required value + */ + @Test(expected = IllegalArgumentException.class) + public void parseArgWithMissingRequiredValue() { + ExtendedCliParser parser = new ExtendedCliParser(); + String argName = "-f"; + + final String[] args = { argName }; + + parser.addArg(new ArgumentBuilder().shortName(argName).valueRequied(true).build()); + parser.parse(args); + } + + /** + * Tests adding two arguments with the same short name + */ + @Test(expected = IllegalArgumentException.class) + public void twoArgsWithSameShortName() { + ExtendedCliParser parser = new ExtendedCliParser(); + + parser.addArg(new ArgumentBuilder() + .shortName("-a") + .longName("--aa") + .valueRequied(true) + .build()); + + parser.addArg(new ArgumentBuilder() + .shortName("-a") + .longName("--bb") + .valueRequied(true) + .build()); + } + + /** + * Tests adding two arguments with the same long name + */ + @Test(expected = IllegalArgumentException.class) + public void twoArgsWithSameLongName() { + ExtendedCliParser parser = new ExtendedCliParser(); + + parser.addArg(new ArgumentBuilder() + .shortName("-a") + .longName("--aa") + .valueRequied(true) + .build()); + + parser.addArg(new ArgumentBuilder() + .shortName("-b") + .longName("--aa") + .valueRequied(true) + .build()); + } +} -- To view, visit http://gerrit.ovirt.org/23995 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Id7fe3bf761bd26c86d684ad1dc85751d4492239b Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: ovirt-engine-3.4 Gerrit-Owner: Martin Peřina <mper...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches