Skip to content

Commit c7320e7

Browse files
committed
updated docs with option visiblity levels
1 parent 9134f22 commit c7320e7

5 files changed

Lines changed: 346 additions & 0 deletions

File tree

content/docs/aesh/command-definition.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ The `@CommandDefinition` annotation is used to define a command class.
2828
| `defaultValueProvider` | `Class<? extends DefaultValueProvider>` | `NullDefaultValueProvider.class` | Dynamic default value resolver |
2929
| `stopAtFirstPositional` | `boolean` | `false` | Stop option parsing after the first positional argument |
3030
| `helpUrl` | `String` | `""` | URL to documentation (shown in `--help` output) |
31+
| `helpGroup` | `String` | `""` | Group heading when listed as a subcommand in parent's help |
32+
| `helpSectionProvider` | `Class<? extends HelpSectionProvider>` | `NullHelpSectionProvider.class` | Provider for dynamic help sections |
3133

3234
## Example
3335

@@ -182,3 +184,142 @@ Provides access to:
182184
- `stop()` - Stop the console
183185
- `getShell()` - Access the shell
184186
- `getHelpInfo(String)` - Get help text
187+
188+
## Help Group for Subcommands
189+
190+
The `helpGroup` property on `@CommandDefinition` (and `@GroupCommandDefinition`) controls how a subcommand appears in its parent's help output. Subcommands with the same `helpGroup` value are displayed together under that heading.
191+
192+
### Basic Usage
193+
194+
```java
195+
@GroupCommandDefinition(
196+
name = "cli",
197+
description = "My CLI tool",
198+
generateHelp = true,
199+
groupCommands = {
200+
BuildCommand.class, TestCommand.class,
201+
InstallCommand.class, PublishCommand.class,
202+
InfoCommand.class, VersionCommand.class
203+
}
204+
)
205+
public class CliCommand implements GroupCommand<CommandInvocation> {
206+
@Override
207+
public CommandResult execute(CommandInvocation invocation) {
208+
return CommandResult.SUCCESS;
209+
}
210+
}
211+
212+
@CommandDefinition(name = "build", description = "Build the project", helpGroup = "Build")
213+
public class BuildCommand implements Command<CommandInvocation> { /* ... */ }
214+
215+
@CommandDefinition(name = "test", description = "Run tests", helpGroup = "Build")
216+
public class TestCommand implements Command<CommandInvocation> { /* ... */ }
217+
218+
@CommandDefinition(name = "install", description = "Install dependencies", helpGroup = "Publish")
219+
public class InstallCommand implements Command<CommandInvocation> { /* ... */ }
220+
221+
@CommandDefinition(name = "publish", description = "Publish package", helpGroup = "Publish")
222+
public class PublishCommand implements Command<CommandInvocation> { /* ... */ }
223+
224+
@CommandDefinition(name = "info", description = "Show project info")
225+
public class InfoCommand implements Command<CommandInvocation> { /* ... */ }
226+
227+
@CommandDefinition(name = "version", description = "Show version")
228+
public class VersionCommand implements Command<CommandInvocation> { /* ... */ }
229+
```
230+
231+
Help output:
232+
```
233+
Usage: cli [<options>]
234+
My CLI tool
235+
236+
Build:
237+
build Build the project
238+
test Run tests
239+
240+
Publish:
241+
install Install dependencies
242+
publish Publish package
243+
244+
Other:
245+
info Show project info
246+
version Show version
247+
```
248+
249+
### How It Works
250+
251+
1. Subcommands with the same `helpGroup` value are grouped under that heading
252+
2. Named groups appear first, in the order their first subcommand was defined
253+
3. Subcommands without a `helpGroup` appear under a default heading
254+
4. If all subcommands have a `helpGroup`, there is no default group
255+
256+
This is separate from the `helpGroup` property on `@Option`, which groups options within a single command's help output. See [Options - Help Grouping](/docs/aesh/options#help-grouping).
257+
258+
## Help Section Provider
259+
260+
The `helpSectionProvider` property lets you dynamically add sections to a command's help output at render time. This is useful for showing external plugins, aliases, or dynamically discovered commands without statically defining them.
261+
262+
### Implementing a Provider
263+
264+
Create a class that implements `HelpSectionProvider`:
265+
266+
```java
267+
public class PluginHelpProvider implements HelpSectionProvider {
268+
269+
@Override
270+
public Map<String, List<HelpEntry>> getAdditionalSections() {
271+
Map<String, List<HelpEntry>> sections = new LinkedHashMap<>();
272+
273+
// Discover plugins at runtime
274+
List<HelpEntry> plugins = new ArrayList<>();
275+
plugins.add(new HelpEntry("docker", "Docker integration plugin"));
276+
plugins.add(new HelpEntry("k8s", "Kubernetes deployment plugin"));
277+
sections.put("Plugins", plugins);
278+
279+
return sections;
280+
}
281+
}
282+
```
283+
284+
### Registering the Provider
285+
286+
```java
287+
@GroupCommandDefinition(
288+
name = "app",
289+
description = "My application",
290+
generateHelp = true,
291+
groupCommands = {RunCommand.class, BuildCommand.class},
292+
helpSectionProvider = PluginHelpProvider.class
293+
)
294+
public class AppCommand implements GroupCommand<CommandInvocation> {
295+
@Override
296+
public CommandResult execute(CommandInvocation invocation) {
297+
return CommandResult.SUCCESS;
298+
}
299+
}
300+
```
301+
302+
Help output:
303+
```
304+
Usage: app [<options>]
305+
My application
306+
307+
app commands:
308+
run Run the application
309+
build Build the project
310+
311+
Plugins:
312+
docker Docker integration plugin
313+
k8s Kubernetes deployment plugin
314+
```
315+
316+
### Merging with helpGroup
317+
318+
If a provider section name matches an existing `helpGroup` from statically defined subcommands, the entries are appended to that group rather than creating a duplicate heading.
319+
320+
### Key Points
321+
322+
1. **Zero startup cost** -- The provider class is stored as a reference and only instantiated when help is rendered
323+
2. **Works with both `@CommandDefinition` and `@GroupCommandDefinition`**
324+
3. **HelpEntry** is a simple value class with `name()` and `description()` (description is optional)
325+
4. **Return an empty map** (not null) if there are no additional sections to show

content/docs/aesh/group-commands.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class GitCommand implements GroupCommand<CommandInvocation> {
3838
| `groupCommands` | `Class[]` | `{}` | Array of subcommand classes |
3939
| `generateHelp` | `boolean` | `false` | Auto-generate `--help` option |
4040
| `aliases` | `String[]` | `{}` | Alternative names for the group |
41+
| `helpGroup` | `String` | `""` | Group heading when listed as a subcommand in parent's help |
42+
| `helpSectionProvider` | `Class<? extends HelpSectionProvider>` | `NullHelpSectionProvider.class` | Provider for dynamic help sections |
4143

4244
## Subcommands
4345

content/docs/aesh/option-groups.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ The `@OptionGroup` annotation defines key=value pair options collected into a `M
2323
| `activator` | `Class<? extends OptionActivator>` | `NullActivator.class` | Custom activator |
2424
| `renderer` | `Class<? extends OptionRenderer>` | `NullOptionRenderer.class` | Custom renderer |
2525
| `parser` | `Class<? extends OptionParser>` | `AeshOptionParser.class` | Custom parser |
26+
| `visibility` | `OptionVisibility` | `BRIEF` | Controls help and completion visibility (see [Options - Visibility Levels](../options#visibility-levels)) |
2627

2728
## Basic Example
2829

content/docs/aesh/option-lists.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ The `@OptionList` annotation defines options that accept multiple values separat
2626
| `parser` | `Class<? extends OptionParser>` | `AeshOptionParser.class` | Custom parser |
2727
| `aliases` | `String[]` | `{}` | Alternative long names for this option |
2828
| `helpGroup` | `String` | `""` | Group heading for this option in help output (see [Options - Help Grouping](../options#help-grouping)) |
29+
| `exclusiveWith` | `String[]` | `{}` | Names of mutually exclusive options (see [Options - Mutually Exclusive Options](../options#mutually-exclusive-options)) |
30+
| `visibility` | `OptionVisibility` | `BRIEF` | Controls help and completion visibility (see [Options - Visibility Levels](../options#visibility-levels)) |
2931

3032
## Basic Example
3133

content/docs/aesh/options.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ The `@Option` annotation defines command-line options (flags with values).
3333
| `parser` | `Class<? extends OptionParser>` | `AeshOptionParser.class` | Custom parser |
3434
| `aliases` | `String[]` | `{}` | Alternative long names for this option |
3535
| `helpGroup` | `String` | `""` | Group heading for this option in help output |
36+
| `exclusiveWith` | `String[]` | `{}` | Names of mutually exclusive options (without `--` prefix) |
37+
| `visibility` | `OptionVisibility` | `BRIEF` | Controls help and completion visibility (`BRIEF`, `FULL`, `HIDDEN`) |
3638
| `descriptionUrl` | `String` | `""` | URL for option documentation (clickable in supported terminals) |
3739
| `url` | `boolean` | `false` | Treat option value as a URL (rendered as clickable link) |
3840

@@ -540,6 +542,204 @@ ProcessedOptionBuilder.builder()
540542
.build();
541543
```
542544

545+
## Mutually Exclusive Options
546+
547+
The `exclusiveWith` property declares that two or more options cannot be used together. If a user provides both, a `MutuallyExclusiveOptionException` is thrown during parsing.
548+
549+
### Basic Usage
550+
551+
```java
552+
@CommandDefinition(name = "export", description = "Export data", generateHelp = true)
553+
public class ExportCommand implements Command<CommandInvocation> {
554+
555+
@Option(hasValue = false, description = "Output as JSON", exclusiveWith = {"xml", "csv"})
556+
private boolean json;
557+
558+
@Option(hasValue = false, description = "Output as XML", exclusiveWith = {"json", "csv"})
559+
private boolean xml;
560+
561+
@Option(hasValue = false, description = "Output as CSV", exclusiveWith = {"json", "xml"})
562+
private boolean csv;
563+
564+
@Option(hasValue = false, description = "Verbose output")
565+
private boolean verbose;
566+
567+
@Override
568+
public CommandResult execute(CommandInvocation invocation) {
569+
return CommandResult.SUCCESS;
570+
}
571+
}
572+
```
573+
574+
Usage:
575+
```bash
576+
# Valid -- only one format selected
577+
$ export --json
578+
$ export --xml --verbose
579+
580+
# Invalid -- mutually exclusive options used together
581+
$ export --json --xml
582+
Error: Options --json and --xml are mutually exclusive.
583+
```
584+
585+
### How It Works
586+
587+
1. Each option lists the names of options it conflicts with (long names, without the `--` prefix)
588+
2. The relationship should be declared on both sides: if `--json` lists `xml`, then `--xml` should list `json`
589+
3. Validation runs after parsing, at the same point as required option checks
590+
4. Non-exclusive options (like `--verbose` above) can be freely combined with any exclusive option
591+
592+
### Tab Completion
593+
594+
When an exclusive option has been set, conflicting options are automatically filtered from tab completion. For example, after typing `export --json`, pressing Tab will not suggest `--xml` or `--csv`.
595+
596+
### With OptionList
597+
598+
`exclusiveWith` also works with `@OptionList`:
599+
600+
```java
601+
@OptionList(description = "Items to process", exclusiveWith = {"file"})
602+
private List<String> items;
603+
604+
@Option(description = "Read items from file", exclusiveWith = {"items"})
605+
private String file;
606+
```
607+
608+
### Programmatic API
609+
610+
When building commands programmatically, use `ProcessedOptionBuilder`:
611+
612+
```java
613+
ProcessedOptionBuilder.builder()
614+
.name("json")
615+
.type(boolean.class)
616+
.hasValue(false)
617+
.exclusiveWith("xml", "csv")
618+
.build();
619+
```
620+
621+
## Visibility Levels
622+
623+
The `visibility` property controls whether an option appears in `--help` output and tab completion. This is useful for organizing help output by importance — showing essential options by default and revealing advanced or deprecated options only on request.
624+
625+
### Visibility Values
626+
627+
| Level | `--help` | `--help=all` | Tab completion |
628+
|-------|----------|--------------|----------------|
629+
| `BRIEF` (default) | Shown | Shown | Shown |
630+
| `FULL` | Hidden | Shown | Shown |
631+
| `HIDDEN` | Hidden | Hidden | Hidden |
632+
633+
### Basic Usage
634+
635+
```java
636+
@CommandDefinition(name = "serve", description = "Start server", generateHelp = true)
637+
public class ServeCommand implements Command<CommandInvocation> {
638+
639+
@Option(shortName = 'p', description = "Server port")
640+
private int port;
641+
642+
@Option(description = "Bind address")
643+
private String host;
644+
645+
@Option(description = "Enable request tracing", visibility = OptionVisibility.FULL)
646+
private boolean trace;
647+
648+
@Option(description = "Thread pool size", visibility = OptionVisibility.FULL)
649+
private int threads;
650+
651+
@Option(description = "Internal diagnostic token", visibility = OptionVisibility.HIDDEN)
652+
private String diagnosticToken;
653+
654+
@Override
655+
public CommandResult execute(CommandInvocation invocation) {
656+
return CommandResult.SUCCESS;
657+
}
658+
}
659+
```
660+
661+
Default help shows only essential options:
662+
663+
```bash
664+
$ serve --help
665+
Usage: serve [<options>]
666+
Start server
667+
668+
Options:
669+
-p, --port Server port
670+
--host Bind address
671+
-h, --help Display help (use --help=all for all options)
672+
```
673+
674+
Full help reveals advanced options:
675+
676+
```bash
677+
$ serve --help=all
678+
Usage: serve [<options>]
679+
Start server
680+
681+
Options:
682+
-p, --port Server port
683+
--host Bind address
684+
--trace Enable request tracing
685+
--threads Thread pool size
686+
-h, --help Display help (use --help=all for all options)
687+
```
688+
689+
The `--diagnosticToken` option never appears in help but still works when typed explicitly.
690+
691+
### When to Use Each Level
692+
693+
- **`BRIEF`** — Options every user needs to know about. This is the default; existing commands are unaffected.
694+
- **`FULL`** — Advanced tuning, debugging, or rarely-used options. Power users can discover them with `--help=all`.
695+
- **`HIDDEN`** — Internal, deprecated, or experimental options that should not be discoverable. They still parse and work when used explicitly.
696+
697+
### Tab Completion
698+
699+
`HIDDEN` options are excluded from tab completion. `BRIEF` and `FULL` options are both offered during completion — visibility only affects help output for `FULL` options.
700+
701+
### With Help Grouping
702+
703+
Visibility works with `helpGroup`. A group heading is only printed if it contains at least one visible option:
704+
705+
```java
706+
@Option(description = "Enable tracing", helpGroup = "Diagnostics",
707+
visibility = OptionVisibility.FULL)
708+
private boolean trace;
709+
710+
@Option(description = "Profile mode", helpGroup = "Diagnostics",
711+
visibility = OptionVisibility.FULL)
712+
private boolean profile;
713+
```
714+
715+
With `--help`, the "Diagnostics" group is omitted entirely. With `--help=all`, it appears with both options.
716+
717+
### With OptionList and OptionGroup
718+
719+
Visibility also works with `@OptionList` and `@OptionGroup`:
720+
721+
```java
722+
@OptionList(description = "Debug modules", visibility = OptionVisibility.FULL)
723+
private List<String> debugModules;
724+
725+
@OptionGroup(shortName = 'X', description = "Internal flags",
726+
visibility = OptionVisibility.HIDDEN)
727+
private Map<String, String> internalFlags;
728+
```
729+
730+
### Programmatic API
731+
732+
When building commands programmatically, use `ProcessedOptionBuilder`:
733+
734+
```java
735+
ProcessedOptionBuilder.builder()
736+
.name("trace")
737+
.type(boolean.class)
738+
.description("Enable request tracing")
739+
.visibility(OptionVisibility.FULL)
740+
.build();
741+
```
742+
543743
## Required Options
544744

545745
```java

0 commit comments

Comments
 (0)