From 8487a7efc370d8df8a9bf03b1b52c90b93e9a7ac Mon Sep 17 00:00:00 2001 From: Alex Turc Date: Fri, 16 Aug 2024 10:58:52 -0600 Subject: [PATCH 1/2] Added support for using model transformers with mixinx. --- .../annotation/processing/TypedMember.java | 9 ++++ src/main/java/picocli/CommandLine.java | 22 +++++++++ src/test/java/picocli/MixinTest.java | 49 +++++++++++++++++++ src/test/java/picocli/ModelArgSpecTest.java | 1 + 4 files changed, 81 insertions(+) diff --git a/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/TypedMember.java b/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/TypedMember.java index 84f2d3231..897139682 100644 --- a/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/TypedMember.java +++ b/picocli-codegen/src/main/java/picocli/codegen/annotation/processing/TypedMember.java @@ -114,6 +114,15 @@ public String getMixinName() { return empty(annotationName) ? getName() : annotationName; } + @Override + public Class getModelTransformer() { + if (isMixin()) { + return getAnnotation(CommandLine.Mixin.class).modelTransformer(); + } else { + return null; + } + } + static String propertyName(String methodName) { if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) { return decapitalize(methodName.substring(3)); } return decapitalize(methodName); diff --git a/src/main/java/picocli/CommandLine.java b/src/main/java/picocli/CommandLine.java index 87de0855b..ae0c1350e 100644 --- a/src/main/java/picocli/CommandLine.java +++ b/src/main/java/picocli/CommandLine.java @@ -4467,6 +4467,10 @@ public enum ScopeType { * If not specified the name of the annotated field is used. * @return a String to register the mixin object with, or an empty String if the name of the annotated field should be used */ String name() default ""; + + /** Returns the model transformer for this mixin. + **/ + Class modelTransformer() default NoOpModelTransformer.class; } /** * Fields annotated with {@code @Spec} will be initialized with the {@code CommandSpec} for the command the field is part of. Example usage: @@ -7464,6 +7468,7 @@ public void updateCommandAttributes(Command cmd, IFactory factory) { void initAliases(String[] aliases) { if (aliases != null) { this.aliases.addAll(Arrays.asList(aliases));}} void initName(String value) { if (initializable(name, value, DEFAULT_COMMAND_NAME)) {name = value;} } + void resetName() {name = null;} void initHelpCommand(boolean value) { if (initializable(isHelpCommand, value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} } void initVersion(String[] value) { if (initializable(version, value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} } void initVersionProvider(IVersionProvider value) { if (versionProvider == null) { versionProvider = value; } } @@ -11347,6 +11352,7 @@ public interface IAnnotatedElement { T getAnnotation(Class annotationClass); String getName(); String getMixinName(); + Class getModelTransformer(); boolean isArgSpec(); boolean isOption(); boolean isParameter(); @@ -11539,6 +11545,13 @@ public String getMixinName() { String annotationName = getAnnotation(Mixin.class).name(); return empty(annotationName) ? getName() : annotationName; } + public Class getModelTransformer() { + if (isMixin()) { + return getAnnotation(Mixin.class).modelTransformer(); + } else { + return NoOpModelTransformer.class; + } + } static String propertyName(String methodName) { if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) { return decapitalize(methodName.substring(3)); } return decapitalize(methodName); @@ -12058,6 +12071,15 @@ private static CommandSpec buildMixinForMember(IAnnotatedElement member, IFactor member.setter().set(userObject); } CommandSpec result = CommandSpec.forAnnotatedObject(userObject, factory); + IModelTransformer modelTransformer = DefaultFactory.create(factory, member.getModelTransformer()); + String oldName = result.name; + result.initName(member.getMixinName()); + result = modelTransformer.transform(result); + if (oldName != null && !oldName.equals(CommandSpec.DEFAULT_COMMAND_NAME)) { + result.initName(oldName); + } else { + result.resetName(); + } return result.withToString(member.getToString()); } catch (InitializationException ex) { throw ex; diff --git a/src/test/java/picocli/MixinTest.java b/src/test/java/picocli/MixinTest.java index 9a4e44e2d..4a053dd3a 100644 --- a/src/test/java/picocli/MixinTest.java +++ b/src/test/java/picocli/MixinTest.java @@ -27,6 +27,8 @@ import java.io.File; import java.io.PrintStream; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import static org.junit.Assert.*; @@ -1100,4 +1102,51 @@ public void testIssue1836CommandAliasOnMixin() { Help help = new Help(new App_Issue1836()); assertEquals("list, ls", help.commandList().trim()); } + + public static class MixinReuseModeTransformer implements IModelTransformer { + @Override + public CommandSpec transform(CommandSpec commandSpec) { + String prefix = commandSpec.name(); + ArrayList options = new ArrayList(commandSpec.options()); + for (OptionSpec option : options) { + commandSpec.remove(option); + OptionSpec.Builder optionBuilder = option.toBuilder(); + String[] names = optionBuilder.names(); + String[] newNames = new String[names.length]; + for (int i = 0; i < names.length; i++) { + String name = names[i]; + String newName = "--" + prefix + name.substring(2, 3).toUpperCase() + name.substring(3); + newNames[i] = newName; + } + optionBuilder.names(newNames); + String defaultValue = optionBuilder.defaultValue(); + if (defaultValue.startsWith("${env:")) { + optionBuilder.defaultValue("${env:" + prefix.toUpperCase() + "_" + defaultValue.substring(6, defaultValue.length() - 1).toUpperCase() + "}"); + } + commandSpec.add(optionBuilder.build()); + } + return commandSpec; + } + } + + @Test + public void testModelTransformation() { + class ReusableMixin { + @Option(names = "--url", defaultValue = "${env:URL}") + String url; + } + class Application { + @Mixin(modelTransformer = MixinReuseModeTransformer.class) + ReusableMixin first; + @Mixin(modelTransformer = MixinReuseModeTransformer.class) + ReusableMixin second; + } + Application application = new Application(); + CommandLine commandLine = new CommandLine(application, new InnerClassFactory(this)); + List options = commandLine.getCommandSpec().options(); + assertArrayEquals(new String[]{"--firstUrl"}, options.get(0).names()); + assertEquals("${env:FIRST_URL}", options.get(0).defaultValueString()); + assertArrayEquals(new String[]{"--secondUrl"}, options.get(1).names()); + assertEquals("${env:SECOND_URL}", options.get(1).defaultValueString()); + } } diff --git a/src/test/java/picocli/ModelArgSpecTest.java b/src/test/java/picocli/ModelArgSpecTest.java index 58988cd33..32f0671b8 100644 --- a/src/test/java/picocli/ModelArgSpecTest.java +++ b/src/test/java/picocli/ModelArgSpecTest.java @@ -243,6 +243,7 @@ private static class AnnotatedImpl implements CommandLine.Model.IAnnotatedElemen public T getAnnotation(Class annotationClass) { return null;} public String getName() {return name;} public String getMixinName() {return null;} + public Class getModelTransformer() { return null; } public boolean isArgSpec() {return false;} public boolean isOption() {return false;} public boolean isParameter() {return false;} From 3b47fe895e899a67b1676c50af8c316adc047c93 Mon Sep 17 00:00:00 2001 From: Alex Turc Date: Fri, 16 Aug 2024 11:12:48 -0600 Subject: [PATCH 2/2] Fixed typo. --- src/test/java/picocli/MixinTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/picocli/MixinTest.java b/src/test/java/picocli/MixinTest.java index 4a053dd3a..7446db670 100644 --- a/src/test/java/picocli/MixinTest.java +++ b/src/test/java/picocli/MixinTest.java @@ -1103,7 +1103,7 @@ public void testIssue1836CommandAliasOnMixin() { assertEquals("list, ls", help.commandList().trim()); } - public static class MixinReuseModeTransformer implements IModelTransformer { + public static class MixinReuseModelTransformer implements IModelTransformer { @Override public CommandSpec transform(CommandSpec commandSpec) { String prefix = commandSpec.name(); @@ -1136,9 +1136,9 @@ class ReusableMixin { String url; } class Application { - @Mixin(modelTransformer = MixinReuseModeTransformer.class) + @Mixin(modelTransformer = MixinReuseModelTransformer.class) ReusableMixin first; - @Mixin(modelTransformer = MixinReuseModeTransformer.class) + @Mixin(modelTransformer = MixinReuseModelTransformer.class) ReusableMixin second; } Application application = new Application();