Skip to content

Commit

Permalink
[#983][#1061][#1101] update release notes; added examples; added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
remkop committed Jun 6, 2020
1 parent 816fb45 commit 7c5f1c1
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 8 deletions.
4 changes: 3 additions & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,10 @@ To use the `ManPageGenerator` tool as a subcommand, you will need the `picocli-c
* [#1087] API: Add methods `ArgumentGroupSpec::allOptionsNested` and `ArgumentGroupSpec::allPositionalParametersNested`.
* [#1086] API: add methods `Help.Layout::addAllOptions` and `Help.Layout::addAllPositionals`, to show all specified options, including hidden ones.
* [#1085] API: Add method `Help::optionSectionGroups` to get argument groups with a header.
* [#1101] API: Add method `Help::createDetailedSynopsisOptionsText` to specify which options to show in the synopsis.
* [#1061] API: Add method `Help::makeSynopsisFromParts` for building complex synopsis strings; synopsis now shows non-group options before argument groups, for a more natural synopsis when groups contain only positional parameters.
* [#983] API: Allow making inherited options hidden on subcommands.
* [#1051][#1056] Enhancement: `GenerateCompletion` command no longer needs to be a direct subcommand of the root command. Thanks to [Philippe Charles](https://github.com/charphi) for the pull request.
* [#1061] Enhancement: synopsis now shows non-group options before argument groups, for a more natural synopsis when groups contain only positional parameters.
* [#1068] Enhancement: Make `ParserSpec::toString` output settings in alphabetic order.
* [#1069] Enhancement: Debug output should show `optionsCaseInsensitive` and `subcommandsCaseInsensitive` settings.
* [#1070] Enhancement: Code cleanup: removed redundant modifiers and initializations, unused variables, incorrect javadoc references, and more. Thanks to [NewbieOrange](https://github.com/NewbieOrange) for the pull request.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package picocli.examples.customhelp;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Help.Ansi.Text;
import picocli.CommandLine.IHelpSectionRenderer;
import picocli.CommandLine.Model.ArgGroupSpec;
import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Option;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS;
import static picocli.CommandLine.ScopeType.INHERIT;

/**
* Demonstrates how to use custom {@link IHelpSectionRenderer} implementations
* to hide INHERIT-ed options in the option list and synopsis of subcommands.
*/
public class CustomOptionListAndSynopsis {

static IHelpSectionRenderer optionListRenderer = new IHelpSectionRenderer() {
public String render(CommandLine.Help help) {
return help.optionListExcludingGroups(filter(help.commandSpec().options()));
}
};

static IHelpSectionRenderer synopsisRenderer = new IHelpSectionRenderer() {
public String render(CommandLine.Help help) {

// building a custom synopsis is more complex;
// we subclass Help here so we can invoke some protected methods
class HelpHelper extends CommandLine.Help {
public HelpHelper(CommandSpec commandSpec, ColorScheme colorScheme) {
super(commandSpec, colorScheme);
}
String buildSynopsis() {
// customize the list of options to show in the synopsis
List<OptionSpec> myOptions = filter(help.commandSpec().options());

// and build up the synopsis text with our customized options list...
Set<ArgSpec> argsInGroups = new HashSet<ArgSpec>();
Text groupsText = createDetailedSynopsisGroupsText(argsInGroups);
Text optionText = createDetailedSynopsisOptionsText(argsInGroups, myOptions, CommandLine.Help.createShortOptionArityAndNameComparator(), true);
Text endOfOptionsText = createDetailedSynopsisEndOfOptionsText();
Text positionalParamText = createDetailedSynopsisPositionalsText(argsInGroups);
Text commandText = createDetailedSynopsisCommandText();

return makeSynopsisFromParts(help.synopsisHeadingLength(), optionText, groupsText, endOfOptionsText, positionalParamText, commandText);
}
}
// and delegate the work to our helper subclass
return new HelpHelper(help.commandSpec(), help.colorScheme()).buildSynopsis();
}
};

private static List<OptionSpec> filter(List<OptionSpec> optionList) {
List<OptionSpec> shown = new ArrayList<OptionSpec>();
for (OptionSpec option : optionList) {
if (!option.inherited() || option.shortestName().equals("-b")) {
shown.add(option);
}
}
return shown;
}


@Command
static class App {
@Option(names = "-a", scope = INHERIT, description = "a option") boolean a;
@Option(names = "-b", scope = INHERIT, description = "b option") boolean b;
@Option(names = "-c", scope = INHERIT, description = "c option") boolean c;

@Command(description = "This is the `sub` subcommand...")
void sub() {}
}
public static void main(String[] args) {
CommandLine cmd = new CommandLine(new App());
for (CommandLine sub : cmd.getSubcommands().values()) {
sub.getHelpSectionMap().put(SECTION_KEY_OPTION_LIST, optionListRenderer);
sub.getHelpSectionMap().put(SECTION_KEY_SYNOPSIS, synopsisRenderer);
}
String expected = String.format("" +
"Usage: <main class> [-abc] [COMMAND]%n" +
" -a a option%n" +
" -b b option%n" +
" -c c option%n" +
"Commands:%n" +
" sub This is the `sub` subcommand...%n");
String actual = cmd.getUsageMessage(CommandLine.Help.Ansi.OFF);
if (!expected.equals(actual)) {
throw new IllegalStateException(expected + " != " + actual);
}

String expectedSub = String.format("" +
"Usage: <main class> sub [-b]%n" +
"This is the `sub` subcommand...%n" +
" -b b option%n");
String actualSub = cmd.getSubcommands().get("sub").getUsageMessage(CommandLine.Help.Ansi.OFF);
if (!expectedSub.equals(actualSub)) {
throw new IllegalStateException(expectedSub + " != " + actualSub);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package picocli.examples.customhelp;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Help;
import picocli.CommandLine.Help.Ansi.Text;
import picocli.CommandLine.Help.ColorScheme;
import picocli.CommandLine.IHelpFactory;
import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Option;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

import static picocli.CommandLine.ScopeType.INHERIT;

/**
* Demonstrates how to use a custom {@link IHelpFactory} and subclassing {@code Help}
* to hide INHERIT-ed options in the option list and synopsis of subcommands.
*/
public class CustomOptionListAndSynopsisWithInheritance {

@Command(description = "Inheritance example")
static class App {
@Option(names = "-a", scope = INHERIT, description = "a option") boolean a;
@Option(names = "-b", scope = INHERIT, description = "b option") boolean b;
@Option(names = "-c", scope = INHERIT, description = "c option") boolean c;

@Command(description = "This is the `sub` subcommand...")
void sub() {}
}

static class MyHelpFactory implements IHelpFactory {
@Override
public Help create(CommandSpec commandSpec, ColorScheme colorScheme) {
return new Help(commandSpec, colorScheme) {
@Override
public String optionListExcludingGroups(List<OptionSpec> optionList, Layout layout, Comparator<OptionSpec> optionSort, IParamLabelRenderer valueLabelRenderer) {
return super.optionListExcludingGroups(filter(optionList), layout, optionSort, valueLabelRenderer);
}

@Override
protected Text createDetailedSynopsisOptionsText(Collection<ArgSpec> done, List<OptionSpec> optionList, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
return super.createDetailedSynopsisOptionsText(done, filter(optionList), optionSort, clusterBooleanOptions);
}

private List<OptionSpec> filter(List<OptionSpec> optionList) {
List<OptionSpec> shown = new ArrayList<OptionSpec>();
for (OptionSpec option : optionList) {
if (!option.inherited() || option.shortestName().equals("-b")) {
shown.add(option);
}
}
return shown;
}
};
}
}

public static void main(String[] args) {
CommandLine cmd = new CommandLine(new App());
cmd.setHelpFactory(new MyHelpFactory());

String expected = String.format("" +
"Usage: <main class> sub [-b]%n" +
"This is the `sub` subcommand...%n" +
" -b b option%n");
String actual = cmd.getSubcommands().get("sub").getUsageMessage(Help.Ansi.OFF);
if (!expected.equals(actual)) {
throw new IllegalStateException(expected + " != " + actual);
}
}
}
96 changes: 89 additions & 7 deletions src/test/java/picocli/HelpTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
Expand All @@ -74,6 +77,8 @@
import static picocli.CommandLine.Help.Visibility.ALWAYS;
import static picocli.CommandLine.Help.Visibility.NEVER;
import static picocli.CommandLine.Help.Visibility.ON_DEMAND;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS;
import static picocli.CommandLine.ScopeType.INHERIT;
import static picocli.TestUtil.textArray;
import static picocli.TestUtil.usageString;
Expand Down Expand Up @@ -4520,7 +4525,6 @@ private Map<String, Help> createCustomCommandList(Help help) {
" yyy subcommand yyy%n"), cmd.getUsageMessage());
}

@Ignore("Needs synopsis customization for #983")
@Test //#983
public void testCustomizeOptionListViaRenderer() {
@Command
Expand All @@ -4532,25 +4536,94 @@ class App {
@Command void sub() {}
}

IHelpSectionRenderer renderer = new IHelpSectionRenderer() {
IHelpSectionRenderer optionListRenderer = new IHelpSectionRenderer() {
public String render(Help help) {
List<OptionSpec> options = new ArrayList<OptionSpec>(help.commandSpec().options());
options.remove(help.commandSpec().findOption("-b"));
return help.optionListExcludingGroups(options);
}
};
IHelpSectionRenderer synopsisRenderer = new IHelpSectionRenderer() {
public String render(Help help) {
List<OptionSpec> options = new ArrayList<OptionSpec>(help.commandSpec().options());
options.remove(help.commandSpec().findOption("-b"));

Set<ArgSpec> argsInGroups = new HashSet<ArgSpec>();
Text groupsText = help.createDetailedSynopsisGroupsText(argsInGroups);
Text optionText = help.createDetailedSynopsisOptionsText(argsInGroups, options, Help.createShortOptionArityAndNameComparator(), true);
Text endOfOptionsText = help.createDetailedSynopsisEndOfOptionsText();
Text positionalParamText = help.createDetailedSynopsisPositionalsText(argsInGroups);
Text commandText = help.createDetailedSynopsisCommandText();

return help.makeSynopsisFromParts(help.synopsisHeadingLength(), optionText, groupsText, endOfOptionsText, positionalParamText, commandText);
}
};

CommandLine cmd = new CommandLine(new App());
cmd.getHelpSectionMap().put(UsageMessageSpec.SECTION_KEY_OPTION_LIST, renderer);
cmd.getHelpSectionMap().put(UsageMessageSpec.SECTION_KEY_OPTION_LIST, optionListRenderer);
cmd.getHelpSectionMap().put(UsageMessageSpec.SECTION_KEY_SYNOPSIS, synopsisRenderer);
assertEquals(String.format("" +
"Usage: <main class> [-ac] [COMMAND]%n" + // TODO synopsis abc
"Usage: <main class> [-ac] [COMMAND]%n" +
" -a a option%n" +
" -c c option%n" +
"Commands:%n" +
" sub%n"), cmd.getUsageMessage());
}

@Ignore("Needs synopsis customization for #983")
@Test //#983
public void testCustomizeSubcommandsOptionListViaRenderer() {
@Command
class App {
@Option(names = "-a", scope = INHERIT, description = "a option") boolean a;
@Option(names = "-b", scope = INHERIT, description = "b option") boolean b;
@Option(names = "-c", scope = INHERIT, description = "c option") boolean c;

@Command(description = "This is the `sub` subcommand...")
void sub() {}
}

IHelpSectionRenderer optionListRenderer = new IHelpSectionRenderer() {
public String render(Help help) {
return help.optionListExcludingGroups(filter(help.commandSpec().options()));
}
};
IHelpSectionRenderer synopsisRenderer = new IHelpSectionRenderer() {
public String render(Help help) {
List<OptionSpec> options = filter(help.commandSpec().options());

Set<ArgSpec> argsInGroups = new HashSet<ArgSpec>();
Text groupsText = help.createDetailedSynopsisGroupsText(argsInGroups);
Text optionText = help.createDetailedSynopsisOptionsText(argsInGroups, options, Help.createShortOptionArityAndNameComparator(), true);
Text endOfOptionsText = help.createDetailedSynopsisEndOfOptionsText();
Text positionalParamText = help.createDetailedSynopsisPositionalsText(argsInGroups);
Text commandText = help.createDetailedSynopsisCommandText();

return help.makeSynopsisFromParts(help.synopsisHeadingLength(), optionText, groupsText, endOfOptionsText, positionalParamText, commandText);
}
};

CommandLine cmd = new CommandLine(new App());
for (CommandLine sub : cmd.getSubcommands().values()) {
sub.getHelpSectionMap().put(SECTION_KEY_OPTION_LIST, optionListRenderer);
sub.getHelpSectionMap().put(SECTION_KEY_SYNOPSIS, synopsisRenderer);
}
String expectedSub = String.format("" +
"Usage: <main class> sub [-b]%n" +
"This is the `sub` subcommand...%n" +
" -b b option%n");
String actualSub = cmd.getSubcommands().get("sub").getUsageMessage(CommandLine.Help.Ansi.OFF);
assertEquals(expectedSub, actualSub);
}
private static List<OptionSpec> filter(List<OptionSpec> optionList) {
List<OptionSpec> shown = new ArrayList<OptionSpec>();
for (OptionSpec option : optionList) {
if (!option.inherited() || option.shortestName().equals("-b")) {
shown.add(option);
}
}
return shown;
}

@Test //#983
public void testCustomizeOptionListViaInheritance() {
@Command
Expand All @@ -4568,20 +4641,29 @@ public Help create(CommandSpec commandSpec, ColorScheme colorScheme) {
return new Help(commandSpec, colorScheme) {
@Override
public String optionListExcludingGroups(List<OptionSpec> optionList, Layout layout, Comparator<OptionSpec> optionSort, IParamLabelRenderer valueLabelRenderer) {
return super.optionListExcludingGroups(filter(optionList), layout, optionSort, valueLabelRenderer);
}

@Override
protected Text createDetailedSynopsisOptionsText(Collection<ArgSpec> done, List<OptionSpec> optionList, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
return super.createDetailedSynopsisOptionsText(done, filter(optionList), optionSort, clusterBooleanOptions);
}

private List<OptionSpec> filter(List<OptionSpec> optionList) {
List<OptionSpec> shown = new ArrayList<OptionSpec>();
for (OptionSpec option : optionList) {
if (!option.inherited() || option.shortestName().equals("-b")) {
shown.add(option);
}
}
return super.optionListExcludingGroups(shown, layout, optionSort, valueLabelRenderer);
return shown;
}
};
}
});
String actual = cmd.getSubcommands().get("sub").getUsageMessage();
assertEquals(String.format("" +
"Usage: <main class> sub [-b]%n" + // TODO synopsis -abc
"Usage: <main class> sub [-b]%n" +
"This is the `sub` subcommand...%n" +
" -b b option%n"), actual);
}
Expand Down

0 comments on commit 7c5f1c1

Please sign in to comment.