Skip to content

Commit b0397e1

Browse files
committed
[#1259][#1266] DOC for IModelTransformer
1 parent 0cd5d8d commit b0397e1

File tree

3 files changed

+108
-26
lines changed

3 files changed

+108
-26
lines changed

RELEASE-NOTES.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ This release contains new features, bug fixes and other enhancements.
88

99
## Community Contributions
1010

11-
* [Andreas Deininger](https://github.com/deining) has been contributing to the documentation and other areas for a while, but recently went into overdrive :-) and contributed many, many new pull requests to improve the documentation. The user manual and Quick Guide now have a "foldable" table of contents, and examples in tabs, with many additional examples in Kotlin, Scala and Groovy. A lot of work went into this! Many thanks, Andreas!
12-
* [Sualeh Fatehi](https://github.com/sualeh) contributed a pull request to `picocli-shell-jline3` that adds a built-in `clear` command and improves the `help` command.
11+
* [Andreas Deininger](https://github.com/deining) has been contributing to the documentation and other areas for a long time, but recently went into overdrive :-) and contributed many, many new pull requests to improve the documentation. The user manual and Quick Guide now have a "foldable" table of contents, and examples in tabs, with many additional examples in Kotlin, Scala and Groovy. A lot of work went into this! Many thanks, Andreas!
12+
* [Marko Mackic](https://github.com/MarkoMackic) contributed a pull request to add `IModelTransformer` API for user-defined model transformations after initialization and before parsing.
13+
* [Sualeh Fatehi](https://github.com/sualeh) contributed a pull request to the `picocli-shell-jline3` module that adds a built-in `clear` command and improves the `help` command.
1314
* [Daniel Gray](https://github.com/danielthegray) contributed a bug fix to prevent incorrectly defaulting inherited positional params after a subcommand.
1415
* [nveeser-google](https://github.com/nveeser-google) contributed a fix for compiler warnings about `Annotation::getClass` and assignment in `if` condition.
1516
* [Petr Hála](https://github.com/pehala) contributed a pull request to add a section on Mocking to user manual.
@@ -57,6 +58,7 @@ Picocli follows [semantic versioning](http://semver.org/).
5758
* [System Properties](#4.6.0-system-properties)
5859
* [`java.util.Optional<T>`](#4.6.0-java-util-optional)
5960
* [Inherited Command Attributes](#4.6.0-inherited-command-attributes)
61+
* [Model Transformations](#4.6.0-model-transformations)
6062
* [Fixed issues](#4.6.0-fixes)
6163
* [Deprecations](#4.6.0-deprecated)
6264
* [Potential breaking changes](#4.6.0-breaking-changes)
@@ -251,11 +253,37 @@ Attributes that are _not_ copied include:
251253
* subcommands
252254
* argument groups
253255

256+
### <a name="4.6.0-model-transformations"></a> Model Transformations
257+
From picocli 4.6, it is possible to use the annotations API to modify the model (commands, options, subcommands, etc.) dynamically at runtime.
258+
The `@Command` annotation now has a `modelTransformer` attribute where applications can specify a class that implements the `IModelTransformer` interface:
259+
260+
This allows applications to dynamically add or remove options, positional parameters or subcommands, or modify the command in any other way, based on some runtime condition.
261+
262+
```java
263+
@Command(modelTransformer = Dynamic.SubCmdFilter.class)
264+
class Dynamic {
265+
266+
private static class SubCmdFilter implements IModelTransformer {
267+
public CommandSpec transform(CommandSpec commandSpec) {
268+
if (Boolean.getBoolean("disable_sub")) {
269+
commandSpec.removeSubcommand("sub");
270+
}
271+
return commandSpec;
272+
}
273+
}
274+
275+
@Command
276+
private void sub() {
277+
// subcommand business logic
278+
}
279+
}
280+
```
254281

255282
## <a name="4.6.0-fixes"></a> Fixed issues
256283
* [#1164] API: Add support for `@Command(scope=INHERIT)`. Thanks to [Nick Cross](https://github.com/rnc) for raising this.
257284
* [#1191] API: Add `@PicocliScript2` annotation to support subcommand methods in Groovy scripts. Thanks to [Mattias Andersson](https://github.com/attiand) for raising this.
258285
* [#1241] API: Add `mapFallbackValue` attribute to `@Options` and `@Parameters` annotations, and corresponding `ArgSpec.mapFallbackValue()`.
286+
* [#1259][#1266] API: Add `IModelTransformer` to support user-defined model transformations after initialization and before parsing. Thanks to [Marko Mackic](https://github.com/MarkoMackic) for the pull request.
259287
* [#1184] API: Added public methods `Help.Layout::colorScheme`, `Help.Layout::textTable`, `Help.Layout::optionRenderer`, `Help.Layout::parameterRenderer`, and `Help::calcLongOptionColumnWidth`.
260288
* [#1254] API: Added `ArgSpec::root`: this method returns the original `ArgSpec` for inherited `ArgSpec` objects, and `null` for other `ArgSpec` objects. Thanks to [Daniel Gray](https://github.com/danielthegray) for the pull request.
261289
* [#1256] API: Added `CommandSpec::removeSubcommand` method. Thanks to [Marko Mackic](https://github.com/MarkoMackic) for raising this.

docs/index.adoc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9894,6 +9894,53 @@ override fun <K : Any> create(clazz: Class<K>): K {
98949894
}
98959895
----
98969896

9897+
=== Model Transformations
9898+
From picocli 4.6, it is possible to use the annotations API to modify the model (commands, options, subcommands, etc.) dynamically at runtime.
9899+
The `@Command` annotation now has a `modelTransformer` attribute where applications can specify a class that implements the `IModelTransformer` interface:
9900+
9901+
[source,java]
9902+
----
9903+
interface IModelTransformer {
9904+
/**
9905+
* Given an original CommandSpec, return the object that should be used
9906+
* instead. Implementors may modify the specified CommandSpec and return it,
9907+
* or create a full or partial copy of the specified CommandSpec, and return
9908+
* that, or even return a completely new CommandSpec.
9909+
* <p>
9910+
* Implementors are free to add or remove options, positional parameters,
9911+
* subcommands or modify the command in any other way.
9912+
* </p><p>
9913+
* This method is called once, after the full command hierarchy is
9914+
* constructed, and before any command line arguments are parsed.
9915+
* </p>
9916+
* @return the CommandSpec to use instead of the specified one
9917+
*/
9918+
CommandSpec transform(CommandSpec commandSpec);
9919+
}
9920+
----
9921+
9922+
This allows applications to dynamically add or remove options, positional parameters or subcommands, or modify the command in any other way, based on some runtime condition.
9923+
9924+
[source,java]
9925+
----
9926+
@Command(modelTransformer = Dynamic.SubCmdFilter.class)
9927+
class Dynamic {
9928+
9929+
private static class SubCmdFilter implements IModelTransformer {
9930+
public CommandSpec transform(CommandSpec commandSpec) {
9931+
if (Boolean.getBoolean("disable_sub")) {
9932+
commandSpec.removeSubcommand("sub");
9933+
}
9934+
return commandSpec;
9935+
}
9936+
}
9937+
9938+
@Command
9939+
private void sub() {
9940+
// subcommand business logic
9941+
}
9942+
}
9943+
----
98979944

98989945
=== Automatic Parameter Indexes
98999946
==== Automatic Indexes

src/main/java/picocli/CommandLine.java

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@ private CommandLine(Object command, IFactory factory, boolean userCalled) {
231231
if (commandSpec.unmatchedArgsBindings().size() > 0) { setUnmatchedArgumentsAllowed(true); }
232232
}
233233

234+
/** Apply transformers to command spec recursively. */
235+
private void applyModelTransformations() {
236+
if (commandSpec.modelTransformer != null) {
237+
commandSpec = commandSpec.modelTransformer.transform(commandSpec);
238+
}
239+
for (CommandLine cmd : getSubcommands().values()) {
240+
cmd.applyModelTransformations();
241+
}
242+
}
243+
234244
private CommandLine copy() {
235245
CommandLine result = new CommandLine(commandSpec.copy(), factory); // create a new sub-hierarchy
236246
result.err = err;
@@ -246,19 +256,6 @@ private CommandLine copy() {
246256
return result;
247257
}
248258

249-
250-
/**
251-
* Apply transformers to command spec recursively
252-
*/
253-
private void applyModelTransformations() {
254-
if (this.commandSpec.modelTransformer != null) {
255-
this.commandSpec = this.commandSpec.modelTransformer.transform(this.commandSpec);
256-
}
257-
for (CommandLine cmd : this.getSubcommands().values()) {
258-
cmd.applyModelTransformations();
259-
}
260-
}
261-
262259
/**
263260
* Returns the {@code CommandSpec} model that this {@code CommandLine} was constructed with.
264261
* @return the {@code CommandSpec} model
@@ -4685,7 +4682,7 @@ enum Target {
46854682
ScopeType scope() default ScopeType.LOCAL;
46864683

46874684

4688-
/** Returns transformer for command
4685+
/** Returns the model transformer for this command.
46894686
* @since 4.6 */
46904687
Class<? extends IModelTransformer> modelTransformer() default NoOpModelTransformer.class;
46914688
}
@@ -4835,16 +4832,26 @@ private static class NoVersionProvider implements IVersionProvider {
48354832
* {@link Command#modelTransformer()} annotation attribute, or via the
48364833
* {@link CommandSpec#modelTransformer(IModelTransformer)} programmatic API.
48374834
* <p>
4838-
* The transformers are invoked only once, after the full command hierarchy is constructed.
4835+
* Model transformers are invoked only once, after the full command hierarchy is constructed.
48394836
* @since 4.6
48404837
*/
4841-
public interface IModelTransformer {
4842-
/**
4843-
* Returns CommandSpec after doing transformation.
4844-
* @return transformed CommandSpec
4845-
*/
4846-
CommandSpec transform(CommandSpec commandSpec);
4847-
}
4838+
public interface IModelTransformer {
4839+
/**
4840+
* Given an original CommandSpec, return the object that should be used
4841+
* instead. Implementors may modify the specified CommandSpec and return it,
4842+
* or create a full or partial copy of the specified CommandSpec, and return
4843+
* that, or even return a completely new CommandSpec.
4844+
* <p>
4845+
* Implementors are free to add or remove options, positional parameters,
4846+
* subcommands or modify the command in any other way.
4847+
* </p><p>
4848+
* This method is called once, after the full command hierarchy is
4849+
* constructed, and before any command line arguments are parsed.
4850+
* </p>
4851+
* @return the CommandSpec to use instead of the specified one
4852+
*/
4853+
CommandSpec transform(CommandSpec commandSpec);
4854+
}
48484855

48494856
private static class NoOpModelTransformer implements IModelTransformer {
48504857
public CommandSpec transform(CommandSpec commandSpec) { return commandSpec; }
@@ -6919,11 +6926,11 @@ public CommandSpec scopeType(ScopeType scopeType) {
69196926
return this;
69206927
}
69216928

6922-
/** Returns the model transformer for this CommandSpec instance
6929+
/** Returns the model transformer for this CommandSpec instance.
69236930
* @since 4.6 */
69246931
public IModelTransformer modelTransformer() { return modelTransformer; }
69256932

6926-
/** Sets the model transformer for the CommandSpec instance
6933+
/** Sets the model transformer for the CommandSpec instance.
69276934
* @since 4.6 */
69286935
public CommandSpec modelTransformer(IModelTransformer modelTransformer) { this.modelTransformer = modelTransformer; return this; }
69296936

0 commit comments

Comments
 (0)