New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: Get CommandSpec for actual command #1839
Comments
@rsenden Apologies for the late reply, this dropped below my inbox horizon... About alternative approaches, one idea is to have a MIXEE spec in every Mixin: public class MyCommand {
@Mixin private MyTopLevelMixin myTopLevelMixin;
...
}
public class MyTopLevelMixin {
@Spec(Spec.Target.MIXEE) CommandSpec mixee;
@Mixin private MySubMixin mySubMixin;
...
}
public class MySubMixin {
@Spec(Spec.Target.MIXEE) private CommandSpec mixee;
...
} Then, from MySubMixin, one can have code like this: ((MyTopLevelMixin) mixee.userObject()).mixee; // gives the MyCommand commandSpec This in addition to your solution, which I am glad to hear also works. |
@remkop Thanks for the reply. Even though the Your approach is more explicit and less hacky, however it would be annoying having to add I think that accessing the |
I had not considered mixins nested in mixins, but I can see that it makes sense in your use case. Not sure how easy it will be to implement something like this... If the userObject of the MIXEE implements some interface, then couldn't the implementation of that interface could potentially take care of the single/multiple levels? The user manual MIXEE example has only a single level but perhaps gives some idea of using interfaces to solve the problem of 2-level vs 3-level nesting... |
For reference, I have now implemented a work-around as described below, for the following reasons:
The new work-around allows for injecting the public interface ICommandAware {
void setCommandSpec(CommandSpec commandSpec);
} public class AbstractCommand implements Runnable {
@Spec private CommandSpec commandSpec;
private boolean mixinsInitialized = false;
public void run() {
// We need to do this in the run method; at the time the CommandSpec is being injected
// (through field above or setter method), the mixins haven't been initialized yet
initMixins();
// Do other work
}
protected final void initMixins() {
if ( !mixinsInitialized ) {
initMixins(commandSpec, commandSpec.mixins());
mixinsInitialized = true;
}
}
private void initMixins(CommandSpec commandSpec, Map<String, CommandSpec> mixins) {
if ( mixins != null ) {
for ( CommandSpec mixin : mixins.values() ) {
Object userObject = mixin.userObject();
if ( userObject!=null && userObject instanceof ICommandAware) {
((ICommandAware)userObject).setCommandSpec(commandSpec);
}
initMixins(commandSpec, mixin.mixins());
}
}
}
} Although mixins can implement the @Command
public final class CommandHelperMixin implements ICommandAware {
@Getter private CommandSpec commandSpec;
@Getter private IMessageResolver messageResolver;
public final void setCommandSpec(CommandSpec commandSpec) {
this.commandSpec = commandSpec;
this.messageResolver = new CommandSpecMessageResolver(commandSpec);
}
/**
* Utility method for retrieving the command being invoked as the given
* type, returning null if the command is not an instance of the given
* type.
*/
public final <T> T getCommandAs(Class<T> asType) {
return getAs(getCommand(), asType);
}
/**
* Utility method for retrieving the command instance.
* @return
*/
public final Object getCommand() {
return commandSpec.userObject();
}
/**
* Utility method for getting the given object as the given type,
* returning null if the given object is not an instance of the
* given type.
*
* TODO This is potentially a reusable method; consider moving elsewhere.
* @param <T>
* @param obj
* @param asType
* @return
*/
@SuppressWarnings("unchecked")
public final static <T> T getAs(Object obj, Class<T> asType) {
if ( obj!=null && asType.isAssignableFrom(obj.getClass()) ) {
return (T)obj;
}
return null;
}
} This allows mixin implementations to declare the public final class RequireConfirmation {
@Mixin private CommandHelperMixin commandHelper; // This is where we declare the mixin mentioned above
@Option(names = {"-y", "--confirm"}, defaultValue = "false")
private boolean confirmed;
public void checkConfirmed() {
if (!confirmed) {
// Get the CommandSpec of the actual command from CommandHelperMixin
CommandSpec spec = commandHelper.getCommandSpec();
if ( System.console()==null ) {
throw new ParameterException(spec.commandLine(), "Missing option: Confirm operation with -y / --confirm (interactive prompt not available)");
} else {
String expectedResponse = PicocliSpecHelper.getRequiredMessageString(spec, "expectedConfirmPromptResponse");
String response = System.console().readLine(getPrompt());
if ( response.equalsIgnoreCase(expectedResponse) ) {
return;
} else {
throw new IllegalStateException("Aborting: operation aborted by user");
}
}
}
}
private String getPrompt() {
// Get the CommandSpec of the actual command from CommandHelperMixin
CommandSpec spec = commandHelper.getCommandSpec();
String prompt = PicocliSpecHelper.getMessageString(spec, "confirmPrompt");
if ( StringUtils.isBlank(prompt) ) {
String[] descriptionLines = spec.optionsMap().get("-y").description();
if ( descriptionLines==null || descriptionLines.length<1 ) {
throw new RuntimeException("No proper description found for generating prompt for --confirm option");
}
prompt = spec.optionsMap().get("-y").description()[0]+"?";
}
String promptOptions = PicocliSpecHelper.getRequiredMessageString(spec, "confirmPromptOptions");
return String.format("%s (%s) ", prompt, promptOptions);
}
} Of course, it would be great if at some point, picocli can provide native support for injecting the |
Assume code like the following:
It seems like the
CommandSpec
injected intoMySubMixin
representsMyTopLevelMixin
, rather thanMyCommand
. This seems logical, asMySubMixin
is mixed intoMyTopLevelMixin
and not directly intoMyCommand
. However, what ifMySubMixin
requires theCommandSpec
forMyCommand
instead?For now, I'm using the following code to retrieve the
CommandSpec
of the actual command being invoked; this seems to work fine but I'm wondering whether this is the best approach?The null-check is necessary as picocli seems to inject the
Mixins
for all commands (even if they are not currently being executed), whereasmixee.commandLine()
only returns a CommandLine instance for the currently executing command.Are there any better approaches for injecting/retrieving the
CommandSpec
for the actual command, bypassing any intermediateMixins
? How about introducing aSpec.Target.COMMAND
that injects theCommandSpec
for the actual command?The text was updated successfully, but these errors were encountered: