mirror of
https://github.com/CarmJos/EasyConfiguration.git
synced 2026-06-04 10:38:19 +08:00
feat: Optimized comments & sections behavior
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
<maven.compiler.target>${project.jdk.version}</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
|
||||
<deps.yamlcommentwriter.version>1.1.0</deps.yamlcommentwriter.version>
|
||||
</properties>
|
||||
|
||||
<artifactId>easyconfiguration-yaml</artifactId>
|
||||
@@ -56,6 +57,13 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cc.carm.lib</groupId>
|
||||
<artifactId>yamlcommentwriter</artifactId>
|
||||
<version>${deps.yamlcommentwriter.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
|
||||
+19
-7
@@ -1,6 +1,6 @@
|
||||
package cc.carm.lib.configuration.source.yaml;
|
||||
|
||||
import cc.carm.lib.configuration.commentable.CommentableMetaTypes;
|
||||
import cc.carm.lib.configuration.commentable.CommentableMeta;
|
||||
import cc.carm.lib.configuration.source.ConfigurationHolder;
|
||||
import cc.carm.lib.configuration.source.file.FileConfigFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -9,8 +9,7 @@ import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class YAMLConfigFactory extends FileConfigFactory<YAMLSource, ConfigurationHolder<YAMLSource>, YAMLConfigFactory> {
|
||||
@@ -60,17 +59,30 @@ public class YAMLConfigFactory extends FileConfigFactory<YAMLSource, Configurati
|
||||
return option(YAMLOptions.DUMPER, modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the header comments for the configuration file.
|
||||
* <p> This will override any existing header comments.
|
||||
*
|
||||
* @param header The header comments to set
|
||||
* @return The current factory instance
|
||||
*/
|
||||
public YAMLConfigFactory header(@NotNull String... header) {
|
||||
return metadata(null, holder -> holder.set(CommentableMeta.HEADER, Arrays.asList(header)));
|
||||
}
|
||||
|
||||
public YAMLConfigFactory footer(@NotNull String... footer) {
|
||||
return metadata(null, holder -> holder.set(CommentableMeta.FOOTER, Arrays.asList(footer)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ConfigurationHolder<YAMLSource> build() {
|
||||
|
||||
File configFile = this.file;
|
||||
String sourcePath = this.resourcePath;
|
||||
|
||||
CommentableMetaTypes.register(this.initializer); // Register commentable meta types
|
||||
CommentableMeta.register(this.initializer); // Register commentable meta types
|
||||
|
||||
return new ConfigurationHolder<YAMLSource>(
|
||||
this.adapters, this.options, new HashMap<>(), this.initializer
|
||||
) {
|
||||
return new ConfigurationHolder<YAMLSource>(this.adapters, this.options, this.metadata, this.initializer) {
|
||||
final @NotNull YAMLSource source = new YAMLSource(this, configFile, sourcePath);
|
||||
|
||||
@Override
|
||||
|
||||
+27
-12
@@ -6,24 +6,39 @@ import org.yaml.snakeyaml.LoaderOptions;
|
||||
|
||||
public interface YAMLOptions {
|
||||
|
||||
|
||||
/**
|
||||
* The {@link LoaderOptions} for SnakeYAML.
|
||||
*
|
||||
* @see LoaderOptions
|
||||
*/
|
||||
ConfigurationOption<LoaderOptions> LOADER = ConfigurationOption.of(() -> {
|
||||
LoaderOptions loaderOptions = new LoaderOptions();
|
||||
LoaderOptions opt = new LoaderOptions();
|
||||
|
||||
// As we handle comments ourselves,
|
||||
// we don't want SnakeYAML to read them when loading the configs.
|
||||
loaderOptions.setProcessComments(false);
|
||||
loaderOptions.setMaxAliasesForCollections(100); // 100 aliases
|
||||
loaderOptions.setCodePointLimit(5 * 1024 * 1024); // 5MB
|
||||
return loaderOptions;
|
||||
opt.setProcessComments(false);
|
||||
|
||||
opt.setMaxAliasesForCollections(100); // 100 aliases
|
||||
opt.setCodePointLimit(5 * 1024 * 1024); // 5MB
|
||||
return opt;
|
||||
});
|
||||
|
||||
/**
|
||||
* The {@link DumperOptions} for SnakeYAML.
|
||||
*
|
||||
* @see DumperOptions
|
||||
*/
|
||||
ConfigurationOption<DumperOptions> DUMPER = ConfigurationOption.of(() -> {
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setIndent(2);
|
||||
options.setWidth(120);
|
||||
options.setProcessComments(true);
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
return options;
|
||||
DumperOptions opt = new DumperOptions();
|
||||
|
||||
// As we handle comments ourselves,
|
||||
// we don't want SnakeYAML to read them when saving the configs.
|
||||
opt.setProcessComments(false);
|
||||
|
||||
opt.setIndent(2);
|
||||
opt.setWidth(120);
|
||||
opt.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
return opt;
|
||||
});
|
||||
|
||||
|
||||
|
||||
+100
-45
@@ -1,29 +1,33 @@
|
||||
package cc.carm.lib.configuration.source.yaml;
|
||||
|
||||
import cc.carm.lib.configuration.commentable.CommentableMetaTypes;
|
||||
import cc.carm.lib.configuration.commentable.CommentableMeta;
|
||||
import cc.carm.lib.configuration.option.CommentableOptions;
|
||||
import cc.carm.lib.configuration.source.ConfigurationHolder;
|
||||
import cc.carm.lib.configuration.source.file.FileConfigSource;
|
||||
import cc.carm.lib.configuration.source.meta.ConfigurationMetadata;
|
||||
import cc.carm.lib.configuration.source.option.StandardOptions;
|
||||
import cc.carm.lib.configuration.source.section.ConfigureSection;
|
||||
import cc.carm.lib.configuration.source.section.MemorySection;
|
||||
import cc.carm.lib.yamlcommentupdater.CommentedSection;
|
||||
import cc.carm.lib.yamlcommentupdater.CommentedYAMLWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.comments.CommentLine;
|
||||
import org.yaml.snakeyaml.comments.CommentType;
|
||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||
import org.yaml.snakeyaml.nodes.*;
|
||||
import org.yaml.snakeyaml.reader.UnicodeReader;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLSource> {
|
||||
public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLSource> implements CommentedSection {
|
||||
|
||||
protected final @NotNull YamlConstructor yamlConstructor;
|
||||
protected final @NotNull YamlRepresenter yamlRepresenter;
|
||||
@@ -65,6 +69,11 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLS
|
||||
return Objects.requireNonNull(this.rootSection, "Root section is not initialized.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public char separator() {
|
||||
return holder().options().get(StandardOptions.PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
public @NotNull LoaderOptions loaderOptions() {
|
||||
return holder().options().get(YAMLOptions.LOADER);
|
||||
}
|
||||
@@ -77,54 +86,23 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLS
|
||||
private MappingNode toNodeTree(@NotNull final ConfigureSection section) {
|
||||
List<NodeTuple> nodeTuples = new ArrayList<>();
|
||||
for (final Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||
|
||||
final Node keyNode = this.yaml.represent(entry.getKey());
|
||||
final Node valueNode;
|
||||
Node keyNode = this.yaml.represent(entry.getKey());
|
||||
Node valueNode;
|
||||
if (entry.getValue() instanceof ConfigureSection) {
|
||||
valueNode = this.toNodeTree((ConfigureSection) entry.getValue());
|
||||
} else {
|
||||
valueNode = this.yaml.represent(entry.getValue());
|
||||
}
|
||||
|
||||
keyNode.setBlockComments(buildComments(CommentType.BLOCK, CommentableMetaTypes.HEADER_COMMENTS, entry.getKey()));
|
||||
if (valueNode instanceof MappingNode || valueNode instanceof SequenceNode) {
|
||||
keyNode.setInLineComments(buildComment(CommentType.IN_LINE, CommentableMetaTypes.INLINE_COMMENT, entry.getKey()));
|
||||
} else {
|
||||
valueNode.setInLineComments(buildComment(CommentType.IN_LINE, CommentableMetaTypes.INLINE_COMMENT, entry.getKey()));
|
||||
}
|
||||
// keyNode.setEndComments(buildComments(CommentType.BLOCK, CommentableMetaTypes.FOOTER_COMMENTS, entry.getKey()));
|
||||
|
||||
nodeTuples.add(new NodeTuple(keyNode, valueNode));
|
||||
}
|
||||
|
||||
return new MappingNode(Tag.MAP, nodeTuples, DumperOptions.FlowStyle.BLOCK);
|
||||
}
|
||||
|
||||
public List<CommentLine> buildComments(@NotNull CommentType type, @NotNull ConfigurationMetadata<List<String>> meta,
|
||||
@Nullable String path) {
|
||||
List<String> comments = holder.metadata(path).get(meta);
|
||||
if (comments == null) return Collections.emptyList();
|
||||
return comments.stream().map(s -> {
|
||||
if (s.isEmpty()) return new CommentLine(null, null, "", CommentType.BLANK_LINE);
|
||||
else return new CommentLine(null, null, s, type);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<CommentLine> buildComment(@NotNull CommentType type, @NotNull ConfigurationMetadata<String> meta,
|
||||
@Nullable String path) {
|
||||
String comment = holder.metadata(path).get(meta);
|
||||
if (comment == null || comment.isEmpty()) return Collections.emptyList();
|
||||
return Collections.singletonList(new CommentLine(null, null, comment, type));
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public String saveToString() {
|
||||
|
||||
MappingNode mappingNode = this.toNodeTree(this);
|
||||
// mappingNode.setBlockComments(this.getCommentLines(this.saveHeader(this.getOptions().getHeader()), CommentType.BLOCK));
|
||||
// mappingNode.setEndComments(this.getCommentLines(this.getOptions().getFooter(), CommentType.BLOCK));
|
||||
|
||||
public String saveToString(ConfigureSection section) {
|
||||
MappingNode mappingNode = this.toNodeTree(section);
|
||||
StringWriter writer = new StringWriter();
|
||||
if ((mappingNode.getBlockComments() == null || mappingNode.getBlockComments().isEmpty())
|
||||
&& (mappingNode.getEndComments() == null || mappingNode.getEndComments().isEmpty())
|
||||
@@ -137,13 +115,21 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLS
|
||||
}
|
||||
this.yaml.serialize(mappingNode, writer);
|
||||
}
|
||||
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() throws Exception {
|
||||
fileWriter(w -> w.write(saveToString()));
|
||||
CommentedYAMLWriter writer = new CommentedYAMLWriter(
|
||||
String.valueOf(this.separator()),
|
||||
dumperOptions().getIndent(),
|
||||
holder.options().get(CommentableOptions.COMMENT_EMPTY_VALUE)
|
||||
);
|
||||
try {
|
||||
fileWriter(w -> w.write(writer.saveToString(this)));
|
||||
} catch (Exception ex) {
|
||||
fileWriter(w -> w.write(saveToString(section())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,6 +137,11 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLS
|
||||
this.rootSection = fileReadString(this::loadFromString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.saveToString(section());
|
||||
}
|
||||
|
||||
public @NotNull MemorySection loadFromString(@NotNull String data) throws Exception {
|
||||
MappingNode mappingNode;
|
||||
try (Reader reader = new UnicodeReader(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))) {
|
||||
@@ -186,6 +177,70 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLS
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serializeValue(@NotNull String key, @NotNull Object value) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put(key, value);
|
||||
return saveToString(MemorySection.root(this, map));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Set<String> getKeys(@Nullable String sectionKey, boolean deep) {
|
||||
if (sectionKey == null) return section().getKeys(deep);
|
||||
ConfigureSection sub = section().getSection(sectionKey);
|
||||
if (sub == null) return null;
|
||||
return sub.getKeys(deep);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getValue(@NotNull String key) {
|
||||
return get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getInlineComment(@NotNull String key) {
|
||||
String comment = getInlineComment(key, null);
|
||||
if (comment != null) return comment;
|
||||
|
||||
String sep = String.valueOf(separator());
|
||||
|
||||
// If the comment is not found, try to get the comment from the parent section
|
||||
String[] keys = key.split(sep);
|
||||
if (keys.length == 1) return null;
|
||||
|
||||
// Try every possible parent key&child key combination
|
||||
for (int i = 1; i < keys.length; i++) {
|
||||
String parentKey = String.join(sep, Arrays.copyOfRange(keys, 0, i));
|
||||
String childKey = String.join(sep, Arrays.copyOfRange(keys, i, keys.length));
|
||||
comment = getInlineComment(childKey, parentKey);
|
||||
if (comment != null) return comment;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable String getInlineComment(@NotNull String key, @Nullable String sectionKey) {
|
||||
Map<String, String> pathComment = holder().metadata(key).get(CommentableMeta.INLINE);
|
||||
if (pathComment == null || pathComment.isEmpty()) return null;
|
||||
if (sectionKey == null) return pathComment.get(null);
|
||||
|
||||
for (Map.Entry<String, String> entry : pathComment.entrySet()) {
|
||||
if (entry.getKey().equals(sectionKey)) return entry.getValue();
|
||||
Pattern pattern = Pattern.compile(entry.getKey().replace(".", "\\.").replace("*", ".*"));
|
||||
if (pattern.matcher(sectionKey).matches()) return entry.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> getHeaderComments(@Nullable String key) {
|
||||
return holder().metadata(key).get(CommentableMeta.HEADER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> getFooterComments(@Nullable String key) {
|
||||
return holder().metadata(key).get(CommentableMeta.FOOTER);
|
||||
}
|
||||
|
||||
public static class YamlRepresenter extends Representer {
|
||||
|
||||
public YamlRepresenter(@NotNull final DumperOptions dumperOptions) {
|
||||
@@ -213,7 +268,7 @@ public class YAMLSource extends FileConfigSource<MemorySection, Map<?, ?>, YAMLS
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void flattenMapping(@NotNull final MappingNode mappingNode) {
|
||||
public void flattenMapping(@NotNull final MappingNode mappingNode) {
|
||||
super.flattenMapping(mappingNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package sample;
|
||||
|
||||
import cc.carm.lib.configuration.Configuration;
|
||||
import cc.carm.lib.configuration.annotation.ConfigPath;
|
||||
import cc.carm.lib.configuration.annotation.HeaderComment;
|
||||
import cc.carm.lib.configuration.annotation.HeaderComments;
|
||||
import cc.carm.lib.configuration.annotation.InlineComment;
|
||||
import cc.carm.lib.configuration.value.standard.ConfiguredList;
|
||||
import cc.carm.lib.configuration.value.standard.ConfiguredValue;
|
||||
@@ -10,12 +10,15 @@ import cc.carm.lib.configuration.value.standard.ConfiguredValue;
|
||||
import java.util.UUID;
|
||||
|
||||
@ConfigPath(root = true)
|
||||
@HeaderComment("Configurations for sample")
|
||||
@HeaderComments("Configurations for sample")
|
||||
public interface SampleConfig extends Configuration {
|
||||
|
||||
@InlineComment("Enabled?") // Inline comment
|
||||
ConfiguredValue<Boolean> ENABLED = ConfiguredValue.of(true);
|
||||
|
||||
@HeaderComments("Server configurations") // Header comment
|
||||
ConfiguredValue<Integer> PORT = ConfiguredValue.of(Integer.class);
|
||||
|
||||
ConfiguredList<UUID> UUIDS = ConfiguredList.builderOf(UUID.class).fromString()
|
||||
.parse(UUID::fromString).serialize(UUID::toString)
|
||||
.defaults(
|
||||
@@ -26,11 +29,12 @@ public interface SampleConfig extends Configuration {
|
||||
@ConfigPath("info") // Custom path
|
||||
interface INFO extends Configuration {
|
||||
|
||||
@HeaderComment("Configure your name!") // Header comment
|
||||
@HeaderComments("Configure your name!") // Header comment
|
||||
ConfiguredValue<String> NAME = ConfiguredValue.of("Joker");
|
||||
|
||||
@ConfigPath("year") // Custom path
|
||||
@ConfigPath("how-old-are-you") // Custom path
|
||||
ConfiguredValue<Integer> AGE = ConfiguredValue.of(24);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ public class SampleTest {
|
||||
// 2. Initialize the configuration classes or instances.
|
||||
holder.initialize(SampleConfig.class);
|
||||
// 3. Enjoy using the configuration!
|
||||
System.out.println("Enabled? -> " + SampleConfig.ENABLED.resolve());
|
||||
SampleConfig.ENABLED.set(false);
|
||||
System.out.println("And now? -> " + SampleConfig.ENABLED.resolve());
|
||||
// p.s. Changes not save so enable value will still be true in the next run.
|
||||
|
||||
System.out.println("Your name is " + SampleConfig.INFO.NAME.resolve() + " (age=" + SampleConfig.INFO.AGE.resolve() + ")!");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package yaml.test;
|
||||
|
||||
import cc.carm.lib.configuration.commentable.CommentableMeta;
|
||||
import cc.carm.lib.configuration.demo.tests.ConfigurationTest;
|
||||
import cc.carm.lib.configuration.source.ConfigurationHolder;
|
||||
import cc.carm.lib.configuration.source.yaml.YAMLConfigFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class YamlTests {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ConfigurationHolder<?> holder = YAMLConfigFactory.from("target/tests.yml")
|
||||
.resourcePath("configs/sample.yml").build();
|
||||
|
||||
ConfigurationTest.testDemo(holder);
|
||||
ConfigurationTest.testInner(holder);
|
||||
|
||||
|
||||
Map<String, List<String>> headers = holder.extractMetadata(CommentableMeta.HEADER);
|
||||
System.out.println("Header comments: ");
|
||||
headers.forEach((k, v) -> {
|
||||
if (v.isEmpty()) return;
|
||||
System.out.println("- " + k + ": ");
|
||||
v.forEach(s -> System.out.println("- | " + s));
|
||||
});
|
||||
|
||||
Map<String, List<String>> footers = holder.extractMetadata(CommentableMeta.FOOTER);
|
||||
System.out.println("Footer comments: ");
|
||||
footers.forEach((k, v) -> {
|
||||
if (v.isEmpty()) return;
|
||||
System.out.println("- " + k + ": ");
|
||||
v.forEach(s -> System.out.println("- | " + s));
|
||||
});
|
||||
|
||||
|
||||
ConfigurationTest.save(holder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user