factory, commands, commandTypes, options, parameters); for (CommandSpec sub : subcommands) { result.addSubcommand(sub.name(), sub); CommandSpec commandSpec = buildCommand(cls, factory, commands, commandTypes, options, parameters); commandSpec.addSubcommand(result.name(), result);
private static void initSubcommands(Command cmd, CommandSpec parent, IFactory factory) { for (Class<?> sub : cmd.subcommands()) { try { if (Help.class == sub) { throw new InitializationException(Help.class.getName() + " is not a valid subcommand. Did you mean " + HelpCommand.class.getName() + "?"); } CommandLine subcommandLine = toCommandLine(factory.create(sub), factory); parent.addSubcommand(subcommandName(sub), subcommandLine); initParentCommand(subcommandLine.getCommandSpec().userObject(), parent.userObject()); } catch (InitializationException ex) { throw ex; } catch (NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " + sub.getName() + ": the class has no constructor", ex); } catch (Exception ex) { throw new InitializationException("Could not instantiate and add subcommand " + sub.getName() + ": " + ex, ex); } } if (cmd.addMethodSubcommands() && !(parent.userObject() instanceof Method)) { parent.addMethodSubcommands(factory); } } static void initParentCommand(Object subcommand, Object parent) {
/** Adds the specified mixin {@code CommandSpec} object to the map of mixins for this command. * @param name the name that can be used to later retrieve the mixin * @param mixin the mixin whose options and positional parameters and other attributes to add to this command * @return this CommandSpec for method chaining */ public CommandSpec addMixin(String name, CommandSpec mixin) { mixins.put(name, mixin); parser.initSeparator(mixin.parser.separator()); initName(mixin.name()); initVersion(mixin.version()); initHelpCommand(mixin.helpCommand()); initVersionProvider(mixin.versionProvider()); initDefaultValueProvider(mixin.defaultValueProvider()); usageMessage.initFromMixin(mixin.usageMessage, this); for (Map.Entry<String, CommandLine> entry : mixin.subcommands().entrySet()) { addSubcommand(entry.getKey(), entry.getValue()); } for (OptionSpec optionSpec : mixin.options()) { addOption(optionSpec); } for (PositionalParamSpec paramSpec : mixin.positionalParameters()) { addPositional(paramSpec); } return this; }
@Test public void testVersionHelp_helpCommand() { CommandSpec helpCommand = CommandSpec.create().helpCommand(true); assertTrue(helpCommand.helpCommand()); CommandSpec parent = CommandSpec.create().addOption(OptionSpec.builder("-x").required(true).build()); parent.addSubcommand("help", helpCommand); CommandLine commandLine = new CommandLine(parent); commandLine.parse("help"); // no missing param exception try { commandLine.parse(); } catch (MissingParameterException ex) { assertEquals("Missing required option '-x=PARAM'", ex.getMessage()); assertEquals(1, ex.getMissing().size()); assertSame(ex.getMissing().get(0).toString(), parent.posixOptionsMap().get('x'), ex.getMissing().get(0)); } }
@Test public void testCommandSpecAddSubcommand_DisallowsDuplicateSubcommandAliases() { CommandSpec spec = CommandSpec.wrapWithoutInspection(null); CommandSpec sub = CommandSpec.wrapWithoutInspection(null); spec.addSubcommand("a", new CommandLine(sub)); CommandSpec sub2 = CommandSpec.wrapWithoutInspection(null); sub2.aliases("a"); try { spec.addSubcommand("x", new CommandLine(sub2)); } catch (InitializationException ex) { assertEquals("Alias 'a' for subcommand 'x' is already used by another subcommand of '<main class>'", ex.getMessage()); } }
@Test public void testCommandSpecAddSubcommand_DisallowsDuplicateSubcommandNames() { CommandSpec spec = CommandSpec.wrapWithoutInspection(null); CommandSpec sub = CommandSpec.wrapWithoutInspection(null); spec.addSubcommand("a", new CommandLine(sub)); try { spec.addSubcommand("a", new CommandLine(sub)); } catch (InitializationException ex) { assertEquals("Another subcommand named 'a' already exists for command '<main class>'", ex.getMessage()); } }
/** Adds the specified subcommand with the specified name. * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec. * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked * @param subcommand describes the subcommand to envoke when the name is encountered on the command line * @return this {@code CommandSpec} object for method chaining */ public CommandSpec addSubcommand(String name, CommandSpec subcommand) { return addSubcommand(name, new CommandLine(subcommand)); }
@Test public void testCommandSpecAddSubcommand_SubcommandInheritsResourceBundle() { ResourceBundle rb = ResourceBundle.getBundle("picocli.SharedMessages"); CommandSpec spec = CommandSpec.wrapWithoutInspection(null); spec.resourceBundle(rb); assertSame(rb, spec.resourceBundle()); CommandSpec sub = CommandSpec.wrapWithoutInspection(null); spec.addSubcommand("a", new CommandLine(sub)); assertSame(rb, sub.resourceBundle()); }
CommandSpec multiLineCmd = createCmd("multiLine", "The quick brown fox jumped over the lazy dog.%nThe quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog.%n%nThe quick brown fox jumped over the lazy dog."); rootCmd.addSubcommand("oneLine", oneLineCmd); rootCmd.addSubcommand("multiLine", multiLineCmd);
@Test public void testIssue430NewlineInSubcommandDescriptionList() { // courtesy [Benny Bottema](https://github.com/bbottema) CommandSpec rootCmd = createCmd("newlines", "Displays subcommands, one of which contains description newlines"); rootCmd.addSubcommand("subA", createCmd("subA", "regular description for subA")); rootCmd.addSubcommand("subB", createCmd("subB", "very,\nspecial,\nChristopher Walken style,\ndescription.")); rootCmd.addSubcommand("subC", createCmd("subC", "not so,%nspecial,%nJon Voight style,%ndescription.")); rootCmd.addSubcommand("subD", createCmd("subD", "regular description for subD")); assertEquals(String.format("" + "Usage: newlines [-hV] [COMMAND]%n" + "Displays subcommands, one of which contains description newlines%n" + " -h, --help Show this help message and exit.%n" + " -V, --version Print version information and exit.%n" + "Commands:%n" + " subA regular description for subA%n" + " subB very,%n" + " special,%n" + " Christopher Walken style,%n" + " description.%n" + " subC not so,%n" + " special,%n" + " Jon Voight style,%n" + " description.%n" + " subD regular description for subD%n"), new CommandLine(rootCmd).getUsageMessage()); }
/** Registers a subcommand with the specified name and all specified aliases. See also {@link #addSubcommand(String, Object)}. * * * @param name the string to recognize on the command line as a subcommand * @param command the object to initialize with command line arguments following the subcommand name. * This may be a {@code CommandLine} instance with its own (nested) subcommands * @param aliases zero or more alias names that are also recognized on the command line as this subcommand * @return this CommandLine object, to allow method chaining * @since 3.1 * @see #addSubcommand(String, Object) */ public CommandLine addSubcommand(String name, Object command, String... aliases) { CommandLine subcommandLine = toCommandLine(command, factory); subcommandLine.getCommandSpec().aliases.addAll(Arrays.asList(aliases)); getCommandSpec().addSubcommand(name, subcommandLine); CommandLine.Model.CommandReflection.initParentCommand(subcommandLine.getCommandSpec().userObject(), getCommandSpec().userObject()); return this; } /** Returns a map with the subcommands {@linkplain #addSubcommand(String, Object) registered} on this instance.
/** Reflects on the class of the {@linkplain #userObject() user object} and registers any command methods * (class methods annotated with {@code @Command}) as subcommands. * @param factory the factory used to create instances of subcommands, converters, etc., that are registered declaratively with annotation attributes * @return this {@link CommandSpec} object for method chaining * @see #addSubcommand(String, CommandLine) * @since 3.7.0 */ public CommandSpec addMethodSubcommands(IFactory factory) { if (userObject() instanceof Method) { throw new InitializationException("Cannot discover subcommand methods of this Command Method: " + userObject()); } for (Method method : getCommandMethods(userObject().getClass(), null)) { CommandLine cmd = new CommandLine(method, factory); addSubcommand(cmd.getCommandName(), cmd); } isAddMethodSubcommands = true; return this; }
@Test public void testHelpSubcommandRunPrintsParentUsageIfParentSet() { HelpCommand cmd = new HelpCommand(); CommandLine help = new CommandLine(cmd); CommandSpec spec = CommandSpec.create().name("parent"); spec.usageMessage().description("the parent command"); spec.addSubcommand("parent", help); new CommandLine(spec); // make sure parent spec has a CommandLine cmd.init(help, Help.Ansi.OFF, System.out, System.err); cmd.run(); String expected = String.format("" + "Usage: parent [COMMAND]%n" + "the parent command%n" + "Commands:%n" + " parent Displays help information about the specified command%n"); assertEquals(expected, this.systemOutRule.getLog()); }
@Test public void testShowAbbreviatedSynopsisUsageWithCommandOption() { CommandSpec spec = CommandSpec.create(); spec.addOption(OptionSpec.builder("-h", "--help").usageHelp(true).description("show help and exit").build()); // using abbreviated synopsis spec.usageMessage().abbreviateSynopsis(true); // adding a subcommand should show "COMMAND" option to the help synopsis spec.addSubcommand("subcommand", CommandSpec.create()); CommandLine commandLine = new CommandLine(spec); String actual = usageString(commandLine, Help.Ansi.OFF); String expected = String.format("" + "Usage: <main class> [OPTIONS] [COMMAND]%n" + " -h, --help show help and exit%n" + "Commands:%n" + " subcommand%n"); assertEquals(expected, actual); }
@Test public void testVersionHelp_helpCommand() { CommandSpec helpCommand = CommandSpec.create().helpCommand(true); assertTrue(helpCommand.helpCommand()); CommandSpec parent = CommandSpec.create().addOption(OptionSpec.builder("-x").required(true).build()); parent.addSubcommand("help", helpCommand); CommandLine commandLine = new CommandLine(parent); commandLine.parse("help"); // no missing param exception try { commandLine.parse(); } catch (MissingParameterException ex) { assertEquals("Missing required option '-x=PARAM'", ex.getMessage()); assertEquals(1, ex.getMissing().size()); assertSame(ex.getMissing().get(0).toString(), parent.posixOptionsMap().get('x'), ex.getMissing().get(0)); } }
@Test public void testShowSynopsisUsageWithCommandOption() { CommandSpec spec = CommandSpec.create(); spec.addOption(OptionSpec.builder("-h", "--help").usageHelp(true).description("show help and exit").build()); // adding a subcommand should show "COMMAND" option to the help synopsis spec.addSubcommand("subcommand", CommandSpec.create()); CommandLine commandLine = new CommandLine(spec); String actual = usageString(commandLine, Help.Ansi.OFF); String expected = String.format("" + "Usage: <main class> [-h] [COMMAND]%n" + " -h, --help show help and exit%n" + "Commands:%n" + " subcommand%n"); assertEquals(expected, actual); }
@Test public void testCommandSpecAddSubcommand_DisallowsDuplicateSubcommandAliases() { CommandSpec spec = CommandSpec.wrapWithoutInspection(null); CommandSpec sub = CommandSpec.wrapWithoutInspection(null); spec.addSubcommand("a", new CommandLine(sub)); CommandSpec sub2 = CommandSpec.wrapWithoutInspection(null); sub2.aliases("a"); try { spec.addSubcommand("x", new CommandLine(sub2)); } catch (InitializationException ex) { assertEquals("Alias 'a' for subcommand 'x' is already used by another subcommand of '<main class>'", ex.getMessage()); } }
@Test public void testCommandSpecAddSubcommand_DisallowsDuplicateSubcommandNames() { CommandSpec spec = CommandSpec.wrapWithoutInspection(null); CommandSpec sub = CommandSpec.wrapWithoutInspection(null); spec.addSubcommand("a", new CommandLine(sub)); try { spec.addSubcommand("a", new CommandLine(sub)); } catch (InitializationException ex) { assertEquals("Another subcommand named 'a' already exists for command '<main class>'", ex.getMessage()); } }
@Test public void testCommandSpecAddSubcommand_SubcommandInheritsResourceBundle() { ResourceBundle rb = ResourceBundle.getBundle("picocli.SharedMessages"); CommandSpec spec = CommandSpec.wrapWithoutInspection(null); spec.resourceBundle(rb); assertSame(rb, spec.resourceBundle()); CommandSpec sub = CommandSpec.wrapWithoutInspection(null); spec.addSubcommand("a", new CommandLine(sub)); assertSame(rb, sub.resourceBundle()); }
/** Adds the specified subcommand with the specified name. * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec. * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked * @param subcommand describes the subcommand to envoke when the name is encountered on the command line * @return this {@code CommandSpec} object for method chaining */ public CommandSpec addSubcommand(String name, CommandSpec subcommand) { return addSubcommand(name, new CommandLine(subcommand)); }