#1055 Grouping and sorting of text messages (PR #198)

* Initial grouping of messages_en.yml (thanks to input by @Twonox)
* Change MessageFileVerifier to only do verification - no writing
* Create classes for sorting and grouping messages as per the messages_en file
This commit is contained in:
ljacqu 2017-01-11 21:09:17 +01:00 committed by GitHub
parent 9c7bb8438e
commit d68c73799b
10 changed files with 443 additions and 214 deletions

View File

@ -252,4 +252,9 @@ public enum MessageKey {
public String[] getTags() { public String[] getTags() {
return tags; return tags;
} }
@Override
public String toString() {
return key;
}
} }

View File

@ -1,49 +1,73 @@
denied_command: '&cIn order to use this command you must be authenticated!' # Registration
same_ip_online: 'A player with the same IP is already in game!'
denied_chat: '&cIn order to chat you must be authenticated!'
kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.'
unknown_user: '&cThis user isn''t registered!'
unsafe_spawn: '&cYour quit location was unsafe, you have been teleported to the world''s spawnpoint.'
not_logged_in: '&cYou''re not logged in!'
usage_log: '&cUsage: /login <password>'
wrong_pwd: '&cWrong password!'
unregistered: '&cSuccessfully unregistered!'
reg_disabled: '&cIn-game registration is disabled!'
valid_session: '&2Logged-in due to Session Reconnection.'
login: '&2Successful login!'
vb_nonActiv: '&cYour account isn''t activated yet, please check your emails!'
user_regged: '&cYou already have registered this username!'
usage_reg: '&cUsage: /register <password> <ConfirmPassword>'
max_reg: '&cYou have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection!'
no_perm: '&4You don''t have the permission to perform this action!'
error: '&4An unexpected error occurred, please contact an administrator!'
login_msg: '&cPlease, login with the command "/login <password>"'
reg_msg: '&3Please, register to the server with the command "/register <password> <ConfirmPassword>"' reg_msg: '&3Please, register to the server with the command "/register <password> <ConfirmPassword>"'
usage_unreg: '&cUsage: /unregister <password>' usage_reg: '&cUsage: /register <password> <ConfirmPassword>'
pwd_changed: '&2Password changed successfully!' reg_only: '&4Only registered users can join the server! Please visit http://example.com to register yourself!'
kicked_admin_registered: 'An admin just registered you; please log in again'
registered: '&2Successfully registered!'
reg_disabled: '&cIn-game registration is disabled!'
user_regged: '&cYou already have registered this username!'
# Password errors on registration
password_error: '&cPasswords didn''t match, check them again!' password_error: '&cPasswords didn''t match, check them again!'
password_error_nick: '&cYou can''t use your name as password, please choose another one...' password_error_nick: '&cYou can''t use your name as password, please choose another one...'
password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...' password_error_unsafe: '&cThe chosen password isn''t safe, please choose another one...'
password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX' password_error_chars: '&4Your password contains illegal characters. Allowed chars: REG_EX'
invalid_session: '&cYour IP has been changed and your session data has expired!'
reg_only: '&4Only registered users can join the server! Please visit http://example.com to register yourself!'
logged_in: '&cYou''re already logged in!'
logout: '&2Logged-out successfully!'
same_nick: '&4The same username is already playing on the server!'
registered: '&2Successfully registered!'
pass_len: '&cYour password is too short or too long! Please try with another one!' pass_len: '&cYour password is too short or too long! Please try with another one!'
reload: '&2Configuration and database have been reloaded correctly!'
# Login
usage_log: '&cUsage: /login <password>'
wrong_pwd: '&cWrong password!'
login: '&2Successful login!'
login_msg: '&cPlease, login with the command "/login <password>"'
timeout: '&4Login timeout exceeded, you have been kicked from the server, please try again!' timeout: '&4Login timeout exceeded, you have been kicked from the server, please try again!'
# Errors
unknown_user: '&cThis user isn''t registered!'
denied_command: '&cIn order to use this command you must be authenticated!'
denied_chat: '&cIn order to chat you must be authenticated!'
not_logged_in: '&cYou''re not logged in!'
tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.'
max_reg: '&cYou have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection!'
no_perm: '&4You don''t have the permission to perform this action!'
error: '&4An unexpected error occurred, please contact an administrator!'
unsafe_spawn: '&cYour quit location was unsafe, you have been teleported to the world''s spawnpoint.'
kick_forvip: '&3A VIP player has joined the server when it was full!'
# AntiBot
kick_antibot: 'AntiBot protection mode is enabled! You have to wait some minutes before joining the server.'
antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!'
antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled after %m minutes!'
# Other messages
unregistered: '&cSuccessfully unregistered!'
accounts_owned_self: 'You own %count accounts:'
accounts_owned_other: 'The player %name has %count accounts:'
two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
recovery_code_sent: 'A recovery code to reset your password has been sent to your email.'
recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one'
vb_nonActiv: '&cYour account isn''t activated yet, please check your emails!'
usage_unreg: '&cUsage: /unregister <password>'
pwd_changed: '&2Password changed successfully!'
logged_in: '&cYou''re already logged in!'
logout: '&2Logged out successfully!'
reload: '&2Configuration and database have been reloaded correctly!'
usage_changepassword: '&cUsage: /changepassword <oldPassword> <newPassword>' usage_changepassword: '&cUsage: /changepassword <oldPassword> <newPassword>'
# Session messages
invalid_session: '&cYour IP has been changed and your session data has expired!'
valid_session: '&2Logged-in due to Session Reconnection.'
# Error messages when joining
name_len: '&4Your username is either too short or too long!' name_len: '&4Your username is either too short or too long!'
regex: '&4Your username contains illegal characters. Allowed chars: REG_EX' regex: '&4Your username contains illegal characters. Allowed chars: REG_EX'
add_email: '&3Please add your email to your account with the command "/email add <yourEmail> <confirmEmail>"' country_banned: '&4Your country is banned from this server!'
recovery_email: '&3Forgot your password? Please use the command "/email recovery <yourEmail>"' not_owner_error: 'You are not the owner of this account. Please choose another name!'
usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha <theCaptcha>"'
wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!'
valid_captcha: '&2Captcha code solved correctly!'
kick_forvip: '&3A VIP player has joined the server when it was full!'
kick_fullserver: '&4The server is full, try again later!' kick_fullserver: '&4The server is full, try again later!'
same_nick: '&4The same username is already playing on the server!'
invalid_name_case: 'You should join using username %valid, not %invalid.'
same_ip_online: 'A player with the same IP is already in game!'
# Email
usage_email_add: '&cUsage: /email add <email> <confirmEmail>' usage_email_add: '&cUsage: /email add <email> <confirmEmail>'
usage_email_change: '&cUsage: /email change <oldEmail> <newEmail>' usage_email_change: '&cUsage: /email change <oldEmail> <newEmail>'
usage_email_recovery: '&cUsage: /email recovery <Email>' usage_email_recovery: '&cUsage: /email recovery <Email>'
@ -56,19 +80,14 @@ email_changed: '&2Email address changed correctly!'
email_send: '&2Recovery email sent successfully! Please check your email inbox!' email_send: '&2Recovery email sent successfully! Please check your email inbox!'
email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:' email_exists: '&cA recovery email was already sent! You can discard it and send a new one using the command below:'
email_show: '&2Your current email address is: &f%email' email_show: '&2Your current email address is: &f%email'
show_no_email: '&2You currently don''t have email address associated with this account.'
country_banned: '&4Your country is banned from this server!'
antibot_auto_enabled: '&4[AntiBotService] AntiBot enabled due to the huge number of connections!'
antibot_auto_disabled: '&2[AntiBotService] AntiBot disabled after %m minutes!'
email_already_used: '&4The email address is already being used'
two_factor_create: '&2Your secret code is %code. You can scan it from here %url'
not_owner_error: 'You are not the owner of this account. Please choose another name!'
invalid_name_case: 'You should join using username %valid, not %invalid.'
tempban_max_logins: '&cYou have been temporarily banned for failing to log in too many times.'
accounts_owned_self: 'You own %count accounts:'
accounts_owned_other: 'The player %name has %count accounts:'
kicked_admin_registered: 'An admin just registered you; please log in again'
incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.' incomplete_email_settings: 'Error: not all required settings are set for sending emails. Please contact an admin.'
email_already_used: '&4The email address is already being used'
email_send_failure: 'The email could not be sent. Please contact an administrator.' email_send_failure: 'The email could not be sent. Please contact an administrator.'
recovery_code_sent: 'A recovery code to reset your password has been sent to your email.' show_no_email: '&2You currently don''t have email address associated with this account.'
recovery_code_incorrect: 'The recovery code is not correct! Use /email recovery [email] to generate a new one' add_email: '&3Please add your email to your account with the command "/email add <yourEmail> <confirmEmail>"'
recovery_email: '&3Forgot your password? Please use the command "/email recovery <yourEmail>"'
# Captcha
usage_captcha: '&3To login you have to solve a captcha code, please use the command "/captcha <theCaptcha>"'
wrong_captcha: '&cWrong captcha, please type "/captcha THE_CAPTCHA" into the chat!'
valid_captcha: '&2Captcha code solved correctly!'

View File

@ -0,0 +1,13 @@
package tools.messages;
import java.util.List;
/**
* Represents a section of one or more consecutive comment lines in a file.
*/
public class MessageFileComments extends MessageFileElement {
public MessageFileComments(List<String> lines) {
super(lines);
}
}

View File

@ -0,0 +1,20 @@
package tools.messages;
import java.util.Collections;
import java.util.List;
/**
* An element (a logical unit) in a messages file.
*/
public abstract class MessageFileElement {
private final List<String> lines;
protected MessageFileElement(List<String> lines) {
this.lines = Collections.unmodifiableList(lines);
}
public List<String> getLines() {
return lines;
}
}

View File

@ -0,0 +1,132 @@
package tools.messages;
import fr.xephi.authme.message.MessageKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Creates the same order of message file elements as the given default message elements,
* using the local file's own elements as much as possible and filling in elements from the
* default messages file where necessary.
* <p>
* The current implementation (of this merger and of the {@link MessageFileElementReader reader})
* has the following limitations:
* <ul>
* <li>It assumes that new comments are only ever added to the bottom of the default file.</li>
* <li>If a file only has a partial number of the comments present in the default messages file,
* the file's comments will be moved to the top. This most likely adds the comment above the
* wrong group of messages.</li>
* <li>Assumes that the text for a message only takes one line.</li>
* <li>Ignores the last comment section of a file if it is not followed by any message entry.</li>
* </ul>
*/
public class MessageFileElementMerger {
/** Ordered list of comments in the messages file. */
private final List<MessageFileComments> comments;
/** List of message entries by corresponding MessageKey. */
private final Map<MessageKey, MessageFileEntry> entries;
/**
* Ordered list of file elements of the default file. The entries of the (non-default) messages
* file are based on this.
*/
private final List<MessageFileElement> defaultFileElements;
/** Missing tags in message entries. */
private final Map<MessageKey, Collection<String>> missingTags;
/** Counter for encountered comment elements. */
private int commentsCounter = 0;
private MessageFileElementMerger(List<MessageFileElement> defaultFileElements,
List<MessageFileComments> comments,
Map<MessageKey, MessageFileEntry> entries,
Map<MessageKey, Collection<String>> missingTags) {
this.defaultFileElements = defaultFileElements;
this.comments = comments;
this.entries = entries;
this.missingTags = missingTags;
}
/**
* Returns a list of file elements that follow the order and type of the provided default file elements.
* In other words, using the list of default file elements as template and fallback, it returns the provided
* file elements in the same order and fills in default file elements if an equivalent in {@code fileElements}
* is not present.
*
* @param fileElements file elements to sort and merge
* @param defaultFileElements file elements of the default file to base the operation on
* @param missingTags list of missing tags per message key
* @return ordered and complete list of file elements
*/
public static List<MessageFileElement> mergeElements(List<MessageFileElement> fileElements,
List<MessageFileElement> defaultFileElements,
Map<MessageKey, Collection<String>> missingTags) {
List<MessageFileComments> comments = filteredStream(fileElements, MessageFileComments.class)
.collect(Collectors.toList());
Map<MessageKey, MessageFileEntry> entries = filteredStream(fileElements, MessageFileEntry.class)
.collect(Collectors.toMap(MessageFileEntry::getMessageKey, Function.identity(), (e1, e2) -> e1));
MessageFileElementMerger merger = new MessageFileElementMerger(
defaultFileElements, comments, entries, missingTags);
return merger.mergeElements();
}
private List<MessageFileElement> mergeElements() {
List<MessageFileElement> mergedElements = new ArrayList<>(defaultFileElements.size());
for (MessageFileElement element : defaultFileElements) {
if (element instanceof MessageFileComments) {
mergedElements.add(getCommentsEntry((MessageFileComments) element));
} else if (element instanceof MessageFileEntry) {
mergedElements.add(getEntryForDefaultMessageEntry((MessageFileEntry) element));
} else {
throw new IllegalStateException("Found element of unknown subtype '" + element.getClass() + "'");
}
}
return mergedElements;
}
private MessageFileComments getCommentsEntry(MessageFileComments defaultComments) {
if (comments.size() > commentsCounter) {
MessageFileComments localComments = comments.get(commentsCounter);
++commentsCounter;
return localComments;
}
return defaultComments;
}
private MessageFileElement getEntryForDefaultMessageEntry(MessageFileEntry entry) {
MessageKey messageKey = entry.getMessageKey();
if (messageKey == null) {
throw new IllegalStateException("Default message file should not have unknown entries, but "
+ " entry with lines '" + entry.getLines() + "' has message key = null");
}
MessageFileEntry localEntry = entries.get(messageKey);
if (localEntry == null) {
return entry.convertToMissingEntryComment();
}
Collection<String> absentTags = missingTags.get(messageKey);
return absentTags == null
? localEntry
: localEntry.convertToEntryWithMissingTagsComment(absentTags);
}
/**
* Creates a stream of the entries in {@code collection} with only the elements which are of type {@code clazz}.
*
* @param collection the collection to stream over
* @param clazz the class to restrict the elements to
* @param <P> the collection type (parent)
* @param <C> the type to restrict to (child)
* @return stream over all elements of the given type
*/
private static <P, C extends P> Stream<C> filteredStream(Collection<P> collection, Class<C> clazz) {
return collection.stream().filter(clazz::isInstance).map(clazz::cast);
}
}

View File

@ -0,0 +1,78 @@
package tools.messages;
import tools.utils.FileIoUtils;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Reads a messages file and returns the lines as corresponding {@link MessageFileElement} objects.
*
* @see MessageFileElementMerger
*/
public class MessageFileElementReader {
private final List<MessageFileElement> elements = new ArrayList<>();
private MessageFileElementReader() {
}
/**
* Returns the message files as separate {@link MessageFileElement elements}.
*
* @param file the file to read
* @return the file's elements
*/
public static List<MessageFileElement> readFileIntoElements(File file) {
checkArgument(file.exists(), "Template file '" + file + "' must exist");
MessageFileElementReader reader = new MessageFileElementReader();
reader.loadElements(file.toPath());
return reader.elements;
}
private void loadElements(Path path) {
List<String> currentCommentSection = new ArrayList<>(10);
for (String line : FileIoUtils.readLinesFromFile(path)) {
if (isTodoComment(line)) {
continue;
}
if (isCommentLine(line)) {
currentCommentSection.add(line);
} else if (MessageFileEntry.isMessageEntry(line)) {
if (!currentCommentSection.isEmpty()) {
processTempCommentsList(currentCommentSection);
}
elements.add(new MessageFileEntry(line));
} else {
throw new IllegalStateException("Could not match line '" + line + "' to any type");
}
}
}
/**
* Creates a message file comments element for one or more read comment lines. Does not add
* a comments element if the read lines are only empty lines.
*
* @param comments the read comment lines
*/
private void processTempCommentsList(List<String> comments) {
if (comments.stream().anyMatch(c -> !c.trim().isEmpty())) {
elements.add(new MessageFileComments(new ArrayList<>(comments)));
}
comments.clear();
}
private static boolean isCommentLine(String line) {
return line.trim().isEmpty() || line.trim().startsWith("#");
}
private static boolean isTodoComment(String line) {
return line.startsWith("# TODO ");
}
}

View File

@ -0,0 +1,85 @@
package tools.messages;
import fr.xephi.authme.message.MessageKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.Collections.singletonList;
/**
* Entry in a message file for a message key.
*/
public class MessageFileEntry extends MessageFileElement {
private static final Pattern MESSAGE_ENTRY_REGEX = Pattern.compile("([a-zA-Z_-]+): .*");
private final MessageKey messageKey;
public MessageFileEntry(String line) {
this(singletonList(line), extractMessageKey(line));
}
private MessageFileEntry(List<String> lines, MessageKey messageKey) {
super(lines);
this.messageKey = messageKey;
}
public static boolean isMessageEntry(String line) {
return MESSAGE_ENTRY_REGEX.matcher(line).matches();
}
public MessageKey getMessageKey() {
return messageKey;
}
/**
* Based on this entry, creates a comments element indicating that this message is missing.
*
* @return comments element based on this message element
*/
public MessageFileComments convertToMissingEntryComment() {
List<String> comments = getLines().stream().map(l -> "# TODO " + l).collect(Collectors.toList());
return new MessageFileComments(comments);
}
/**
* Creates an adapted message file entry object with a comment for missing tags.
*
* @param missingTags the tags missing in the message
* @return message file entry with verification comment
*/
public MessageFileEntry convertToEntryWithMissingTagsComment(Collection<String> missingTags) {
List<String> lines = new ArrayList<>(getLines().size() + 1);
lines.add("# TODO: Missing tags " + String.join(", ", missingTags));
lines.addAll(getLines());
return new MessageFileEntry(lines, messageKey);
}
/**
* Returns the {@link MessageKey} this entry is for. Returns {@code null} if the message key could not be matched.
*
* @param line the line to process
* @return the associated message key, or {@code null} if no match was found
*/
private static MessageKey extractMessageKey(String line) {
Matcher matcher = MESSAGE_ENTRY_REGEX.matcher(line);
if (matcher.find()) {
String key = matcher.group(1);
return fromKey(key);
}
throw new IllegalStateException("Could not extract message key from line '" + line + "'");
}
private static MessageKey fromKey(String key) {
for (MessageKey messageKey : MessageKey.values()) {
if (messageKey.getKey().equals(key)) {
return messageKey;
}
}
return null;
}
}

View File

@ -1,21 +1,14 @@
package tools.messages; package tools.messages;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import tools.utils.FileIoUtils;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -26,8 +19,8 @@ public class MessageFileVerifier {
private final File messagesFile; private final File messagesFile;
private final Set<String> unknownKeys = new HashSet<>(); private final Set<String> unknownKeys = new HashSet<>();
private final List<MissingKey> missingKeys = new ArrayList<>(); private final Set<MessageKey> missingKeys = new HashSet<>();
private final Multimap<String, String> missingTags = HashMultimap.create(); private final Multimap<MessageKey, String> missingTags = HashMultimap.create();
/** /**
* Create a verifier that verifies the given messages file. * Create a verifier that verifies the given messages file.
@ -56,16 +49,16 @@ public class MessageFileVerifier {
* *
* @return The list of missing keys in the file * @return The list of missing keys in the file
*/ */
public List<MissingKey> getMissingKeys() { public Set<MessageKey> getMissingKeys() {
return missingKeys; return missingKeys;
} }
/** /**
* Return the collection of tags the message key defines that aren't present in the read line. * Return the collection of tags the message key defines that aren't present in the read line.
* *
* @return Collection of missing tags per message key. Key = message key, value = missing tag. * @return Collection of missing tags per message key
*/ */
public Multimap<String, String> getMissingTags() { public Multimap<MessageKey, String> getMissingTags() {
return missingTags; return missingTags;
} }
@ -78,7 +71,7 @@ public class MessageFileVerifier {
if (configuration.isString(key)) { if (configuration.isString(key)) {
checkTagsInMessage(messageKey, configuration.getString(key)); checkTagsInMessage(messageKey, configuration.getString(key));
} else { } else {
missingKeys.add(new MissingKey(key)); missingKeys.add(messageKey);
} }
} }
@ -93,84 +86,11 @@ public class MessageFileVerifier {
private void checkTagsInMessage(MessageKey messageKey, String message) { private void checkTagsInMessage(MessageKey messageKey, String message) {
for (String tag : messageKey.getTags()) { for (String tag : messageKey.getTags()) {
if (!message.contains(tag)) { if (!message.contains(tag)) {
missingTags.put(messageKey.getKey(), tag); missingTags.put(messageKey, tag);
} }
} }
} }
/**
* Add missing keys to the file with the provided default (English) message.
*
* @param defaultMessages The collection of default messages
*/
public void addMissingKeys(FileConfiguration defaultMessages) {
final List<String> fileLines = FileIoUtils.readLinesFromFile(messagesFile.toPath());
List<MissingKey> keysToAdd = new ArrayList<>();
for (MissingKey entry : missingKeys) {
final String key = entry.getKey();
if (!entry.getWasAdded() && defaultMessages.get(key) != null) {
keysToAdd.add(entry);
}
}
// Add missing keys as comments to the bottom of the file
for (MissingKey keyToAdd : keysToAdd) {
final String key = keyToAdd.getKey();
int indexOfComment = Iterables.indexOf(fileLines, isCommentFor(key));
if (indexOfComment != -1) {
// Comment for keyToAdd already exists, so remove it since we're going to add it
fileLines.remove(indexOfComment);
}
String comment = commentForKey(key) + "'" +
defaultMessages.getString(key).replace("'", "''") + "'";
fileLines.add(comment);
keyToAdd.setWasAdded(true);
}
// Add a comment above messages missing a tag
for (Map.Entry<String, Collection<String>> entry : missingTags.asMap().entrySet()) {
final String key = entry.getKey();
addCommentForMissingTags(fileLines, key, entry.getValue());
}
FileIoUtils.writeToFile(messagesFile.toPath(), String.join("\n", fileLines));
}
/**
* Add a comment above a message to note the tags the message is missing. Removes
* any similar comment that may already be above the message.
*
* @param fileLines The lines of the file (to modify)
* @param key The key of the message
* @param tags The missing tags
*/
private void addCommentForMissingTags(List<String> fileLines, final String key, Collection<String> tags) {
int indexForComment = Iterables.indexOf(fileLines, isCommentFor(key));
if (indexForComment == -1) {
indexForComment = Iterables.indexOf(fileLines, input -> input.startsWith(key + ": "));
if (indexForComment == -1) {
System.err.println("Error adding comment for key '" + key + "': couldn't find entry in file lines");
return;
}
} else {
fileLines.remove(indexForComment);
}
String tagWord = tags.size() > 1 ? "tags" : "tag";
fileLines.add(indexForComment, commentForKey(key)
+ String.format("Missing %s %s", tagWord, String.join(", ", tags)));
}
private static String commentForKey(String key) {
return String.format("# TODO %s: ", key);
}
private static Predicate<String> isCommentFor(final String key) {
return input -> input.startsWith(commentForKey(key));
}
private static boolean messageKeyExists(String key) { private static boolean messageKeyExists(String key) {
for (MessageKey messageKey : MessageKey.values()) { for (MessageKey messageKey : MessageKey.values()) {
if (messageKey.getKey().equals(key)) { if (messageKey.getKey().equals(key)) {

View File

@ -1,37 +0,0 @@
package tools.messages;
/**
* Missing message key in a file.
*/
public class MissingKey {
private final String key;
private boolean wasAdded;
public MissingKey(String key) {
this.key = key;
}
/**
* @return the message key that is missing
*/
public String getKey() {
return key;
}
public void setWasAdded(boolean wasAdded) {
this.wasAdded = wasAdded;
}
/**
* @return true if a comment was added to the file, false otherwise
*/
public boolean getWasAdded() {
return wasAdded;
}
@Override
public String toString() {
return key;
}
}

View File

@ -1,9 +1,9 @@
package tools.messages; package tools.messages;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.FileConfiguration; import tools.utils.FileIoUtils;
import org.bukkit.configuration.file.YamlConfiguration;
import tools.utils.ToolTask; import tools.utils.ToolTask;
import tools.utils.ToolsConstants; import tools.utils.ToolsConstants;
@ -54,9 +54,9 @@ public final class VerifyMessagesTask implements ToolTask {
messageFiles = Collections.singletonList(customFile); messageFiles = Collections.singletonList(customFile);
} }
FileConfiguration defaultMessages = null; List<MessageFileElement> defaultFileElements = null;
if (addMissingKeys) { if (addMissingKeys) {
defaultMessages = YamlConfiguration.loadConfiguration(new File(DEFAULT_MESSAGES_FILE)); defaultFileElements = MessageFileElementReader.readFileIntoElements(new File(DEFAULT_MESSAGES_FILE));
} }
// Verify the given files // Verify the given files
@ -64,9 +64,10 @@ public final class VerifyMessagesTask implements ToolTask {
System.out.println("Verifying '" + file.getName() + "'"); System.out.println("Verifying '" + file.getName() + "'");
MessageFileVerifier verifier = new MessageFileVerifier(file); MessageFileVerifier verifier = new MessageFileVerifier(file);
if (addMissingKeys) { if (addMissingKeys) {
verifyFileAndAddKeys(verifier, defaultMessages); outputVerificationResults(verifier);
updateMessagesFile(file, verifier, defaultFileElements);
} else { } else {
verifyFile(verifier); outputVerificationResults(verifier);
} }
} }
@ -75,8 +76,13 @@ public final class VerifyMessagesTask implements ToolTask {
} }
} }
private static void verifyFile(MessageFileVerifier verifier) { /**
List<MissingKey> missingKeys = verifier.getMissingKeys(); * Outputs the verification results to the console.
*
* @param verifier the verifier whose results should be output
*/
private static void outputVerificationResults(MessageFileVerifier verifier) {
Set<MessageKey> missingKeys = verifier.getMissingKeys();
if (!missingKeys.isEmpty()) { if (!missingKeys.isEmpty()) {
System.out.println(" Missing keys: " + missingKeys); System.out.println(" Missing keys: " + missingKeys);
} }
@ -86,44 +92,32 @@ public final class VerifyMessagesTask implements ToolTask {
System.out.println(" Unknown keys: " + unknownKeys); System.out.println(" Unknown keys: " + unknownKeys);
} }
Multimap<String, String> missingTags = verifier.getMissingTags(); Multimap<MessageKey, String> missingTags = verifier.getMissingTags();
for (Map.Entry<String, String> entry : missingTags.entries()) { for (Map.Entry<MessageKey, String> entry : missingTags.entries()) {
System.out.println(" Missing tag '" + entry.getValue() + "' in entry with key '" + entry.getKey() + "'"); System.out.println(" Missing tag '" + entry.getValue() + "' in entry '" + entry.getKey() + "'");
} }
} }
public static void verifyFileAndAddKeys(MessageFileVerifier verifier, FileConfiguration defaultMessages) { /**
List<MissingKey> missingKeys = verifier.getMissingKeys(); * Updates a messages file to have the same order as the default file and to contain all
if (!missingKeys.isEmpty() || !verifier.getMissingTags().isEmpty()) { * failed verifications as comments.
verifier.addMissingKeys(defaultMessages); *
List<String> addedKeys = getMissingKeysWithAdded(missingKeys, true); * @param file the file to update
System.out.println(" Added missing keys " + addedKeys); * @param verifier the verifier whose results should be used
* @param defaultFileElements default file elements to base the new file structure on
List<String> unsuccessfulKeys = getMissingKeysWithAdded(missingKeys, false); */
if (!unsuccessfulKeys.isEmpty()) { private static void updateMessagesFile(File file, MessageFileVerifier verifier,
System.err.println(" Warning! Could not add all missing keys (problem with loading " + List<MessageFileElement> defaultFileElements) {
"default messages?)"); List<MessageFileElement> messageFileElements = MessageFileElementReader.readFileIntoElements(file);
System.err.println(" Could not add keys " + unsuccessfulKeys); String newMessageFileContents = MessageFileElementMerger
} .mergeElements(messageFileElements, defaultFileElements, verifier.getMissingTags().asMap())
} .stream()
.map(MessageFileElement::getLines)
Set<String> unknownKeys = verifier.getUnknownKeys(); .flatMap(List::stream)
if (!unknownKeys.isEmpty()) { .collect(Collectors.joining("\n"));
System.out.println(" Unknown keys: " + unknownKeys); FileIoUtils.writeToFile(file.toPath(), newMessageFileContents + "\n");
}
Multimap<String, String> missingTags = verifier.getMissingTags();
for (Map.Entry<String, String> entry : missingTags.entries()) {
System.out.println(" Missing tag '" + entry.getValue() + "' in entry with key '" + entry.getKey() + "'");
}
} }
private static List<String> getMissingKeysWithAdded(List<MissingKey> missingKeys, boolean wasAdded) {
return missingKeys.stream()
.filter(e -> e.getWasAdded() == wasAdded)
.map(MissingKey::getKey)
.collect(Collectors.toList());
}
private static List<File> getMessagesFiles() { private static List<File> getMessagesFiles() {
File[] files = listFilesOrThrow(new File(MESSAGES_FOLDER)); File[] files = listFilesOrThrow(new File(MESSAGES_FOLDER));