Merge pull request #55 from marhali/feat/yml-support

Feat/yml support
This commit is contained in:
Marcel 2021-09-21 15:16:49 +02:00 committed by GitHub
commit e4410c264c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 888 additions and 101 deletions

View File

@ -3,6 +3,13 @@
# easy-i18n Changelog
## [Unreleased]
### Added
- Support for YAML locale files. Thanks to @sunarya-thito
- Optional path-prefix for translations
### Changed
- Optimized i18n key completion
## [1.4.1]
### Added
- Support for IntelliJ 2021.2

View File

@ -0,0 +1,17 @@
alpha:
spacing: ' führendes Leerzeichen'
first: Beispiel Übersetzung
beta:
title: Titel
nested:
title: Ein verschachtelter Titel
gamma:
array:
escaped:
- Erstes;Element
- Zweites Element
- Drittes;Element
simple:
- Erstes Element
- Zweites Element
title: Gamma Titel

View File

@ -0,0 +1,17 @@
alpha:
spacing: ' leading space'
first: Example Translation
beta:
title: Title
nested:
title: some nested title
gamma:
array:
escaped:
- first;element
- second element
- third;element
simple:
- first element
- second element
title: gamma title

View File

@ -26,7 +26,8 @@ public class SettingsDialog {
private TextFieldWithBrowseButton pathText;
private JBTextField filePatternText;
private JBTextField previewText;
private JBTextField previewLocaleText;
private JBTextField pathPrefixText;
private JBCheckBox codeAssistanceCheckbox;
public SettingsDialog(Project project) {
@ -37,22 +38,25 @@ public class SettingsDialog {
String localesPath = SettingsService.getInstance(project).getState().getLocalesPath();
String filePattern = SettingsService.getInstance(project).getState().getFilePattern();
String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
String pathPrefix = SettingsService.getInstance(project).getState().getPathPrefix();
boolean codeAssistance = SettingsService.getInstance(project).getState().isCodeAssistance();
if(prepare(localesPath, filePattern, previewLocale, codeAssistance).show() == DialogWrapper.OK_EXIT_CODE) { // Save changes
if(prepare(localesPath, filePattern, previewLocale, pathPrefix, codeAssistance).show() == DialogWrapper.OK_EXIT_CODE) { // Save changes
SettingsService.getInstance(project).getState().setLocalesPath(pathText.getText());
SettingsService.getInstance(project).getState().setFilePattern(filePatternText.getText());
SettingsService.getInstance(project).getState().setPreviewLocale(previewText.getText());
SettingsService.getInstance(project).getState().setPreviewLocale(previewLocaleText.getText());
SettingsService.getInstance(project).getState().setCodeAssistance(codeAssistanceCheckbox.isSelected());
SettingsService.getInstance(project).getState().setPathPrefix(pathPrefixText.getText());
// Reload instance
DataStore.getInstance(project).reloadFromDisk();
}
}
private DialogBuilder prepare(String localesPath, String filePattern, String previewLocale, boolean codeAssistance) {
private DialogBuilder prepare(String localesPath, String filePattern, String previewLocale, String pathPrefix, boolean codeAssistance) {
JPanel rootPanel = new JPanel(new GridLayout(0, 1, 2, 2));
/* path */
JBLabel pathLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.text"));
pathText = new TextFieldWithBrowseButton(new JTextField(localesPath));
@ -63,20 +67,29 @@ public class SettingsDialog {
rootPanel.add(pathLabel);
rootPanel.add(pathText);
/* file pattern */
JBLabel filePatternLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.file-pattern"));
filePatternText = new JBTextField(filePattern);
rootPanel.add(filePatternLabel);
rootPanel.add(filePatternText);
/* preview locale */
JBLabel previewLocaleLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.preview"));
previewLocaleText = new JBTextField(previewLocale);
previewLocaleLabel.setLabelFor(previewLocaleText);
JBLabel previewLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.preview"));
previewText = new JBTextField(previewLocale);
previewLabel.setLabelFor(previewText);
rootPanel.add(previewLocaleLabel);
rootPanel.add(previewLocaleText);
rootPanel.add(previewLabel);
rootPanel.add(previewText);
/* path prefix */
JBLabel pathPrefixLabel = new JBLabel(ResourceBundle.getBundle("messages").getString("settings.path.prefix"));
pathPrefixText = new JBTextField(pathPrefix);
rootPanel.add(pathPrefixLabel);
rootPanel.add(pathPrefixText);
/* code assistance */
codeAssistanceCheckbox = new JBCheckBox(ResourceBundle.getBundle("messages").getString("settings.editor.assistance"));
codeAssistanceCheckbox.setSelected(codeAssistance);

View File

@ -29,7 +29,17 @@ public class KeyAnnotator {
}
String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
LocalizedNode node = DataStore.getInstance(project).getTranslations().getNode(key);
String pathPrefix = SettingsService.getInstance(project).getState().getPathPrefix();
String searchKey = key.length() >= pathPrefix.length()
? key.substring(pathPrefix.length())
: key;
if(searchKey.startsWith(".")) {
searchKey = searchKey.substring(1);
}
LocalizedNode node = DataStore.getInstance(project).getTranslations().getNode(searchKey);
if(node == null) { // Unknown translation. Just ignore it
return;

View File

@ -1,20 +1,16 @@
package de.marhali.easyi18n.editor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.util.ProcessingContext;
import de.marhali.easyi18n.model.LocalizedNode;
import de.marhali.easyi18n.service.DataStore;
import de.marhali.easyi18n.service.SettingsService;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.*;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.project.*;
import com.intellij.util.*;
import de.marhali.easyi18n.model.*;
import de.marhali.easyi18n.service.*;
import de.marhali.easyi18n.util.TranslationsUtil;
import org.jetbrains.annotations.*;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.*;
/**
* I18n translation key completion provider.
@ -33,38 +29,58 @@ public class KeyCompletionProvider extends CompletionProvider<CompletionParamete
return;
}
DataStore store = DataStore.getInstance(project);
String previewLocale = SettingsService.getInstance(project).getState().getPreviewLocale();
String pathPrefix = SettingsService.getInstance(project).getState().getPathPrefix();
String query = result.getPrefixMatcher().getPrefix();
List<String> sections = TranslationsUtil.getSections(query);
String lastSection = null;
String path = result.getPrefixMatcher().getPrefix();
if(!sections.isEmpty() && !query.endsWith(".")) {
lastSection = sections.remove(sections.size() - 1);
if(pathPrefix == null) {
pathPrefix = "";
}
String path = TranslationsUtil.sectionsToFullPath(sections);
if(path.startsWith(pathPrefix)) {
path = path.substring(pathPrefix.length());
LocalizedNode node = sections.isEmpty() ? DataStore.getInstance(project).getTranslations().getNodes()
: DataStore.getInstance(project).getTranslations().getNode(path);
if(path.startsWith(".")) { // Remove leading dot
path = path.substring(1);
}
if(node == null) { // Unknown translation
return;
} else {
path = ""; // Show suggestions for root view
}
for(LocalizedNode children : node.getChildren()) {
if(lastSection == null || children.getKey().startsWith(lastSection)) {
// Construct full key path / Fore nested objects add '.' to indicate deeper level
String fullKey = (path.isEmpty() ? children.getKey() : path + "." + children.getKey()) + (children.isLeaf() ? "" : ".");
if(pathPrefix.length() > 0 && !pathPrefix.endsWith(".")) {
pathPrefix += ".";
}
result.addElement(LookupElementBuilder.create(fullKey)
.appendTailText(getTailText(children, previewLocale), true));
List<String> fullKeys = store.getTranslations().getFullKeys();
int sections = path.split("\\.").length;
int maxSectionForwardLookup = 5;
for(String key : fullKeys) {
// Path matches
if(key.startsWith(path)) {
String[] keySections = key.split("\\.");
if(keySections.length > sections + maxSectionForwardLookup) { // Key is too deep nested
String shrinkKey = TranslationsUtil.sectionsToFullPath(Arrays.asList(
Arrays.copyOf(keySections, sections + maxSectionForwardLookup)));
result.addElement(LookupElementBuilder.create(pathPrefix + shrinkKey)
.appendTailText(" I18n([])", true));
} else {
LocalizedNode node = store.getTranslations().getNode(key);
String translation = node != null ? node.getValue().get(previewLocale) : null;
result.addElement(LookupElementBuilder.create(pathPrefix + key)
.withIcon(AllIcons.Actions.PreserveCaseHover)
.appendTailText(" I18n(" + previewLocale + ": " + translation + ")", true)
);
}
}
}
}
private String getTailText(LocalizedNode node, String previewLocale) {
return !node.isLeaf() ? " I18n([])"
: " I18n(" + previewLocale + ": " + node.getValue().get(previewLocale) + ")";
}
}

View File

@ -3,7 +3,8 @@ package de.marhali.easyi18n.editor.generic;
import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.patterns.*;
import com.intellij.psi.PsiLiteralValue;
import com.intellij.psi.*;
import com.intellij.psi.xml.*;
import de.marhali.easyi18n.editor.KeyCompletionProvider;
/**
@ -13,6 +14,10 @@ import de.marhali.easyi18n.editor.KeyCompletionProvider;
public class GenericKeyCompletionContributor extends CompletionContributor {
public GenericKeyCompletionContributor() {
extend(CompletionType.BASIC, PlatformPatterns.psiElement(PlainTextTokenTypes.PLAIN_TEXT),
new KeyCompletionProvider());
extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(XmlElement.class),
new KeyCompletionProvider());
extend(CompletionType.BASIC, PlatformPatterns.psiElement().inside(PsiLiteralValue.class),
new KeyCompletionProvider());
}

View File

@ -0,0 +1,125 @@
package de.marhali.easyi18n.io.implementation;
import com.intellij.openapi.application.*;
import com.intellij.openapi.project.*;
import com.intellij.openapi.vfs.*;
import de.marhali.easyi18n.io.*;
import de.marhali.easyi18n.model.*;
import de.marhali.easyi18n.util.*;
import de.marhali.easyi18n.util.array.YamlArrayUtil;
import org.jetbrains.annotations.*;
import thito.nodeflow.config.*;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import java.util.function.*;
public class YamlTranslatorIO implements TranslatorIO {
@Override
public void read(@NotNull Project project, @NotNull String directoryPath, @NotNull Consumer<Translations> callback) {
ApplicationManager.getApplication().saveAll(); // Save opened files (required if new locales were added)
ApplicationManager.getApplication().runReadAction(() -> {
VirtualFile directory = LocalFileSystem.getInstance().findFileByIoFile(new File(directoryPath));
if(directory == null || directory.getChildren() == null) {
throw new IllegalArgumentException("Specified folder is invalid (" + directoryPath + ")");
}
VirtualFile[] files = directory.getChildren();
List<String> locales = new ArrayList<>();
LocalizedNode nodes = new LocalizedNode(LocalizedNode.ROOT_KEY, new ArrayList<>());
try {
for(VirtualFile file : files) {
if(!IOUtil.isFileRelevant(project, file)) { // File does not matches pattern
continue;
}
locales.add(file.getNameWithoutExtension());
try (Reader reader = new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8)) {
Section section = Section.parseToMap(reader);
load(file.getNameWithoutExtension(), nodes, section);
}
}
callback.accept(new Translations(locales, nodes));
} catch(IOException e) {
e.printStackTrace();
callback.accept(null);
}
});
}
private void load(String locale, LocalizedNode node, Section section) {
if (section instanceof MapSection) {
for (String key : section.getKeys()) {
LocalizedNode child = node.getChildren(key);
if (child == null) {
node.addChildren(child = new LocalizedNode(key, new ArrayList<>()));
}
LocalizedNode finalChild = child;
MapSection map = section.getMap(key).orElse(null);
if (map != null) {
load(locale, finalChild, map);
} else {
if(section.isList(key) && section.getList(key).isPresent()) {
child.getValue().put(locale, YamlArrayUtil.read(section.getList(key).get()));
} else {
String value = section.getString(key).orElse(null);
if (value != null) {
child.getValue().put(locale, value);
}
}
}
}
}
}
private void save(LocalizedNode node, String locale, Section section, String path) {
if (node.isLeaf() && !node.getKey().equals(LocalizedNode.ROOT_KEY)) {
String value = node.getValue().get(locale);
if (value != null) {
section.set(path, YamlArrayUtil.isArray(value) ? YamlArrayUtil.write(value) : value);
}
} else {
for (LocalizedNode child : node.getChildren()) {
save(child, locale, section, path == null ? child.getKey() : path + "." + child.getKey());
}
}
}
@Override
public void save(@NotNull Project project, @NotNull Translations translations, @NotNull String directoryPath, @NotNull Consumer<Boolean> callback) {
ApplicationManager.getApplication().runWriteAction(() -> {
try {
for(String locale : translations.getLocales()) {
Section section = new MapSection();
save(translations.getNodes(), locale, section, null);
String fullPath = directoryPath + "/" + locale + ".yml";
VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(fullPath));
file.setBinaryContent(Section.toString(section).getBytes(file.getCharset()));
}
// Successfully saved
callback.accept(true);
} catch(IOException e) {
e.printStackTrace();
callback.accept(false);
}
});
}
}

View File

@ -16,6 +16,7 @@ public class SettingsState {
private String localesPath;
private String filePattern;
private String previewLocale;
private String pathPrefix;
private Boolean codeAssistance;
public SettingsState() {}
@ -51,4 +52,12 @@ public class SettingsState {
public void setCodeAssistance(boolean codeAssistance) {
this.codeAssistance = codeAssistance;
}
public void setPathPrefix(String pathPrefix) {
this.pathPrefix = pathPrefix;
}
public String getPathPrefix() {
return pathPrefix;
}
}

View File

@ -4,7 +4,6 @@ import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.project.Project;
import com.intellij.util.xmlb.XmlSerializerUtil;
import de.marhali.easyi18n.model.SettingsState;
import org.jetbrains.annotations.NotNull;

View File

@ -3,9 +3,7 @@ package de.marhali.easyi18n.util;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import de.marhali.easyi18n.io.implementation.JsonTranslatorIO;
import de.marhali.easyi18n.io.implementation.ModularizedJsonTranslatorIO;
import de.marhali.easyi18n.io.implementation.PropertiesTranslatorIO;
import de.marhali.easyi18n.io.implementation.*;
import de.marhali.easyi18n.io.TranslatorIO;
import de.marhali.easyi18n.service.SettingsService;
@ -50,7 +48,8 @@ public class IOUtil {
case "properties":
return new PropertiesTranslatorIO();
case "yml":
return new YamlTranslatorIO();
default:
throw new UnsupportedOperationException("Unsupported i18n locale file format: " +
any.get().getFileType().getDefaultExtension());

View File

@ -1,51 +0,0 @@
package de.marhali.easyi18n.util;
import com.google.gson.JsonArray;
import org.apache.commons.lang.StringEscapeUtils;
import java.util.regex.Pattern;
/**
* Utility methods to read and write json arrays.
* @author marhali
*/
public class JsonArrayUtil {
public static String ARRAY_PREFIX = "!arr[";
public static String ARRAY_SUFFIX = "]";
public static char ARRAY_DELIMITER = ';';
public static String read(JsonArray array) {
StringBuilder builder = new StringBuilder(ARRAY_PREFIX);
for(int i = 0; i < array.size(); i++) {
if(i > 0) {
builder.append(ARRAY_DELIMITER);
}
String value = array.get(i).getAsString().replace(";", "\\;");
builder.append(StringUtil.escapeControls(value, true));
}
builder.append(ARRAY_SUFFIX);
return builder.toString();
}
public static JsonArray write(String concat) {
concat = concat.substring(ARRAY_PREFIX.length(), concat.length() - ARRAY_SUFFIX.length());
String regex = "(?<!\\\\)" + Pattern.quote(String.valueOf(ARRAY_DELIMITER));
JsonArray array = new JsonArray();
for(String element : concat.split(regex)) {
element = element.replace("\\" + ARRAY_DELIMITER, String.valueOf(ARRAY_DELIMITER));
array.add(StringEscapeUtils.unescapeJava(element));
}
return array;
}
public static boolean isArray(String concat) {
return concat != null && concat.startsWith(ARRAY_PREFIX) && concat.endsWith(ARRAY_SUFFIX);
}
}

View File

@ -6,6 +6,7 @@ import com.google.gson.JsonPrimitive;
import de.marhali.easyi18n.model.LocalizedNode;
import de.marhali.easyi18n.util.array.JsonArrayUtil;
import org.apache.commons.lang.StringEscapeUtils;
import java.util.ArrayList;

View File

@ -0,0 +1,58 @@
package de.marhali.easyi18n.util.array;
import de.marhali.easyi18n.util.StringUtil;
import org.apache.commons.lang.StringEscapeUtils;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
/**
* Utility methods for simple array support.
* @author marhali
*/
public abstract class ArrayUtil {
static final String PREFIX = "!arr[";
static final String SUFFIX = "]";
static final char DELIMITER = ';';
static final String SPLITERATOR_REGEX =
MessageFormat.format("(?<!\\\\){0}", Pattern.quote(String.valueOf(DELIMITER)));
static <T> String read(Iterator<T> elements, Function<T, String> stringFactory) {
StringBuilder builder = new StringBuilder(PREFIX);
int i = 0;
while(elements.hasNext()) {
if(i > 0) {
builder.append(DELIMITER);
}
String value = stringFactory.apply(elements.next());
builder.append(StringUtil.escapeControls(
value.replace(String.valueOf(DELIMITER), "\\" + DELIMITER), true));
i++;
}
builder.append(SUFFIX);
return builder.toString();
}
static void write(String concat, Consumer<String> writeElement) {
concat = concat.substring(PREFIX.length(), concat.length() - SUFFIX.length());
for(String element : concat.split(SPLITERATOR_REGEX)) {
element = element.replace("\\" + DELIMITER, String.valueOf(DELIMITER));
writeElement.accept(StringEscapeUtils.unescapeJava(element));
}
}
public static boolean isArray(String concat) {
return concat != null && concat.startsWith(PREFIX) && concat.endsWith(SUFFIX);
}
}

View File

@ -0,0 +1,20 @@
package de.marhali.easyi18n.util.array;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
/**
* Utility methods to read and write json arrays.
* @author marhali
*/
public class JsonArrayUtil extends ArrayUtil {
public static String read(JsonArray array) {
return read(array.iterator(), JsonElement::getAsString);
}
public static JsonArray write(String concat) {
JsonArray array = new JsonArray();
write(concat, array::add);
return array;
}
}

View File

@ -0,0 +1,20 @@
package de.marhali.easyi18n.util.array;
import thito.nodeflow.config.ListSection;
/**
* Utility methods to read and write yaml lists.
* @author marhali
*/
public class YamlArrayUtil extends ArrayUtil {
public static String read(ListSection list) {
return read(list.iterator(), Object::toString);
}
public static ListSection write(String concat) {
ListSection list = new ListSection();
write(concat, list::add);
return list;
}
}

View File

@ -0,0 +1,227 @@
package thito.nodeflow.config;
import java.util.*;
import java.util.stream.*;
public class ListSection extends ArrayList<Object> implements Section {
private Section parent;
private String name;
public ListSection() {
super();
}
public ListSection(Collection<?> c) {
super();
addAll(c);
}
protected void setParent(Section parent, String name) {
this.parent = parent;
this.name = name;
}
@Override
public String getName() {
if (name == null && parent != null) {
if (parent instanceof ListSection) {
return String.valueOf(((ListSection) parent).indexOf(this));
}
if (parent instanceof MapSection) {
return ((MapSection) parent).entrySet().stream().filter(e -> Objects.equals(e.getValue(), this))
.findAny().map(Map.Entry::getKey).orElse(null);
}
}
return name;
}
public Optional<Object> getObject(int index) {
return index >= 0 && index < size() ? Optional.ofNullable(get(index)) : Optional.empty();
}
@Override
public Section getParent() {
return parent;
}
@Override
public Set<String> getKeys() {
return IntStream.range(0, size()).mapToObj(String::valueOf).collect(Collectors.toSet());
}
@Override
public Optional<?> getInScope(String key) {
try {
return Optional.ofNullable(get(Integer.parseInt(key)));
} catch (Throwable t) {
}
return Optional.empty();
}
@Override
public void setInScope(String key, Object value) {
try {
set(Integer.parseInt(key), value);
} catch (Throwable t) {
}
}
@Override
public Object set(int index, Object element) {
element = Section.wrap(element);
if (element instanceof Section) element = Section.wrapParent(this, null, (Section) element);
return super.set(index, element);
}
@Override
public boolean add(Object o) {
o = Section.wrap(o);
if (o instanceof Section) o = Section.wrapParent(this, null, (Section) o);
return super.add(o);
}
@Override
public void add(int index, Object element) {
element = Section.wrap(element);
if (element instanceof Section) element = Section.wrapParent(this, null, (Section) element);
super.add(index, element);
}
@Override
public boolean addAll(Collection<?> c) {
c.forEach(o -> add(o));
return !c.isEmpty();
}
@Override
public boolean addAll(int index, Collection<?> c) {
List<Object> wrapped = new ArrayList<>();
c.forEach(obj -> {
Object o = Section.wrap(obj);
if (o instanceof Section) o = Section.wrapParent(this, null, (Section) o);
wrapped.add(o);
});
return super.addAll(index, wrapped);
}
@Override
public String toString() {
return Section.toString(this);
}
public <T extends Enum<T>> Optional<T> getEnum(int index, Class<T> clz) {
return getObject(index).map(o -> {
try {
return Enum.valueOf(clz, String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
public Optional<String> getString(int index) {
return getObject(index).map(String::valueOf);
}
public Optional<Integer> getInteger(int index) {
return getObject(index).map(o -> {
try {
return Integer.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
public Optional<Double> getDouble(int index) {
return getObject(index).map(o -> {
try {
return Double.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
public Optional<Long> getLong(int index) {
return getObject(index).map(o -> {
try {
return Long.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
public Optional<Float> getFloat(int index) {
return getObject(index).map(o -> {
try {
return Float.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
public Optional<Short> getShort(int index) {
return getObject(index).map(o -> {
try {
return Short.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
public Optional<Byte> getByte(int index) {
return getObject(index).map(o -> {
try {
return Byte.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
public Optional<Character> getCharacter(int index) {
return getObject(index).map(o -> {
String text = String.valueOf(o);
return text.isEmpty() ? null : text.charAt(0);
});
}
public Optional<Boolean> getBoolean(int index) {
return getObject(index).map(o -> {
String text = String.valueOf(o);
return text.equals("true") ? Boolean.TRUE : text.equals("false") ? Boolean.FALSE : null;
});
}
public Optional<MapSection> getMap(int index) {
return getObject(index).map(o -> {
if (o instanceof Map) {
if (o instanceof MapSection) return (MapSection) o;
MapSection mapSection = new MapSection((Map<?, ?>) o);
mapSection.setParent(this, null);
return mapSection;
}
return null;
});
}
public Optional<ListSection> getList(int index) {
return getObject(index).map(o -> {
if (o instanceof List) {
if (o instanceof ListSection) {
return (ListSection) o;
}
ListSection list = new ListSection((List<?>) o);
list.setParent(this, null);
return list;
}
ListSection list = new ListSection(Collections.singleton(o));
list.setParent(this, null);
return list;
});
}
}

View File

@ -0,0 +1,73 @@
package thito.nodeflow.config;
import java.util.*;
public class MapSection extends HashMap<String, Object> implements Section {
private Section parent;
private String name;
public MapSection() {
super();
}
public MapSection(Map<?, ?> m) {
super();
m.forEach((key, value) -> put(String.valueOf(key), value));
}
protected void setParent(Section parent, String name) {
this.parent = parent;
this.name = name;
}
@Override
public String getName() {
if (name == null && parent != null) {
if (parent instanceof ListSection) {
return String.valueOf(((ListSection) parent).indexOf(this));
}
if (parent instanceof MapSection) {
return ((MapSection) parent).entrySet().stream().filter(e -> Objects.equals(e.getValue(), this))
.findAny().map(Entry::getKey).orElse(null);
}
}
return name;
}
@Override
public Section getParent() {
return parent;
}
@Override
public void setInScope(String key, Object value) {
put(key, value);
}
@Override
public Object put(String key, Object value) {
value = Section.wrap(value);
if (value instanceof Section) value = Section.wrapParent(this, key, (Section) value);
return super.put(key, value);
}
@Override
public void putAll(Map<? extends String, ?> m) {
m.forEach(this::put);
}
@Override
public Set<String> getKeys() {
return keySet();
}
@Override
public Optional<?> getInScope(String key) {
return Optional.ofNullable(get(key));
}
@Override
public String toString() {
return Section.toString(this);
}
}

View File

@ -0,0 +1,221 @@
package thito.nodeflow.config;
import org.yaml.snakeyaml.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
public interface Section {
String SEPARATOR = ".";
static Object wrap(Object o) {
if (o instanceof Section) return o;
if (o instanceof List) {
return new ListSection((List<?>) o);
}
if (o instanceof Map) {
return new MapSection((Map<?, ?>) o);
}
return o;
}
static String getName(String path) {
String[] split = path.split(Pattern.quote(SEPARATOR));
return split[split.length - 1];
}
static Section wrapParent(Section parent, String name, Section current) {
if (current.getParent() != null && current.getParent() != parent) {
if (current instanceof MapSection) {
MapSection mapSection = new MapSection((MapSection) current);
mapSection.setParent(parent, name);
return mapSection;
}
if (current instanceof ListSection) {
ListSection objects = new ListSection((ListSection) current);
objects.setParent(parent, name);
return objects;
}
} else {
if (current instanceof MapSection) {
((MapSection) current).setParent(parent, name);
}
if (current instanceof ListSection) {
((ListSection) current).setParent(parent, name);
}
}
return current;
}
static String toString(Section section) {
DumperOptions options = new DumperOptions();
options.setIndent(4);
options.setAllowUnicode(true);
options.setPrettyFlow(true);
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options);
return yaml.dumpAsMap(section);
}
static MapSection parseToMap(Reader reader) {
Yaml yaml = new Yaml();
return new MapSection(yaml.loadAs(reader, Map.class));
}
Set<String> getKeys();
default Set<String> getPaths() {
Set<String> paths = new HashSet<>();
for (String k : getKeys()) {
Object lookup = getInScope(k).orElse(null);
if (lookup instanceof Section) {
for (String p : ((Section) lookup).getPaths()) {
paths.add(k + "." + p);
}
}
}
return paths;
}
String getName();
default String getPath() {
StringBuilder path = new StringBuilder(getName());
Section parent;
while ((parent = getParent()) != null) {
path.insert(0, parent.getName() + SEPARATOR);
}
return path.toString();
}
Section getParent();
Optional<?> getInScope(String key);
void setInScope(String key, Object value);
default void set(String path, Object value) {
String[] paths = path.split(Pattern.quote(SEPARATOR));
Object lookup = this;
for (int i = 0; i < paths.length - 1; i++) {
Section oldLookup = (Section) lookup;
lookup = oldLookup.getInScope(paths[i]).orElse(null);
if (!(lookup instanceof Section)) {
oldLookup.setInScope(paths[i], lookup = new MapSection());
}
}
if (paths.length > 0) {
((Section) lookup).setInScope(paths[paths.length - 1], value);
}
}
default Optional<?> getObject(String path) {
String[] paths = path.split(Pattern.quote(SEPARATOR));
Object lookup = this;
for (String s : paths) {
if (lookup instanceof Section) {
lookup = ((Section) lookup).getInScope(s).orElse(null);
} else {
return Optional.empty();
}
}
return Optional.ofNullable(lookup);
}
default <T extends Enum<T>> Optional<T> getEnum(String path, Class<T> clz) {
return getObject(path).map(o -> {
try {
return Enum.valueOf(clz, String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
default Optional<String> getString(String path) {
return getObject(path).map(String::valueOf);
}
default Optional<Integer> getInteger(String path) {
return getObject(path).map(o -> {
try {
return Integer.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
default Optional<Double> getDouble(String path) {
return getObject(path).map(o -> {
try {
return Double.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
default Optional<Long> getLong(String path) {
return getObject(path).map(o -> {
try {
return Long.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
default Optional<Float> getFloat(String path) {
return getObject(path).map(o -> {
try {
return Float.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
default Optional<Short> getShort(String path) {
return getObject(path).map(o -> {
try {
return Short.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
default Optional<Byte> getByte(String path) {
return getObject(path).map(o -> {
try {
return Byte.valueOf(String.valueOf(o));
} catch (Throwable t) {
return null;
}
});
}
default Optional<Character> getCharacter(String path) {
return getObject(path).map(o -> {
String text = String.valueOf(o);
return text.isEmpty() ? null : text.charAt(0);
});
}
default Optional<Boolean> getBoolean(String path) {
return getObject(path).map(o -> {
String text = String.valueOf(o);
return text.equals("true") ? Boolean.TRUE : text.equals("false") ? Boolean.FALSE : null;
});
}
default Optional<MapSection> getMap(String path) {
return getObject(path).map(o -> {
if (o instanceof Map) {
if (o instanceof MapSection) return (MapSection) o;
MapSection mapSection = new MapSection((Map<?, ?>) o);
mapSection.setParent(this, Section.getName(path));
return mapSection;
}
return null;
});
}
default boolean isList(String path) {
Optional<?> o = getObject(path);
return o.isPresent() && o.get() instanceof List;
}
default Optional<ListSection> getList(String path) {
return getObject(path).map(o -> {
if (o instanceof List) {
if (o instanceof ListSection) {
return (ListSection) o;
}
ListSection list = new ListSection((List<?>) o);
list.setParent(this, Section.getName(path));
return list;
}
ListSection list = new ListSection(Collections.singleton(o));
list.setParent(this, Section.getName(path));
return list;
});
}
}

View File

@ -14,5 +14,6 @@ translation.locales=Locales
settings.path.title=Locales Directory
settings.path.text=Locales directory
settings.path.file-pattern=Translation file pattern
settings.path.prefix=Path prefix
settings.preview=Preview locale
settings.editor.assistance=I18n key completion and annotation inside editor