Skip to content
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

[https://github.com/remkop/picocli/issues/1706] Resolve messages against parent resource bundles with updated i18n unit tests #1710

Merged
merged 1 commit into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import picocli.CommandLine.Help.Ansi.IStyle;
import picocli.CommandLine.Help.Ansi.Style;
import picocli.CommandLine.Help.Ansi.Text;
Expand Down Expand Up @@ -11558,6 +11559,7 @@ public static class Messages {
private final String bundleBaseName;
private final ResourceBundle rb;
private final Set<String> keys;
private Messages parent;
public Messages(CommandSpec spec, String baseName) {
this(spec, baseName, createBundle(baseName));
}
Expand All @@ -11581,6 +11583,21 @@ private static String extractName(ResourceBundle rb) {
return (String) ResourceBundle.class.getDeclaredMethod("getBaseBundleName").invoke(rb);
} catch (Exception ignored) { return "?"; }
}
public Messages parent() {
CommandSpec parentSpec = this.spec.parent();
if (parent == null || parent.spec != parentSpec) {
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
parent = null; // Refresh if parentSpec doesn't match
while (parent == null && parentSpec != null) {
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
String parentResourceBundleBaseName = parentSpec.resourceBundleBaseName();
if (parentResourceBundleBaseName != null && !parentResourceBundleBaseName.equals(this.bundleBaseName)) {
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
parent = new Messages(parentSpec, parentResourceBundleBaseName);
} else {
parentSpec = parentSpec.parent();
}
}
}
return parent;
}
private static Set<String> keys(ResourceBundle rb) {
if (rb == null) { return Collections.emptySet(); }
Set<String> keys = new LinkedHashSet<String>();
Expand All @@ -11596,8 +11613,8 @@ private static Set<String> keys(ResourceBundle rb) {
public static Messages copy(CommandSpec spec, Messages original) {
return original == null ? null : new Messages(spec, original.bundleBaseName, original.rb);
}
/** Returns {@code true} if the specified {@code Messages} is {@code null} or has a {@code null ResourceBundle}. */
public static boolean empty(Messages messages) { return messages == null || messages.rb == null; }
/** Returns {@code true} if the specified {@code Messages} is {@code null}, has a {@code null ResourceBundle}, or has a {@code null parent Messages}. */
public static boolean empty(Messages messages) { return messages == null || messages.isEmpty(); }

/** Returns the String value found in the resource bundle for the specified key, or the specified default value if not found.
* @param key unqualified resource bundle key. This method will first try to find a value by qualifying the key with the command's fully qualified name,
Expand All @@ -11608,12 +11625,19 @@ public static Messages copy(CommandSpec spec, Messages original) {
public String getString(String key, String defaultValue) {
if (isEmpty()) { return defaultValue; }
String cmd = spec.qualifiedName(".");
if (keys.contains(cmd + "." + key)) { return rb.getString(cmd + "." + key); }
if (keys.contains(key)) { return rb.getString(key); }
return defaultValue;
String qualifiedKey = cmd + "." + key;
String result = getStringForExactKey(qualifiedKey);
if (result == null) { result = getStringForExactKey(key); }
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
return result != null ? result : defaultValue;
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
}

boolean isEmpty() { return rb == null || keys.isEmpty(); }
private String getStringForExactKey(String key) {
if (keys.contains(key)) { return rb.getString(key); }
else if (parent() != null) { return parent().getStringForExactKey(key); }
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
else { return null; }
}

boolean isEmpty() { return (rb == null || keys.isEmpty()) && (parent() == null || parent().isEmpty()); }
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved

/** Returns the String array value found in the resource bundle for the specified key, or the specified default value if not found.
* Multi-line strings can be specified in the resource bundle with {@code key.0}, {@code key.1}, {@code key.2}, etc.
Expand All @@ -11625,10 +11649,15 @@ public String getString(String key, String defaultValue) {
public String[] getStringArray(String key, String[] defaultValues) {
if (isEmpty()) { return defaultValues; }
String cmd = spec.qualifiedName(".");
List<String> result = addAllWithPrefix(rb, cmd + "." + key, keys, new ArrayList<String>());
if (!result.isEmpty()) { return result.toArray(new String[0]); }
addAllWithPrefix(rb, key, keys, result);
return result.isEmpty() ? defaultValues : result.toArray(new String[0]);
String qualifiedKey = cmd + "." + key;
String[] result = getStringArrayForExactKey(qualifiedKey);
if (result == null) { result = getStringArrayForExactKey(key); }
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
return result != null ? result : defaultValues;
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
}
private String[] getStringArrayForExactKey(String key) {
List<String> result = addAllWithPrefix(rb, key, keys, new ArrayList<String>());
if (!result.isEmpty()) { return result.toArray(new String[0]); }
return parent() == null ? null : parent().getStringArrayForExactKey(key);
MikeTheSnowman marked this conversation as resolved.
Show resolved Hide resolved
}
private static List<String> addAllWithPrefix(ResourceBundle rb, String key, Set<String> keys, List<String> result) {
if (keys.contains(key)) { result.add(rb.getString(key)); }
Expand Down Expand Up @@ -18236,7 +18265,7 @@ private Tracer() {}
/** Returns whether the current trace level is WARN or higher. */
public boolean isWarn() { return level.isEnabled(TraceLevel.WARN); }
/** Returns whether the current trace level is OFF (the lowest). */
public boolean isOff() { return level== TraceLevel.OFF; }
public boolean isOff() { return level == TraceLevel.OFF; }
/** Prints the specified message if the current trace level is WARN or higher.
* @param msg the message to print; may use {@link String#format(String, Object...)} syntax
* @param params Arguments referenced by the format specifiers in the format string. If there are more arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero.
Expand Down
10 changes: 9 additions & 1 deletion src/test/java/picocli/I18nCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Mixin;

@Command(name = "i18n-top",
resourceBundle = "picocli.SharedMessages",
subcommands = {CommandLine.HelpCommand.class, I18nSubcommand.class },
subcommands = {CommandLine.HelpCommand.class, I18nSubcommand.class, I18nSubcommand2.class },
mixinStandardHelpOptions = true,
description = {"top desc 1", "top desc 2", "top desc 3"},
descriptionHeading = "top desc heading%n",
Expand All @@ -33,6 +34,13 @@ public class I18nCommand {
@Parameters(index = "1", description = "top param1 description")
String param1;

@Option(
names = {"--optionWithDescriptionFromParent"},
descriptionKey = "optionWithDescriptionFromParent",
scope = CommandLine.ScopeType.INHERIT
)
public boolean parentOption1;

@Override
public String toString() {
return getClass().getName();
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/picocli/I18nSubcommand2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package picocli;

import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Mixin;

/**
* This command was created to test Issue #1706.
* We are testing here to ensure that the description for a shared Mixin option, where the description for that option
* is defined in a parent command's resource bundle, can still be displayed correctly by a subcommand that's using a
* resource bundle that doesn't have that Mixin's option description defined. Ideally, if the subcommand's resource
* bundle doesn't have the needed description key, picocli SHOULD check the resource bundle of the parent command to see
* if the description key exists.
*/
@Command(name = "sub2",
subcommands = CommandLine.HelpCommand.class,
mixinStandardHelpOptions = true,
resourceBundle = "picocli.ResourceBundlePropagationTest")
public class I18nSubcommand2 {
@Option(names = {"-a", "--aaa"}, descriptionKey = "additional.value")
String a;

// There's an option, --optionWithDescriptionFromParent, which is inherited from the parent command with description

@Option(names = {"-x"}) // Description should come from Parent command's resource bundle
String x;

@Option(names = {"-q"}) // Description is in both parent & sub command resource bundles, but subcommand's should take priority
String q;
}
Loading