remkop / picocli

Picocli is a modern framework for building powerful, user-friendly, GraalVM-enabled command line apps with ease. It supports colors, autocompletion, subcommands, and more. In 1 source file so apps can include as source & avoid adding a dependency. Written in Java, usable from Groovy, Kotlin, Scala, etc.
https://picocli.info
Apache License 2.0
4.94k stars 424 forks source link

Mixin-with-prefix capability? #2310

Open alexturc opened 5 months ago

alexturc commented 5 months ago

Discussed in https://github.com/remkop/picocli/discussions/1808

Originally posted by **rcauble** September 11, 2022 Hi, I have a use case where I have 2 commands with each their own set of settings: ``` class Command1Settings { @CommandLine.Option(names = "--foo") private String foo; @CommandLine.Option(names = "--bar") private String bar; } @CommandLine.Command(name = "command1") public static final class Command1 { @CommandLine.Mixin private Command1Settings settings = new Command1Settings(); public void run() { String results1 = Command1Runner.run(settings); System.out.println(results1); } } class Command2Settings { @CommandLine.Option(names = "--foo") private String foo; @CommandLine.Option(names = "--bar") private String bar; } @CommandLine.Command(name = "command2") public static final class Command2 { @CommandLine.Mixin private Command2Settings settings = new Command2Settings(); public void run() { String results2 = Command2Runner.run(settings); System.out.println(results2); } } ``` And I am trying to create a 3rd command that computes a diff of the other 2. As you can see above, though there is overlap in the options of the 2 commands and so I'm looking for a MixinWithPrefix capability so that I can do something like this: ``` @CommandLine.Command(name = "diff") public static final class Diff { @CommandLine.MixinWithPrefix("first-") private Command1Settings settings1 = new Command1Settings(); @CommandLine.MixinWithPrefix("second-") private Command1Settings settings2 = new Command2Settings(); public void run() { String results1 = Command1Runner.run(settings1); String results2 = Command2Runner.run(settings2); System.out.println(diff(results1, results2)); } } ``` And then this will define a new command where the user can do: ``` diff --first-foo thing1 --first-bar thing2 --second-foo thing3 --second-bar thing4 ``` To add an additional constraint for context, Command1 and Command2 live in different libraries from the differ and so I can't simply arrange for the names to be distinct as they are not necessarily owned by me. Any suggestions?
remkop commented 4 months ago

@alexturc I see the PR for this feature (#2311) but to be honest I don't like the idea...

Note that there is already a generic mechanism to transform the model: https://picocli.info/#_model_transformations

If that mechanism has limitations so that it cannot be used for this use case I would rather improve the existing generic mechanism rather than introduce another more narrow new mechanism.

alexturc commented 4 months ago

Hi @remkop,

Mixins are a great way of grouping related options such that complex commands can be built while still being able to manage complexity. But a feature which I'm facing quite often and became a burden is inability of reuse mixins in the same command, as option names would clash.

A typical example is for database connections (but I encounter multiple such situations), where a mixin would be

public class DbMixin {
  @Option(name = "--url")
  String url;
  @Option(name = "--user")
  String user;
  @Option(name = "--pass")
  String pass;
}

An application working with multiple DB connections would reuse the above class like this:

@Command
public class DbMixin {
  @Mixin
  DbMixin firstDbMixin = new DbMixin();
  @Mixin
  DbMixin secondDbMixin = new DbMixin();
  @Mixin
  DbMixin thirdDbMixin = new DbMixin();
}

As of now this would not work as option names coming from different DbMixin instances would class an building the command line would fail. And it would fail before having the chance to apply model transformations as you suggested - because the are applied last, after an initial version of the model was build.

The goal here would be to add a prefix to option names originating from the three mixins such that we can successfully build a command line while reusing DbMxin class. --url option from firstDbMixin could become --firstDb.url or --firstDbUrl etc.

The solution int he PR keeps the mapping logic at mixin declaration, so I think it has good developer ergonomics - one can see immediately how option names are transformed.

But I can work with IModelTransformer as well if you would be ok to have @Mixin annotation take a model transformer and apply it immediately after the mixin is built. In practice I intend the have a transformer which would take the name of the mixin and and use it as a prefix for option names.

Would this be acceptable? If yes, would you prefer for @Mixin to take an array of transformers or just one, like @Command does?

alexturc commented 3 months ago

I sent another PR which uses IModelTransformer with mixins. This alows me to make more changes actually, including prefixing the environment variables from where the default values would be taken by default. Please let me know if this acceptable.

remkop commented 3 months ago

Hi! Thanks for the PR!I’m currently traveling with limited access to internet. I’ll take a look when I get back. On Aug 17, 2024, at 1:08, alexturc @.***> wrote: I sent another PR which uses IModelTransformer with mixins. This alows me to make more changes actually, including prefixing the environment variables from where the default values would be taken by default. Please let me know if this acceptable.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>