static CommandSpec extractCommandSpec(Object command, IFactory factory, boolean annotationsAreMandatory) { Class<?> cls = command.getClass(); Tracer t = new Tracer(); t.debug("Creating CommandSpec for object of class %s with factory %s%n", cls.getName(), factory.getClass().getName()); if (command instanceof CommandSpec) { return (CommandSpec) command; } Object instance = command; commandClassName = cls.getName(); try { t.debug("Getting a %s instance from the factory%n", cls.getName()); instance = DefaultFactory.create(factory, cls); cls = instance.getClass(); commandClassName = cls.getName(); t.debug("Factory returned a %s instance%n", commandClassName); } catch (InitializationException ex) { if (cls.isInterface()) { t.debug("%s. Creating Proxy for interface %s%n", ex.getCause(), cls.getName()); instance = Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new PicocliInvocationHandler()); } else { t.debug("Using method %s as command %n", method); commandClassName = method.toString(); Command cmd = method.getAnnotation(Command.class);
if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse(copy(args)));} tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n"); endOfOptions = true; processRemainderAsPositionalParameters(required, initialized, args); if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, subcommand.commandSpec.toString());} subcommand.interpreter.parse(parsedCommands, args, originalArgs, nowProcessing); parseResult.subcommand(subcommand.interpreter.parseResult.build()); tracer.warn("Both '%s' and '%s' are valid option names in %s. Using '%s'...%n", arg, key, getCommandName(), arg); } else if (commandSpec.optionsMap().containsKey(key)) { paramAttachedToOption = true; args.push(optionParam); arg = key; if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);} } else { if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);} if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);} if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);} processClusteredShortOptions(required, initialized, arg, args); if (tracer.isDebug()) {tracer.debug("Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n", arg);} if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); continue; } // #149 if (tracer.isDebug()) {tracer.debug("No option named '%s' found. Processing remainder as positional parameters%n", arg);} processPositionalParameter(required, initialized, args);
if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse(copy(args)));} tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n"); endOfOptions = true; processRemainderAsPositionalParameters(required, initialized, args); if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, subcommand.commandSpec.toString());} subcommand.interpreter.parse(parsedCommands, args, originalArgs, nowProcessing); parseResult.subcommand(subcommand.interpreter.parseResult.build()); tracer.warn("Both '%s' and '%s' are valid option names in %s. Using '%s'...%n", arg, key, getCommandName(), arg); } else if (commandSpec.optionsMap().containsKey(key)) { paramAttachedToOption = true; args.push(optionParam); arg = key; if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);} } else { if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);} if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);} if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);} processClusteredShortOptions(required, initialized, arg, args); if (tracer.isDebug()) {tracer.debug("Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n", arg);} if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); continue; } // #149 if (tracer.isDebug()) {tracer.debug("No option named '%s' found. Processing remainder as positional parameters%n", arg);} processPositionalParameter(required, initialized, args);
Range arity = argSpec.arity(); String argDescription = "option " + prefix + cluster.charAt(0); if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: %s, arity=%s%n", prefix, cluster.charAt(0), arg, argSpec, arity);} required.remove(argSpec); if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);} args.push(paramAttachedToOption ? prefix + cluster : cluster); if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options if (tracer.isDebug()) {tracer.debug("Could not match any short options in %s, deciding whether to treat as unmatched option or positional parameter...%n", arg);} if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); return; } // #149 processPositionalParameter(required, initialized, args); if (tracer.isDebug()) {tracer.debug("No option found for %s in %s%n", cluster, arg);} handleUnmatchedArgument(args); } else { args.push(cluster); if (tracer.isDebug()) {tracer.debug("%s is not an option parameter for %s%n", cluster, arg);} processPositionalParameter(required, initialized, args);
static CommandSpec extractCommandSpec(Object command, IFactory factory, boolean annotationsAreMandatory) { Class<?> cls = command.getClass(); Tracer t = new Tracer(); t.debug("Creating CommandSpec for object of class %s with factory %s%n", cls.getName(), factory.getClass().getName()); if (command instanceof CommandSpec) { return (CommandSpec) command; } Object instance = command; commandClassName = cls.getName(); try { t.debug("Getting a %s instance from the factory%n", cls.getName()); instance = DefaultFactory.create(factory, cls); cls = instance.getClass(); commandClassName = cls.getName(); t.debug("Factory returned a %s instance%n", commandClassName); } catch (InitializationException ex) { if (cls.isInterface()) { t.debug("%s. Creating Proxy for interface %s%n", ex.getCause(), cls.getName()); instance = Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new PicocliInvocationHandler()); } else { t.debug("Using method %s as command %n", method); commandClassName = method.toString(); hasCommandAnnotation |= updateCommandAttributes(method, result, factory);
private void parse(List<CommandLine> parsedCommands, Stack<String> argumentStack, String[] originalArgs, List<Object> nowProcessing) { if (tracer.isDebug()) {tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d subcommands.%n", commandSpec.toString(), new HashSet<ArgSpec>(commandSpec.optionsMap().values()).size(), commandSpec.positionalParameters().size(), commandSpec.requiredArgs().size(), commandSpec if (tracer.isInfo()) { tracer.info("Unmatched arguments: %s%n", parseResult.unmatched); }
new Tracer().warn("Unbalanced quotes in [%s] for %s (value=%s)%n", temp, this, value); quotedValues.add(temp.toString()); temp.setLength(0); new Tracer().warn("Unable to respect quotes while splitting value %s for %s (unprocessed remainder: %s)%n", value, this, quotedValues); return value.split(splitRegex(), limit);
private void processPositionalParameter(Collection<ArgSpec> required, Set<ArgSpec> initialized, Stack<String> args) throws Exception { if (tracer.isDebug()) {tracer.debug("Processing next arg as a positional parameter at index=%d. Remainder=%s%n", position, reverse(copy(args)));} if (config().stopAtPositional()) { if (!endOfOptions && tracer.isDebug()) {tracer.debug("Parser was configured with stopAtPositional=true, treating remaining arguments as positional parameters.%n");} endOfOptions = true; if (tracer.isDebug()) {tracer.debug("Position %d is in index range %s. Trying to assign args to %s, arity=%s%n", position, indexRange, positionalParam, arity);} if (!assertNoMissingParameters(positionalParam, arity, argsCopy)) { break; } // #389 collectErrors parsing int originalSize = argsCopy.size(); if (tracer.isDebug()) {tracer.debug("Consumed %d arguments and %d interactive values, moving position to index %d.%n", argsConsumed, interactiveConsumed, position);} if (argsConsumed == 0 && interactiveConsumed == 0 && !args.isEmpty()) { handleUnmatchedArgument(args);
String name = argSpec.isOption() ? ((OptionSpec) argSpec).longestName() : "position " + position; String prompt = String.format("Enter value for %s (%s): ", name, str(argSpec.renderedDescription(), 0)); if (tracer.isDebug()) {tracer.debug("Reading value for %s from console...%n", name);} char[] value = readPassword(prompt); if (tracer.isDebug()) {tracer.debug("User entered '%s' for %s.%n", value, name);} workingStack.push(new String(value));
/** Ensures all attributes of this {@code CommandSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */ void validate() { Collections.sort(positionalParameters, new PositionalParametersSorter()); validatePositionalParameters(positionalParameters); List<String> wrongUsageHelpAttr = new ArrayList<String>(); List<String> wrongVersionHelpAttr = new ArrayList<String>(); List<String> usageHelpAttr = new ArrayList<String>(); List<String> versionHelpAttr = new ArrayList<String>(); for (OptionSpec option : options()) { if (option.usageHelp()) { usageHelpAttr.add(option.longestName()); if (!isBoolean(option.type())) { wrongUsageHelpAttr.add(option.longestName()); } } if (option.versionHelp()) { versionHelpAttr.add(option.longestName()); if (!isBoolean(option.type())) { wrongVersionHelpAttr.add(option.longestName()); } } } String wrongType = "Non-boolean options like %s should not be marked as '%s=true'. Usually a command has one %s boolean flag that triggers display of the %s. Alternatively, consider using @Command(mixinStandardHelpOptions = true) on your command instead."; String multiple = "Multiple options %s are marked as '%s=true'. Usually a command has only one %s option that triggers display of the %s. Alternatively, consider using @Command(mixinStandardHelpOptions = true) on your command instead.%n"; if (!wrongUsageHelpAttr.isEmpty()) { throw new InitializationException(String.format(wrongType, wrongUsageHelpAttr, "usageHelp", "--help", "usage help message")); } if (!wrongVersionHelpAttr.isEmpty()) { throw new InitializationException(String.format(wrongType, wrongVersionHelpAttr, "versionHelp", "--version", "version information")); } if (usageHelpAttr.size() > 1) { new Tracer().warn(multiple, usageHelpAttr, "usageHelp", "--help", "usage help message"); } if (versionHelpAttr.size() > 1) { new Tracer().warn(multiple, versionHelpAttr, "versionHelp", "--version", "version information"); } }
@SuppressWarnings("unchecked") private int applyValuesToCollectionField(ArgSpec argSpec, LookBehind lookBehind, Range arity, Stack<String> args, Set<ArgSpec> initialized, String argDescription) throws Exception { Collection<Object> collection = (Collection<Object>) argSpec.getValue(); Class<?> type = argSpec.auxiliaryTypes()[0]; List<Object> converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription); if (collection == null || (!collection.isEmpty() && !initialized.contains(argSpec))) { tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName()); collection = createCollection(argSpec.type()); // collection type argSpec.setValue(collection); } initialized.add(argSpec); for (Object element : converted) { if (element instanceof Collection<?>) { collection.addAll((Collection<?>) element); } else { collection.add(element); } } parseResult.add(argSpec, position); argSpec.setValue(collection); return converted.size(); }
/** Returns the default value String displayed in the description. If this ArgSpec is part of a * CommandSpec with a {@link IDefaultValueProvider}, this method will first try to obtain * the default value from the default value provider; if the provider is {@code null} or if it * returns a {@code null} value, then next any value set to {@link ArgSpec#defaultValue()} * is returned, and if this is also {@code null}, finally the {@linkplain ArgSpec#initialValue() initial value} is returned. * @see CommandSpec#defaultValueProvider() * @see ArgSpec#defaultValue() */ public String defaultValueString() { String fromProvider = null; IDefaultValueProvider defaultValueProvider = null; try { defaultValueProvider = commandSpec.defaultValueProvider(); fromProvider = defaultValueProvider == null ? null : defaultValueProvider.defaultValue(this); } catch (Exception ex) { new Tracer().info("Error getting default value for %s from %s: %s", this, defaultValueProvider, ex); } String defaultVal = fromProvider == null ? this.defaultValue() : fromProvider; Object value = defaultVal == null ? initialValue() : defaultVal; if (value != null && value.getClass().isArray()) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < Array.getLength(value); i++) { sb.append(i > 0 ? ", " : "").append(Array.get(value, i)); } return sb.insert(0, "[").append("]").toString(); } return String.valueOf(value); }
private int consumeOneArgument(ArgSpec argSpec, LookBehind lookBehind, Range arity, int consumed, String arg, Class<?> type, List<Object> result, int index, String argDescription) { if (!lookBehind.isAttached()) { parseResult.nowProcessing(argSpec, arg); } String raw = trim(arg); String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed); ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0); for (int j = 0; j < values.length; j++) { result.add(tryConvert(argSpec, index, converter, values[j], type)); if (tracer.isInfo()) { tracer.info("Adding [%s] to %s for %s%n", String.valueOf(result.get(result.size() - 1)), argSpec.toString(), argDescription); } parseResult.addStringValue(argSpec, values[j]); } parseResult.addOriginalStringValue(argSpec, raw); return ++index; } private boolean canConsumeOneArgument(ArgSpec argSpec, Range arity, int consumed, String arg, Class<?> type, String argDescription) {
private void consumeOneMapArgument(ArgSpec argSpec, LookBehind lookBehind, Range arity, int consumed, String arg, Class<?>[] classes, ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter, Map<Object, Object> result, int index, String argDescription) { if (!lookBehind.isAttached()) { parseResult.nowProcessing(argSpec, arg); } String raw = trim(arg); String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed); for (String value : values) { String[] keyValue = splitKeyValue(argSpec, value); Object mapKey = tryConvert(argSpec, index, keyConverter, keyValue[0], classes[0]); Object mapValue = tryConvert(argSpec, index, valueConverter, keyValue[1], classes[1]); result.put(mapKey, mapValue); if (tracer.isInfo()) { tracer.info("Putting [%s : %s] in %s<%s, %s> %s for %s%n", String.valueOf(mapKey), String.valueOf(mapValue), result.getClass().getSimpleName(), classes[0].getSimpleName(), classes[1].getSimpleName(), argSpec.toString(), argDescription); } parseResult.addStringValue(argSpec, keyValue[0]); parseResult.addStringValue(argSpec, keyValue[1]); } parseResult.addOriginalStringValue(argSpec, raw); }
private int applyValuesToMapField(ArgSpec argSpec, LookBehind lookBehind, Range arity, Stack<String> args, Set<ArgSpec> initialized, String argDescription) throws Exception { Class<?>[] classes = argSpec.auxiliaryTypes(); if (classes.length < 2) { throw new ParameterException(CommandLine.this, argSpec.toString() + " needs two types (one for the map key, one for the value) but only has " + classes.length + " types configured.",argSpec, null); } ITypeConverter<?> keyConverter = getTypeConverter(classes[0], argSpec, 0); ITypeConverter<?> valueConverter = getTypeConverter(classes[1], argSpec, 1); @SuppressWarnings("unchecked") Map<Object, Object> map = (Map<Object, Object>) argSpec.getValue(); if (map == null || (!map.isEmpty() && !initialized.contains(argSpec))) { tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName()); map = createMap(argSpec.type()); // map class argSpec.setValue(map); } initialized.add(argSpec); int originalSize = map.size(); consumeMapArguments(argSpec, lookBehind, arity, args, classes, keyConverter, valueConverter, map, argDescription); parseResult.add(argSpec, position); argSpec.setValue(map); return map.size() - originalSize; }
boolean resemblesOption(String arg, Tracer tracer) { if (parser().unmatchedOptionsArePositionalParams()) { if (tracer != null && tracer.isDebug()) {tracer.debug("Parser is configured to treat all unmatched options as positional parameter%n", arg);} return false; } if (options().isEmpty()) { boolean result = arg.startsWith("-"); if (tracer != null && tracer.isDebug()) {tracer.debug("%s %s an option%n", arg, (result ? "resembles" : "doesn't resemble"));} return result; } int count = 0; for (String optionName : optionsMap().keySet()) { for (int i = 0; i < arg.length(); i++) { if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) { count++; } else { break; } } } boolean result = count > 0 && count * 10 >= optionsMap().size() * 9; // at least one prefix char in common with 9 out of 10 options if (tracer != null && tracer.isDebug()) {tracer.debug("%s %s an option: %d matching prefix chars out of %d option names%n", arg, (result ? "resembles" : "doesn't resemble"), count, optionsMap().size());} return result; } }
@Test public void testTracerIsWarn() { final String PROPERTY = "picocli.trace"; String old = System.getProperty(PROPERTY); try { System.clearProperty(PROPERTY); assertTrue("WARN enabled by default", new CommandLine.Tracer().isWarn()); System.setProperty(PROPERTY, "OFF"); assertFalse("WARN can be disabled by setting to OFF", new CommandLine.Tracer().isWarn()); System.setProperty(PROPERTY, "WARN"); assertTrue("WARN can be explicitly enabled", new CommandLine.Tracer().isWarn()); } finally { if (old == null) { System.clearProperty(PROPERTY); } else { System.setProperty(PROPERTY, old); } } }