Skip to content

Commit d4f9779

Browse files
committed
Improve getting-started and fix API inaccuracies in converters, validators, activators docs
1 parent 294e17a commit d4f9779

5 files changed

Lines changed: 460 additions & 167 deletions

File tree

content/docs/aesh/activators.md

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,41 @@ title: 'Activators'
55
weight: 11
66
---
77

8-
Activators control whether options, arguments, or entire commands are available/enabled.
8+
Activators control whether options or entire commands are available. Deactivated options are hidden from tab completion and help, and deactivated commands are invisible to the user.
99

1010
## OptionActivator
1111

12-
Enable or disable an option based on conditions:
12+
Enable or disable an option based on conditions. The `ParsedCommand` gives you access to the current command and the values of other options that have been parsed so far:
1313

1414
```java
15+
public interface OptionActivator {
16+
boolean isActivated(ParsedCommand parsedCommand);
17+
}
18+
```
19+
20+
### Example: Conditional Options
21+
22+
SSL-related options should only appear when `--secure` is used:
23+
24+
```java
25+
import org.aesh.command.activator.OptionActivator;
26+
import org.aesh.command.impl.internal.ParsedCommand;
27+
1528
public class SslOptionActivator implements OptionActivator {
1629

1730
@Override
18-
public boolean isActivated(CompleterInvocation invocation) {
19-
MyCommand cmd = (MyCommand) invocation.getCommand();
20-
// SSL options only active if secure mode is enabled
21-
return cmd.secureMode;
31+
public boolean isActivated(ParsedCommand parsedCommand) {
32+
// SSL options only active if --secure has been specified
33+
return parsedCommand.findLongOptionNoActivatorCheck("secure")
34+
.getValue() != null;
2235
}
2336
}
2437
```
2538

2639
Usage:
2740

2841
```java
29-
@CommandDefinition(name = "server")
42+
@CommandDefinition(name = "server", description = "Start the server")
3043
public class ServerCommand implements Command<CommandInvocation> {
3144

3245
@Option(name = "secure", hasValue = false, description = "Enable secure mode")
@@ -48,34 +61,43 @@ public class ServerCommand implements Command<CommandInvocation> {
4861

4962
@Override
5063
public CommandResult execute(CommandInvocation invocation) {
64+
if (secureMode) {
65+
invocation.println("Starting HTTPS with cert=" + certPath);
66+
} else {
67+
invocation.println("Starting HTTP server");
68+
}
5169
return CommandResult.SUCCESS;
5270
}
5371
}
5472
```
5573

74+
When `--secure` is not set, `--cert` and `--key` are hidden from tab completion and `--help` output.
75+
5676
## CommandActivator
5777

58-
Control whether a command is visible/available:
78+
Control whether an entire command is visible and available:
5979

6080
```java
81+
public interface CommandActivator {
82+
boolean isActivated(ParsedCommand command);
83+
}
84+
```
85+
86+
### Example: Admin-Only Commands
87+
88+
```java
89+
import org.aesh.command.activator.CommandActivator;
90+
import org.aesh.command.impl.internal.ParsedCommand;
91+
6192
public class AdminCommandActivator implements CommandActivator {
6293

6394
@Override
64-
public boolean isActivated(CommandInvocation invocation) {
65-
// Only show admin commands if user is admin
66-
return isAdmin(invocation);
67-
}
68-
69-
private boolean isAdmin(CommandInvocation invocation) {
70-
// Check if user has admin privileges
71-
// This could check environment variables, config files, etc.
72-
return System.getProperty("user.role", "user").equals("admin");
95+
public boolean isActivated(ParsedCommand command) {
96+
return "admin".equals(System.getProperty("user.role", "user"));
7397
}
7498
}
7599
```
76100

77-
Usage:
78-
79101
```java
80102
@CommandDefinition(
81103
name = "shutdown",
@@ -92,56 +114,47 @@ public class ShutdownCommand implements Command<CommandInvocation> {
92114
}
93115
```
94116

95-
## Conditional Validation
117+
The `shutdown` command only appears in tab completion and help when `user.role` is `admin`. Users without admin role cannot discover or execute it.
96118

97-
Use activators to conditionally require options:
119+
## Conditional Required Options
120+
121+
Combine activators with `required = true` to make options conditionally required:
98122

99123
```java
100124
public class OutputFileActivator implements OptionActivator {
101125

102126
@Override
103-
public boolean isActivated(CompleterInvocation invocation) {
104-
MyCommand cmd = (MyCommand) invocation.getCommand();
105-
// Output file option only active when not verbose
106-
return !cmd.verbose;
127+
public boolean isActivated(ParsedCommand parsedCommand) {
128+
// Output file only required when not in verbose mode
129+
return parsedCommand.findLongOptionNoActivatorCheck("verbose")
130+
.getValue() == null;
107131
}
108132
}
109133

110-
@CommandDefinition(name = "process")
134+
@CommandDefinition(name = "process", description = "Process data")
111135
public class ProcessCommand implements Command<CommandInvocation> {
112136

113-
@Option(name = "verbose", hasValue = false, description = "Verbose output")
137+
@Option(name = "verbose", hasValue = false, description = "Verbose output to console")
114138
private boolean verbose = false;
115139

116140
@Option(
117141
name = "output",
118142
activator = OutputFileActivator.class,
119143
required = true,
120-
description = "Output file (required unless verbose)"
144+
description = "Output file (required unless --verbose)"
121145
)
122146
private String outputFile;
123147

124148
@Override
125149
public CommandResult execute(CommandInvocation invocation) {
150+
if (verbose) {
151+
invocation.println("Processing... (verbose output)");
152+
} else {
153+
invocation.println("Writing output to " + outputFile);
154+
}
126155
return CommandResult.SUCCESS;
127156
}
128157
}
129158
```
130159

131-
When `--verbose` is used, the `--output` option is hidden and not required.
132-
133-
## Accessing Runtime Context
134-
135-
Activators can access the Aesh context:
136-
137-
```java
138-
public class ContextAwareActivator implements OptionActivator {
139-
140-
@Override
141-
public boolean isActivated(CompleterInvocation invocation) {
142-
AeshContext context = invocation.getAeshContext();
143-
// Access runtime context for more complex conditions
144-
return context.someCondition();
145-
}
146-
}
147-
```
160+
When `--verbose` is used, `--output` is deactivated and not required. Without `--verbose`, `--output` is required.

content/docs/aesh/command-invocation.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -842,8 +842,7 @@ public class MyApplication {
842842
.create();
843843

844844
// Configure settings with the custom invocation provider
845-
Settings<MyCommandInvocation, CommandInvocation,
846-
CommandInputProcessor, CommandProcessorContext, CommandOutput> settings =
845+
Settings<MyCommandInvocation> settings =
847846
SettingsBuilder.<MyCommandInvocation>builder()
848847
.commandRegistry(registry)
849848
.commandInvocationProvider(invocationProvider)

content/docs/aesh/converters.md

Lines changed: 94 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,105 @@ title: 'Converters'
55
weight: 10
66
---
77

8-
Converters transform string input into custom types.
8+
Converters transform the raw string input from the command line into the Java type your option or argument expects. They run automatically when Æsh populates your command object.
99

1010
## Converter Interface
1111

12-
Implement the `Converter<T>` interface:
12+
```java
13+
public interface Converter<T, C extends ConverterInvocation> {
14+
T convert(C converterInvocation) throws OptionValidatorException;
15+
}
16+
```
17+
18+
The `ConverterInvocation` provides:
19+
- `getInput()` -- the raw string value from the command line
20+
- `getAeshContext()` -- the current Æsh context
21+
22+
## Built-in Converters
23+
24+
Æsh converts these types automatically -- no converter needed:
25+
26+
- `String`
27+
- `int` / `Integer`, `long` / `Long`, `short` / `Short`, `byte` / `Byte`
28+
- `float` / `Float`, `double` / `Double`
29+
- `boolean` / `Boolean` (accepts `true`, `false`, `yes`, `no`)
30+
- `char` / `Character`
31+
- `File`, `Path`
32+
- Any `Enum` type (matched by name, case-insensitive)
33+
34+
## Enum Conversion
35+
36+
Enums work out of the box:
1337

1438
```java
15-
public interface Converter<T> {
16-
T convert(String input) throws ConverterException;
39+
public enum LogLevel {
40+
DEBUG, INFO, WARN, ERROR
41+
}
42+
43+
@CommandDefinition(name = "log", description = "Configure logging")
44+
public class LogCommand implements Command<CommandInvocation> {
45+
46+
@Option(description = "Log level")
47+
private LogLevel level = LogLevel.INFO;
48+
49+
@Override
50+
public CommandResult execute(CommandInvocation invocation) {
51+
invocation.println("Log level: " + level);
52+
return CommandResult.SUCCESS;
53+
}
1754
}
1855
```
1956

20-
## Custom Type Conversion
57+
```
58+
$ log --level DEBUG
59+
Log level: DEBUG
60+
```
61+
62+
Tab completion for enum values is also automatic.
63+
64+
## Custom Converters
2165

22-
Convert a string to a custom class:
66+
Write a converter when you need a type that isn't built in. Implement `Converter<T, ConverterInvocation>`:
2367

2468
```java
25-
public class DurationConverter implements Converter<Duration> {
69+
import org.aesh.command.converter.Converter;
70+
import org.aesh.command.converter.ConverterInvocation;
71+
import org.aesh.command.validator.OptionValidatorException;
72+
73+
import java.time.Duration;
74+
import java.util.regex.Matcher;
75+
import java.util.regex.Pattern;
76+
77+
public class DurationConverter implements Converter<Duration, ConverterInvocation> {
78+
79+
private static final Pattern PATTERN = Pattern.compile("(\\d+)([smhd])");
2680

2781
@Override
28-
public Duration convert(String input) throws ConverterException {
29-
try {
30-
String[] parts = input.split("(?<=\\D)(?=\\d)");
31-
long value = Long.parseLong(parts[0]);
32-
String unit = parts[1].toLowerCase();
33-
34-
switch (unit) {
35-
case "s": return Duration.ofSeconds(value);
36-
case "m": return Duration.ofMinutes(value);
37-
case "h": return Duration.ofHours(value);
38-
default: throw new ConverterException("Invalid unit: " + unit);
39-
}
40-
} catch (Exception e) {
41-
throw new ConverterException("Invalid duration: " + input +
42-
". Use format like: 30s, 5m, 2h");
82+
public Duration convert(ConverterInvocation invocation) throws OptionValidatorException {
83+
String input = invocation.getInput();
84+
Matcher matcher = PATTERN.matcher(input.toLowerCase());
85+
86+
if (!matcher.matches()) {
87+
throw new OptionValidatorException("Invalid duration: " + input +
88+
". Use format like: 30s, 5m, 2h, 1d");
89+
}
90+
91+
long value = Long.parseLong(matcher.group(1));
92+
switch (matcher.group(2)) {
93+
case "s": return Duration.ofSeconds(value);
94+
case "m": return Duration.ofMinutes(value);
95+
case "h": return Duration.ofHours(value);
96+
case "d": return Duration.ofDays(value);
97+
default: throw new OptionValidatorException("Unknown unit: " + matcher.group(2));
4398
}
4499
}
45100
}
46101
```
47102

48-
Usage:
103+
Use it with `@Option`:
49104

50105
```java
51-
@CommandDefinition(name = "timeout")
106+
@CommandDefinition(name = "timeout", description = "Set a timeout")
52107
public class TimeoutCommand implements Command<CommandInvocation> {
53108

54109
@Option(
@@ -60,47 +115,29 @@ public class TimeoutCommand implements Command<CommandInvocation> {
60115

61116
@Override
62117
public CommandResult execute(CommandInvocation invocation) {
63-
invocation.println("Timeout: " + duration.toSeconds() + " seconds");
118+
invocation.println("Timeout set to " + duration.toSeconds() + " seconds");
64119
return CommandResult.SUCCESS;
65120
}
66121
}
67122
```
68123

69-
Usage: `timeout --duration 5m`
70-
71-
## Built-in Converters
72-
73-
Æsh includes converters for common types:
74-
- `String`
75-
- `int`, `Integer`
76-
- `long`, `Long`
77-
- `float`, `Float`
78-
- `double`, `Double`
79-
- `boolean`, `Boolean`
80-
- `File`
81-
- `Path`
82-
83-
## Enum Conversion
124+
```
125+
$ timeout --duration 5m
126+
Timeout set to 300 seconds
127+
```
84128

85-
Enums are automatically converted:
129+
## Error Handling
86130

87-
```java
88-
public enum LogLevel {
89-
DEBUG, INFO, WARN, ERROR
90-
}
131+
When conversion fails, throw `OptionValidatorException` with a message describing what went wrong and what the user should provide instead. Æsh displays the message to the user and aborts command parsing:
91132

92-
@CommandDefinition(name = "log")
93-
public class LogCommand implements Command<CommandInvocation> {
133+
```
134+
$ timeout --duration abc
135+
Invalid duration: abc. Use format like: 30s, 5m, 2h, 1d
136+
```
94137

95-
@Option(description = "Log level")
96-
private LogLevel level = LogLevel.INFO;
138+
## Converter vs Validator
97139

98-
@Override
99-
public CommandResult execute(CommandInvocation invocation) {
100-
invocation.println("Log level: " + level);
101-
return CommandResult.SUCCESS;
102-
}
103-
}
104-
```
140+
- **Converters** transform the input string into a typed value. They answer: *"What does this string mean?"*
141+
- **Validators** check whether a value is acceptable. They answer: *"Is this value allowed?"*
105142

106-
Usage: `log --level DEBUG`
143+
Converters run first. If conversion succeeds, validators run next. Use a converter when you need a custom type; use a [Validator](../validators) when the type is standard but the allowed values are restricted (e.g., port must be 1-65535).

0 commit comments

Comments
 (0)