From 57d820f0e078d076b69e5288824f414c0211efc7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 22 Jun 2023 12:36:27 +0200 Subject: [PATCH 01/17] Added support for YAML comments --- .../yaml/YamlConfigurationLoader.java | 6 +- .../configurate/yaml/YamlRepresenter.java | 100 ++++++++++++++++++ .../yaml/YamlConfigurationLoaderTest.java | 24 +++++ .../configurate/yaml/comments-expected.yml | 5 + 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java create mode 100644 format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java index e68418492..6564425ab 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java @@ -29,7 +29,6 @@ import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; -import org.yaml.snakeyaml.representer.Representer; import java.io.BufferedReader; import java.io.Writer; @@ -175,7 +174,8 @@ private YamlConfigurationLoader(final Builder builder) { final DumperOptions opts = builder.options; opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.style)); - this.yaml = ThreadLocal.withInitial(() -> new Yaml(new Constructor(loaderOpts), new Representer(opts), opts, loaderOpts)); + opts.setProcessComments(true); + this.yaml = ThreadLocal.withInitial(() -> new Yaml(new Constructor(loaderOpts), new YamlRepresenter(opts), opts, loaderOpts)); } @Override @@ -185,7 +185,7 @@ protected void loadInternal(final CommentedConfigurationNode node, final Buffere @Override protected void saveInternal(final ConfigurationNode node, final Writer writer) { - this.yaml.get().dump(node.raw(), writer); + this.yaml.get().dump(node, writer); } @Override diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java new file mode 100644 index 000000000..f29723592 --- /dev/null +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java @@ -0,0 +1,100 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.yaml; + +import static org.spongepowered.configurate.loader.AbstractConfigurationLoader.CONFIGURATE_LINE_PATTERN; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.CommentedConfigurationNodeIntermediary; +import org.spongepowered.configurate.ConfigurationNode; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.comments.CommentLine; +import org.yaml.snakeyaml.comments.CommentType; +import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.SequenceNode; +import org.yaml.snakeyaml.nodes.Tag; +import org.yaml.snakeyaml.representer.Represent; +import org.yaml.snakeyaml.representer.Representer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +final class YamlRepresenter extends Representer { + + YamlRepresenter(final DumperOptions options) { + super(options); + multiRepresenters.put(ConfigurationNode.class, new ConfigurationNodeRepresent()); + } + + private final class ConfigurationNodeRepresent implements Represent { + @Override + public Node representData(final Object nodeObject) { + final ConfigurationNode node = (ConfigurationNode) nodeObject; + + final Node yamlNode; + if (node.isMap()) { + final List children = new ArrayList<>(); + for (Map.Entry ent : node.childrenMap().entrySet()) { + // SnakeYAML supports both key as value comments. Add the comments on the key + final Node value = represent(ent.getValue()); + final Node key = represent(String.valueOf(ent.getKey())); + key.setBlockComments(value.getBlockComments()); + value.setBlockComments(Collections.emptyList()); + + children.add(new NodeTuple(key, value)); + } + yamlNode = new MappingNode(Tag.MAP, children, FlowStyle.AUTO); + } else if (node.isList()) { + final List children = new ArrayList<>(); + for (ConfigurationNode ent : node.childrenList()) { + children.add(represent(ent)); + } + yamlNode = new SequenceNode(Tag.SET, children, FlowStyle.AUTO); + } else { + yamlNode = represent(node.rawScalar()); + } + + if (node instanceof CommentedConfigurationNodeIntermediary) { + final @Nullable String nodeComment = ((CommentedConfigurationNodeIntermediary) node).comment(); + if (nodeComment != null) { + yamlNode.setBlockComments( + Arrays.stream(CONFIGURATE_LINE_PATTERN.split(nodeComment)) + .map(this::commentLineFor) + .collect(Collectors.toList()) + ); + } + } + + return yamlNode; + } + + private CommentLine commentLineFor(final String comment) { + // prepend a space before the comment: + // before: #hello + // after: # hello + return new CommentLine(null, null, " " + comment, CommentType.BLOCK); + } + } + +} diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index 0c74f0165..205cff858 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -105,6 +105,30 @@ void testWriteBasicFile(final @TempDir Path tempDir) throws ConfigurateException assertEquals(readLines(this.getClass().getResource("write-expected.yml")), Files.readAllLines(target, StandardCharsets.UTF_8)); } + @Test + void testWriteComments(final @TempDir Path tempDir) throws IOException { + final Path target = tempDir.resolve("comments-actual.yml"); + final ConfigurationNode node = CommentedConfigurationNode.root(n -> + n.node("pizza") + .comment("john's") + .act(p -> + p.node("pineapple") + .set(true) + .comment("who doesn't love a good pineapple\non a pizza??"))); + + final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .path(target) + .nodeStyle(NodeStyle.BLOCK) + .build(); + + loader.save(node); + + assertEquals( + readLines(this.getClass().getResource("comments-expected.yml")), + Files.readAllLines(target, StandardCharsets.UTF_8) + ); + } + private static List readLines(final URL source) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(source.openStream(), StandardCharsets.UTF_8))) { return reader.lines().collect(Collectors.toList()); diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml new file mode 100644 index 000000000..2e5a5f512 --- /dev/null +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml @@ -0,0 +1,5 @@ +# john's +pizza: + # who doesn't love pineapple + # on a pizza?? + pineapple: true From a0901bf009a2321528aec3f3973d8f57613be1cd Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 22 Jun 2023 13:22:54 +0200 Subject: [PATCH 02/17] Changed the example and changed the sequence node tag --- .../configurate/yaml/YamlRepresenter.java | 4 ++-- .../yaml/YamlConfigurationLoaderTest.java | 14 +++++++++----- .../configurate/yaml/comments-expected.yml | 14 +++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java index f29723592..7945aef78 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java @@ -56,7 +56,7 @@ public Node representData(final Object nodeObject) { if (node.isMap()) { final List children = new ArrayList<>(); for (Map.Entry ent : node.childrenMap().entrySet()) { - // SnakeYAML supports both key as value comments. Add the comments on the key + // SnakeYAML supports both key and value comments. Add the comments on the key final Node value = represent(ent.getValue()); final Node key = represent(String.valueOf(ent.getKey())); key.setBlockComments(value.getBlockComments()); @@ -70,7 +70,7 @@ public Node representData(final Object nodeObject) { for (ConfigurationNode ent : node.childrenList()) { children.add(represent(ent)); } - yamlNode = new SequenceNode(Tag.SET, children, FlowStyle.AUTO); + yamlNode = new SequenceNode(Tag.SEQ, children, FlowStyle.AUTO); } else { yamlNode = represent(node.rawScalar()); } diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index 205cff858..29f727c82 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -109,12 +109,16 @@ void testWriteBasicFile(final @TempDir Path tempDir) throws ConfigurateException void testWriteComments(final @TempDir Path tempDir) throws IOException { final Path target = tempDir.resolve("comments-actual.yml"); final ConfigurationNode node = CommentedConfigurationNode.root(n -> - n.node("pizza") - .comment("john's") + n.node("waffles-with-syrup") + .comment("hello world") .act(p -> - p.node("pineapple") - .set(true) - .comment("who doesn't love a good pineapple\non a pizza??"))); + p.node("ingredients") + .comment("multi-line\ncomments") + .act(i -> { + i.appendListNode().set("waffles").comment("would you've guessed the ingredients?"); + i.appendListNode().set("syrup").comment("I certainly didn't"); + }) + )); final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() .path(target) diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml index 2e5a5f512..8d02ab2b1 100644 --- a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml @@ -1,5 +1,9 @@ -# john's -pizza: - # who doesn't love pineapple - # on a pizza?? - pineapple: true +# hello world +waffles-with-syrup: + # multi-line + # comments + ingredients: + # would you've guessed the ingredients? + - waffles + # I certainly didn't + - syrup From 8b06a98a5f16d78e15db421e9ebceffab891c199 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 24 Jun 2023 21:29:18 +0200 Subject: [PATCH 03/17] Added support for reading comments --- .../yaml/YamlConfigurationLoader.java | 13 ++- .../configurate/yaml/YamlConstructor.java | 110 ++++++++++++++++++ .../yaml/YamlConfigurationLoaderTest.java | 46 +++++++- ...omments-expected.yml => comments-test.yml} | 0 4 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java rename format/yaml/src/test/resources/org/spongepowered/configurate/yaml/{comments-expected.yml => comments-test.yml} (100%) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java index 6564425ab..8b701682e 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java @@ -28,7 +28,6 @@ import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; import java.io.BufferedReader; import java.io.Writer; @@ -163,24 +162,30 @@ public YamlConfigurationLoader build() { } } + private final ThreadLocal constructor; private final ThreadLocal yaml; private YamlConfigurationLoader(final Builder builder) { super(builder, new CommentHandler[] {CommentHandlers.HASH}); final LoaderOptions loaderOpts = new LoaderOptions() .setAcceptTabs(true) - .setProcessComments(false); + .setProcessComments(true); loaderOpts.setCodePointLimit(Integer.MAX_VALUE); final DumperOptions opts = builder.options; opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.style)); opts.setProcessComments(true); - this.yaml = ThreadLocal.withInitial(() -> new Yaml(new Constructor(loaderOpts), new YamlRepresenter(opts), opts, loaderOpts)); + // the constructor needs ConfigurationOptions, which is only available when called (loadInternal) + this.constructor = ThreadLocal.withInitial(() -> new YamlConstructor(loaderOpts)); + this.yaml = ThreadLocal.withInitial(() -> new Yaml(this.constructor.get(), new YamlRepresenter(opts), opts, loaderOpts)); } @Override protected void loadInternal(final CommentedConfigurationNode node, final BufferedReader reader) { - node.raw(this.yaml.get().load(reader)); + // the constructor needs ConfigurationOptions for the to be created nodes + // and since it's a thread-local, this won't cause any issues + this.constructor.get().options = node.options(); + node.from(this.yaml.get().load(reader)); } @Override diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java new file mode 100644 index 000000000..5ae4876d8 --- /dev/null +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -0,0 +1,110 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.yaml; + +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.comments.CommentLine; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeTuple; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +class YamlConstructor extends Constructor { + + @Nullable ConfigurationOptions options; + + YamlConstructor(final LoaderOptions loadingConfig) { + super(loadingConfig); + } + + @Override + @EnsuresNonNull("options") + public Object getSingleData(final Class type) { + if (this.options == null) { + throw new IllegalStateException("options must be set before calling load!"); + } + return super.getSingleData(type); + } + + @Override + protected Object constructObjectNoCheck(final Node yamlNode) { + final Object raw = super.constructObjectNoCheck(yamlNode); + //noinspection DataFlowIssue guarenteed NonNull by getSingleData, which load(Reader) uses + final CommentedConfigurationNode node = CommentedConfigurationNode.root(this.options); + + // SnakeYAML uses a LinkedHashMap to preserve key order + if (raw instanceof LinkedHashMap) { + // make sure to mark it as a map type, even if the map itself is empty + node.raw(Collections.emptyMap()); + + // Map is always a MappingNode + final List tuples = ((MappingNode) yamlNode).getValue(); + + // comments are on the key, but SnakeYAML uses strings as key, so we have to be a bit creative + final AtomicInteger index = new AtomicInteger(); + ((LinkedHashMap) raw).forEach((key, value) -> { + node.node(key) + .from((ConfigurationNode) value) + .comment(commentFor(tuples.get(index.getAndIncrement()).getKeyNode().getBlockComments())); + }); + } else if (raw instanceof Collection) { + // make sure to mark it as a list type, even if the collection itself is empty + node.raw(Collections.emptyList()); + + ((Collection) raw).forEach(value -> { + node.appendListNode().from((ConfigurationNode) value); + }); + } else { + node.raw(raw); + } + + if (yamlNode.getBlockComments() != null) { + node.comment(commentFor(yamlNode.getBlockComments())); + } + + return node; + } + + private static @Nullable String commentFor(final @Nullable List commentLines) { + if (commentLines == null || commentLines.isEmpty()) { + return null; + } + return commentLines.stream() + .map(input -> { + final String lineStripped = input.getValue().replace("\r", ""); + if (!lineStripped.isEmpty() && lineStripped.charAt(0) == ' ') { + return lineStripped.substring(1); + } else { + return lineStripped; + } + }) + .collect(Collectors.joining("\n")); + } + +} diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index 29f727c82..b8c61d924 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -105,9 +105,31 @@ void testWriteBasicFile(final @TempDir Path tempDir) throws ConfigurateException assertEquals(readLines(this.getClass().getResource("write-expected.yml")), Files.readAllLines(target, StandardCharsets.UTF_8)); } + @Test + void testReadComments(final @TempDir Path tempDir) throws IOException { + final ConfigurationNode expected = CommentedConfigurationNode.root(n -> + n.node("waffles-with-syrup") + .comment("hello world") + .act(p -> + p.node("ingredients") + .comment("multi-line\ncomments") + .act(i -> { + i.appendListNode().set("waffles").comment("would you've guessed the ingredients?"); + i.appendListNode().set("syrup").comment("I certainly didn't"); + }) + )); + + final URL url = this.getClass().getResource("comments-test.yml"); + final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .url(url).build(); + + final ConfigurationNode actual = loader.load(); + assertEquals(expected, actual); + } + @Test void testWriteComments(final @TempDir Path tempDir) throws IOException { - final Path target = tempDir.resolve("comments-actual.yml"); + final Path target = tempDir.resolve("comments-write.yml"); final ConfigurationNode node = CommentedConfigurationNode.root(n -> n.node("waffles-with-syrup") .comment("hello world") @@ -128,11 +150,31 @@ void testWriteComments(final @TempDir Path tempDir) throws IOException { loader.save(node); assertEquals( - readLines(this.getClass().getResource("comments-expected.yml")), + readLines(this.getClass().getResource("comments-test.yml")), Files.readAllLines(target, StandardCharsets.UTF_8) ); } + @Test + void testReadWriteComments(final @TempDir Path tempDir) throws IOException { + final URL source = this.getClass().getResource("comments-test.yml"); + final Path destination = tempDir.resolve("comments-readwrite.yml"); + + final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .path(destination) + .url(source) + .nodeStyle(NodeStyle.BLOCK) + .build(); + + final ConfigurationNode sourceNode = loader.load(); + loader.save(sourceNode); + + assertEquals( + readLines(source), + Files.readAllLines(destination, StandardCharsets.UTF_8) + ); + } + private static List readLines(final URL source) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(source.openStream(), StandardCharsets.UTF_8))) { return reader.lines().collect(Collectors.toList()); diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-test.yml similarity index 100% rename from format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-expected.yml rename to format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-test.yml From 4b79298bf3fd82e126d55a8b8a6c1cbbb22e66a1 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 25 Jun 2023 00:12:32 +0200 Subject: [PATCH 04/17] Improved getting mapping key comments --- .../configurate/yaml/YamlConstructor.java | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java index 5ae4876d8..2b1cb143a 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -26,13 +26,12 @@ import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; -import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.NodeId; +import org.yaml.snakeyaml.nodes.ScalarNode; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; class YamlConstructor extends Constructor { @@ -54,26 +53,29 @@ public Object getSingleData(final Class type) { @Override protected Object constructObjectNoCheck(final Node yamlNode) { - final Object raw = super.constructObjectNoCheck(yamlNode); //noinspection DataFlowIssue guarenteed NonNull by getSingleData, which load(Reader) uses final CommentedConfigurationNode node = CommentedConfigurationNode.root(this.options); - // SnakeYAML uses a LinkedHashMap to preserve key order - if (raw instanceof LinkedHashMap) { + if (yamlNode.getNodeId() == NodeId.mapping) { // make sure to mark it as a map type, even if the map itself is empty node.raw(Collections.emptyMap()); - // Map is always a MappingNode - final List tuples = ((MappingNode) yamlNode).getValue(); + ((MappingNode) yamlNode).getValue().forEach(tuple -> { + // I don't think it's possible to have a non-scalar node as key + final ScalarNode keyNode = (ScalarNode) tuple.getKeyNode(); + final Node valueNode = tuple.getValueNode(); - // comments are on the key, but SnakeYAML uses strings as key, so we have to be a bit creative - final AtomicInteger index = new AtomicInteger(); - ((LinkedHashMap) raw).forEach((key, value) -> { - node.node(key) - .from((ConfigurationNode) value) - .comment(commentFor(tuples.get(index.getAndIncrement()).getKeyNode().getBlockComments())); + // comments are on the key, not the value + node.node(keyNode.getValue()) + .from((ConfigurationNode) constructObject(valueNode)) + .comment(commentFor(keyNode.getBlockComments())); }); - } else if (raw instanceof Collection) { + + return node.comment(commentFor(yamlNode.getBlockComments())); + } + + final Object raw = super.constructObjectNoCheck(yamlNode); + if (raw instanceof Collection) { // make sure to mark it as a list type, even if the collection itself is empty node.raw(Collections.emptyList()); @@ -84,11 +86,7 @@ protected Object constructObjectNoCheck(final Node yamlNode) { node.raw(raw); } - if (yamlNode.getBlockComments() != null) { - node.comment(commentFor(yamlNode.getBlockComments())); - } - - return node; + return node.comment(commentFor(yamlNode.getBlockComments())); } private static @Nullable String commentFor(final @Nullable List commentLines) { From 90ee1edc2d767c1f9939b7d730b6ccf8b022b07a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 29 Jul 2023 19:28:56 +0200 Subject: [PATCH 05/17] Correctly remove all line breaks from comments when reading comments --- .../configurate/yaml/YamlConstructor.java | 13 ++++++++++++- .../yaml/YamlConfigurationLoaderTest.java | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java index 2b1cb143a..2622de0d2 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -32,10 +32,13 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; import java.util.stream.Collectors; class YamlConstructor extends Constructor { + private static final Pattern LINE_BREAK_PATTERN; + @Nullable ConfigurationOptions options; YamlConstructor(final LoaderOptions loadingConfig) { @@ -95,7 +98,7 @@ protected Object constructObjectNoCheck(final Node yamlNode) { } return commentLines.stream() .map(input -> { - final String lineStripped = input.getValue().replace("\r", ""); + final String lineStripped = removeLineBreaksForLine(input.getValue()); if (!lineStripped.isEmpty() && lineStripped.charAt(0) == ' ') { return lineStripped.substring(1); } else { @@ -105,4 +108,12 @@ protected Object constructObjectNoCheck(final Node yamlNode) { .collect(Collectors.joining("\n")); } + static { + LINE_BREAK_PATTERN = Pattern.compile("\\R"); + } + + private static String removeLineBreaksForLine(final String line) { + return LINE_BREAK_PATTERN.matcher(line).replaceAll(""); + } + } diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index b8c61d924..f7d10c1cf 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -106,7 +106,7 @@ void testWriteBasicFile(final @TempDir Path tempDir) throws ConfigurateException } @Test - void testReadComments(final @TempDir Path tempDir) throws IOException { + void testReadComments() throws IOException { final ConfigurationNode expected = CommentedConfigurationNode.root(n -> n.node("waffles-with-syrup") .comment("hello world") From 1d885c3166dffbd8223d49fae6624be449b3e15c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 29 Jul 2023 20:01:42 +0200 Subject: [PATCH 06/17] Minor change --- .../org/spongepowered/configurate/yaml/YamlConstructor.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java index 2622de0d2..eae5a7f04 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -37,7 +37,7 @@ class YamlConstructor extends Constructor { - private static final Pattern LINE_BREAK_PATTERN; + private static final Pattern LINE_BREAK_PATTERN = Pattern.compile("\\R"); @Nullable ConfigurationOptions options; @@ -108,10 +108,6 @@ protected Object constructObjectNoCheck(final Node yamlNode) { .collect(Collectors.joining("\n")); } - static { - LINE_BREAK_PATTERN = Pattern.compile("\\R"); - } - private static String removeLineBreaksForLine(final String line) { return LINE_BREAK_PATTERN.matcher(line).replaceAll(""); } From fb49dd932382aff7ba112c91d2f903f8e8da7c19 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 29 Jul 2023 20:46:24 +0200 Subject: [PATCH 07/17] Added an option to disable handling comments --- .../yaml/YamlConfigurationLoader.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java index 8b701682e..f594f8c3e 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java @@ -70,6 +70,8 @@ public static Builder builder() { *
*
<prefix>.yaml.node-style
*
Equivalent to {@link #nodeStyle(NodeStyle)}
+ *
<prefix>.yaml.comments-enabled
+ *
Equivalent to {@link #commentsEnabled(boolean)}
*
* * @since 4.0.0 @@ -77,6 +79,7 @@ public static Builder builder() { public static final class Builder extends AbstractConfigurationLoader.Builder { private final DumperOptions options = new DumperOptions(); private @Nullable NodeStyle style; + private boolean enableComments; Builder() { this.indent(4); @@ -90,6 +93,7 @@ protected void populate(final LoaderOptionSource options) { if (declared != null) { this.style = declared; } + this.enableComments = options.getBoolean(true, "yaml", "comments-enabled"); } /** @@ -156,6 +160,34 @@ public Builder nodeStyle(final @Nullable NodeStyle style) { return this.style; } + /** + * Set whether comment handling is enabled on this loader. + * + *

When comment handling is enabled, comments will be read from files + * and written back to files where possible.

+ * + *

The default value is {@code true}

+ * + * @param enableComments whether comment handling should be enabled + * @return this builder (for chaining) + * @since 4.2.0 + */ + public Builder commentsEnabled(final boolean enableComments) { + this.enableComments = enableComments; + return this; + } + + /** + * Get whether comment handling is enabled. + * + * @return whether comment handling is enabled + * @see #commentsEnabled(boolean) for details on comment handling + * @since 4.2.0 + */ + public boolean commentsEnabled() { + return this.enableComments; + } + @Override public YamlConfigurationLoader build() { return new YamlConfigurationLoader(this); @@ -169,12 +201,12 @@ private YamlConfigurationLoader(final Builder builder) { super(builder, new CommentHandler[] {CommentHandlers.HASH}); final LoaderOptions loaderOpts = new LoaderOptions() .setAcceptTabs(true) - .setProcessComments(true); + .setProcessComments(builder.commentsEnabled()); loaderOpts.setCodePointLimit(Integer.MAX_VALUE); final DumperOptions opts = builder.options; opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.style)); - opts.setProcessComments(true); + opts.setProcessComments(builder.commentsEnabled()); // the constructor needs ConfigurationOptions, which is only available when called (loadInternal) this.constructor = ThreadLocal.withInitial(() -> new YamlConstructor(loaderOpts)); this.yaml = ThreadLocal.withInitial(() -> new Yaml(this.constructor.get(), new YamlRepresenter(opts), opts, loaderOpts)); From cc14b5c0c6aab502ae0a098ff08626ce554ddf01 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 14 Oct 2023 18:30:32 -0700 Subject: [PATCH 08/17] fix(format/yaml): Test and fix for complex keys --- .../configurate/yaml/YamlConstructor.java | 7 ++-- .../yaml/YamlConfigurationLoaderTest.java | 37 +++++++++++++++---- .../configurate/yaml/complex-keys.yaml | 4 ++ 3 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 format/yaml/src/test/resources/org/spongepowered/configurate/yaml/complex-keys.yaml diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java index eae5a7f04..1f944a278 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -27,7 +27,6 @@ import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeId; -import org.yaml.snakeyaml.nodes.ScalarNode; import java.util.Collection; import java.util.Collections; @@ -65,13 +64,13 @@ protected Object constructObjectNoCheck(final Node yamlNode) { ((MappingNode) yamlNode).getValue().forEach(tuple -> { // I don't think it's possible to have a non-scalar node as key - final ScalarNode keyNode = (ScalarNode) tuple.getKeyNode(); + final ConfigurationNode keyNode = (ConfigurationNode) this.constructObject(tuple.getKeyNode()); final Node valueNode = tuple.getValueNode(); // comments are on the key, not the value - node.node(keyNode.getValue()) + node.node(keyNode.raw()) .from((ConfigurationNode) constructObject(valueNode)) - .comment(commentFor(keyNode.getBlockComments())); + .comment(commentFor(tuple.getKeyNode().getBlockComments())); }); return node.comment(commentFor(yamlNode.getBlockComments())); diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index f7d10c1cf..695ca9852 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.leangen.geantyref.TypeToken; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.spongepowered.configurate.BasicConfigurationNode; @@ -35,6 +36,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -46,7 +49,7 @@ class YamlConfigurationLoaderTest { @Test void testSimpleLoading() throws ConfigurateException { - final URL url = this.getClass().getResource("/example.yml"); + final URL url = this.resource("/example.yml"); final ConfigurationLoader loader = YamlConfigurationLoader.builder() .url(url).build(); final ConfigurationNode node = loader.load(); @@ -73,7 +76,7 @@ void testReadWithTabs() throws ConfigurateException { }); }); - final URL url = this.getClass().getResource("/tab-example.yml"); + final URL url = this.resource("/tab-example.yml"); final ConfigurationLoader loader = YamlConfigurationLoader.builder() .url(url).build(); final ConfigurationNode node = loader.load(); @@ -81,7 +84,7 @@ void testReadWithTabs() throws ConfigurateException { } @Test - void testWriteBasicFile(final @TempDir Path tempDir) throws ConfigurateException, IOException { + void testWriteBasicFile(final @TempDir Path tempDir) throws IOException { final Path target = tempDir.resolve("write-basic.yml"); final ConfigurationNode node = BasicConfigurationNode.root(n -> { n.node("mapping", "first").set("hello"); @@ -102,7 +105,7 @@ void testWriteBasicFile(final @TempDir Path tempDir) throws ConfigurateException loader.save(node); - assertEquals(readLines(this.getClass().getResource("write-expected.yml")), Files.readAllLines(target, StandardCharsets.UTF_8)); + assertEquals(readLines(this.resource("write-expected.yml")), Files.readAllLines(target, StandardCharsets.UTF_8)); } @Test @@ -119,7 +122,7 @@ void testReadComments() throws IOException { }) )); - final URL url = this.getClass().getResource("comments-test.yml"); + final URL url = this.resource("comments-test.yml"); final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() .url(url).build(); @@ -150,14 +153,14 @@ void testWriteComments(final @TempDir Path tempDir) throws IOException { loader.save(node); assertEquals( - readLines(this.getClass().getResource("comments-test.yml")), + readLines(this.resource("comments-test.yml")), Files.readAllLines(target, StandardCharsets.UTF_8) ); } @Test void testReadWriteComments(final @TempDir Path tempDir) throws IOException { - final URL source = this.getClass().getResource("comments-test.yml"); + final URL source = this.resource("comments-test.yml"); final Path destination = tempDir.resolve("comments-readwrite.yml"); final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() @@ -175,6 +178,26 @@ void testReadWriteComments(final @TempDir Path tempDir) throws IOException { ); } + @Test + void testComplexKeys() throws ConfigurateException { + final URL source = this.resource("complex-keys.yaml"); + final CommentedConfigurationNode node = YamlConfigurationLoader.builder() + .url(source) + .build().load(); + + assertEquals("good", node.node("mapping", Arrays.asList("one", "two")).getString()); + assertEquals("bad", node.node("mapping", Arrays.asList("red", "blue")).getString()); + assertEquals("cat", node.node("mapping", Collections.singletonMap("name", "Meow")).getString()); + } + + private URL resource(final String path) { + final @Nullable URL res = this.getClass().getResource(path); + if (res == null) { + throw new IllegalArgumentException("No resource found for path '" + path + "'"); + } + return res; + } + private static List readLines(final URL source) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(source.openStream(), StandardCharsets.UTF_8))) { return reader.lines().collect(Collectors.toList()); diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/complex-keys.yaml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/complex-keys.yaml new file mode 100644 index 000000000..d03723a6f --- /dev/null +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/complex-keys.yaml @@ -0,0 +1,4 @@ +mapping: + [one, two]: good + [red, blue]: bad + {name: "Meow"}: cat From 618f113649936577235e3d290383b0727370055d Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 14 Oct 2023 18:53:33 -0700 Subject: [PATCH 09/17] chore(format/yaml): Add failing tests to demonstrate roundtripping flaws --- .../yaml/YamlConfigurationLoaderTest.java | 74 +- .../configurate/yaml/essx-example.yml | 1160 +++++++++++++++++ .../configurate/yaml/essx-legacy.yml | 728 +++++++++++ .../configurate/yaml/mobcleaner-example.yml | 138 ++ 4 files changed, 2089 insertions(+), 11 deletions(-) create mode 100644 format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-example.yml create mode 100644 format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-legacy.yml create mode 100644 format/yaml/src/test/resources/org/spongepowered/configurate/yaml/mobcleaner-example.yml diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index 695ca9852..debabc641 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -105,7 +105,7 @@ void testWriteBasicFile(final @TempDir Path tempDir) throws IOException { loader.save(node); - assertEquals(readLines(this.resource("write-expected.yml")), Files.readAllLines(target, StandardCharsets.UTF_8)); + assertContentsSame(this.resource("write-expected.yml"), target); } @Test @@ -152,10 +152,7 @@ void testWriteComments(final @TempDir Path tempDir) throws IOException { loader.save(node); - assertEquals( - readLines(this.resource("comments-test.yml")), - Files.readAllLines(target, StandardCharsets.UTF_8) - ); + assertContentsSame(this.resource("comments-test.yml"), target); } @Test @@ -172,10 +169,7 @@ void testReadWriteComments(final @TempDir Path tempDir) throws IOException { final ConfigurationNode sourceNode = loader.load(); loader.save(sourceNode); - assertEquals( - readLines(source), - Files.readAllLines(destination, StandardCharsets.UTF_8) - ); + assertContentsSame(source, destination); } @Test @@ -190,6 +184,57 @@ void testComplexKeys() throws ConfigurateException { assertEquals("cat", node.node("mapping", Collections.singletonMap("name", "Meow")).getString()); } + @Test + void testRoundtripEssX(final @TempDir Path tempDir) throws IOException { + final URL source = this.resource("essx-example.yml"); + final Path destination = tempDir.resolve("essx-example-roundtrip.yml"); + + final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .path(destination) + .url(source) + .nodeStyle(NodeStyle.BLOCK) + .build(); + + final ConfigurationNode sourceNode = loader.load(); + loader.save(sourceNode); + + assertContentsSame(source, destination); + } + + @Test + void testRoundtripEssXLegacy(final @TempDir Path tempDir) throws IOException { + final URL source = this.resource("essx-legacy.yml"); + final Path destination = tempDir.resolve("essx-legacy-roundtrip.yml"); + + final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .path(destination) + .url(source) + .nodeStyle(NodeStyle.BLOCK) + .build(); + + final ConfigurationNode sourceNode = loader.load(); + loader.save(sourceNode); + + assertContentsSame(source, destination); + } + + @Test + void testRoundtripMobCleaner(final @TempDir Path tempDir) throws IOException { + final URL source = this.resource("mobcleaner-example.yml"); + final Path destination = tempDir.resolve("mobcleaner-example-roundtrip.yml"); + + final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() + .path(destination) + .url(source) + .nodeStyle(NodeStyle.BLOCK) + .build(); + + final ConfigurationNode sourceNode = loader.load(); + loader.save(sourceNode); + + assertContentsSame(source, destination); + } + private URL resource(final String path) { final @Nullable URL res = this.getClass().getResource(path); if (res == null) { @@ -198,9 +243,16 @@ private URL resource(final String path) { return res; } - private static List readLines(final URL source) throws IOException { + private static void assertContentsSame(final URL expected, final Path actual) throws IOException { + assertEquals( + readLines(expected), + String.join("\n", Files.readAllLines(actual, StandardCharsets.UTF_8)) + ); + } + + private static String readLines(final URL source) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(source.openStream(), StandardCharsets.UTF_8))) { - return reader.lines().collect(Collectors.toList()); + return reader.lines().collect(Collectors.joining("\n")); } } diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-example.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-example.yml new file mode 100644 index 000000000..76ed4502f --- /dev/null +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-example.yml @@ -0,0 +1,1160 @@ +############################################################ +# +------------------------------------------------------+ # +# | Notes | # +# +------------------------------------------------------+ # +############################################################ + +# This is the config file for EssentialsX. +# This config was generated for version 2.19.0-dev+227-87a6cf7. + +# If you want to use special characters in this document, such as accented letters, you MUST save the file as UTF-8, not ANSI. +# If you receive an error when Essentials loads, ensure that: +# - No tabs are present: YAML only allows spaces +# - Indents are correct: YAML hierarchy is based entirely on indentation +# - You have "escaped" all apostrophes in your text: If you want to write "don't", for example, write "don''t" instead (note the doubled apostrophe) +# - Text with symbols is enclosed in single or double quotation marks + +# If you need help, you can join the EssentialsX community: https://essentialsx.net/community.html + +############################################################ +# +------------------------------------------------------+ # +# | Essentials (Global) | # +# +------------------------------------------------------+ # +############################################################ + +# A color code between 0-9 or a-f. Set to 'none' to disable. +# In 1.16+ you can use hex color codes here as well. (For example, #613e1d is brown). +ops-name-color: '4' + +# The character(s) to prefix all nicknames, so that you know they are not true usernames. +nickname-prefix: '~' + +# The maximum length allowed in nicknames. The nickname prefix is included in this. +max-nick-length: 15 + +# A list of phrases that cannot be used in nicknames. You can include regular expressions here. +# Users with essentials.nick.blacklist.bypass will be able to bypass this filter. +nick-blacklist: +#- Notch +#- '^Dinnerbone' + +# When this option is enabled, nickname length checking will exclude color codes in player names. +# ie: "&6Notch" has 7 characters (2 are part of a color code), a length of 5 is used when this option is set to true +ignore-colors-in-max-nick-length: false + +# When this option is enabled, display names for hidden users will not be shown. This prevents players from being +# able to see that they are online while vanished. +hide-displayname-in-vanish: true + +# Disable this if you have any other plugin, that modifies the displayname of a user. +change-displayname: true + +# When this option is enabled, the (tab) player list will be updated with the displayname. +# The value of change-displayname (above) has to be true. +# change-playerlist: true + +# When EssentialsChat.jar isn't used, force essentials to add the prefix and suffix from permission plugins to displayname. +# This setting is ignored if EssentialsChat.jar is used, and defaults to 'true'. +# The value of change-displayname (above) has to be true. +# Do not edit this setting unless you know what you are doing! +# add-prefix-suffix: false + +# When this option is enabled, player prefixes will be shown in the playerlist. +# This feature only works for Minecraft version 1.8 and higher. +# This value of change-playerlist has to be true +# add-prefix-in-playerlist: true + +# When this option is enabled, player suffixes will be shown in the playerlist. +# This feature only works for Minecraft version 1.8 and higher. +# This value of change-playerlist has to be true +# add-suffix-in-playerlist: true + +# If the teleport destination is unsafe, should players be teleported to the nearest safe location? +# If this is set to true, Essentials will attempt to teleport players close to the intended destination. +# If this is set to false, attempted teleports to unsafe locations will be cancelled with a warning. +teleport-safety: true + +# This forcefully disables teleport safety checks without a warning if attempting to teleport to unsafe locations. +# teleport-safety and this option need to be set to true to force teleportation to dangerous locations. +force-disable-teleport-safety: false + +# If a player is teleporting to an unsafe location in creative, adventure, or god mode; they will not be teleported to a +# safe location. If you'd like players to be teleported to a safe location all of the time, set this option to true. +force-safe-teleport-location: false + +# If a player has any passengers, the teleport will fail. Should their passengers be dismounted before they are teleported? +# If this is set to true, Essentials will dismount the player's passengers before teleporting. +# If this is set to false, attempted teleports will be canceled with a warning. +teleport-passenger-dismount: true + +# The delay, in seconds, required between /home, /tp, etc. +teleport-cooldown: 0 + +# The delay, in seconds, before a user actually teleports. If the user moves or gets attacked in this timeframe, the teleport is cancelled. +teleport-delay: 0 + +# The delay, in seconds, a player can't be attacked by other players after they have been teleported by a command. +# This will also prevent the player attacking other players. +teleport-invulnerability: 4 + +# Whether to make all teleportations go to the center of the block; where the x and z coordinates decimal become .5 +teleport-to-center: true + +# The delay, in seconds, required between /heal or /feed attempts. +heal-cooldown: 60 + +# Do you want to remove potion effects when healing a player? +remove-effects-on-heal: true + +# Near Radius +# The default radius with /near +# Used to use chat radius but we are going to make it separate. +near-radius: 200 + +# What to prevent from /item and /give. +# e.g item-spawn-blacklist: 10,11,46 +item-spawn-blacklist: + +# Set this to true if you want permission based item spawn rules. +# Note: The blacklist above will be ignored then. +# Example permissions (these go in your permissions manager): +# - essentials.itemspawn.item-all +# - essentials.itemspawn.item-[itemname] +# - essentials.itemspawn.item-[itemid] +# - essentials.give.item-all +# - essentials.give.item-[itemname] +# - essentials.give.item-[itemid] +# - essentials.unlimited.item-all +# - essentials.unlimited.item-[itemname] +# - essentials.unlimited.item-[itemid] +# - essentials.unlimited.item-bucket # Unlimited liquid placing +# +# For more information, visit http://wiki.ess3.net/wiki/Command_Reference/ICheat#Item.2FGive +permission-based-item-spawn: false + +# Mob limit on the /spawnmob command per execution. +spawnmob-limit: 10 + +# Shall we notify users when using /lightning? +warn-on-smite: true + +# Shall we drop items instead of adding to inventory if the target inventory is full? +drop-items-if-full: false + +# Essentials Mail Notification +# Should we notify players if they have no new mail? +notify-no-new-mail: true + +# Specifies the duration (in seconds) between each time a player is notified of mail they have. +# Useful for servers with a lot of mail traffic. +notify-player-of-mail-cooldown: 60 + +# The motd and rules are now configured in the files motd.txt and rules.txt. + +# When a command conflicts with another plugin, by default, Essentials will try to force the OTHER plugin to take priority. +# Commands in this list, will tell Essentials to 'not give up' the command to other plugins. +# In this state, which plugin 'wins' appears to be almost random. +# +# If you have two plugin with the same command and you wish to force Essentials to take over, you need an alias. +# To force essentials to take 'god' alias 'god' to 'egod'. +# See http://wiki.bukkit.org/Commands.yml#aliases for more information. + +overridden-commands: +# - god +# - info + +# Disabling commands here will prevent Essentials handling the command, this will not affect command conflicts. +# You should not have to disable commands used in other plugins, they will automatically get priority. +# See http://wiki.bukkit.org/Commands.yml#aliases to map commands to other plugins. +disabled-commands: +# - nick +# - clear + +# These commands will be shown to players with socialSpy enabled. +# You can add commands from other plugins you may want to track or +# remove commands that are used for something you dont want to spy on. +# Set - '*' in order to listen on all possible commands. +socialspy-commands: + - msg + - w + - r + - mail + - m + - t + - whisper + - emsg + - tell + - er + - reply + - ereply + - email + - action + - describe + - eme + - eaction + - edescribe + - etell + - ewhisper + - pm + +# Whether the private and public messages from muted players should appear in the social spy. +# If so, they will be differentiated from those sent by normal players. +socialspy-listen-muted-players: true + +# Whether social spy should spy on private messages or just the commands from the list above. +# If false, social spy will only monitor commands from the list above. +socialspy-messages: true + +# The following settings listen for when a player changes worlds. +# If you use another plugin to control speed and flight, you should change these to false. + +# When a player changes world, should EssentialsX reset their flight? +# This will disable flight if the player does not have essentials.fly. +world-change-fly-reset: true + +# When a player changes world, should we reset their speed according to their permissions? +# This resets the player's speed to the default if they don't have essentials.speed. +# If the player doesn't have essentials.speed.bypass, this resets their speed to the maximum specified above. +world-change-speed-reset: true + +# Mute Commands +# These commands will be disabled when a player is muted. +# Use '*' to disable every command. +# Essentials already disabled Essentials messaging commands by default. +# It only cares about the root command, not args after that (it sees /f chat the same as /f) +mute-commands: + - f + - kittycannon + # - '*' + +# If you do not wish to use a permission system, you can define a list of 'player perms' below. +# This list has no effect if you are using a supported permissions system. +# If you are using an unsupported permissions system, simply delete this section. +# Whitelist the commands and permissions you wish to give players by default (everything else is op only). +# These are the permissions without the "essentials." part. +# +# To enable this feature, please set use-bukkit-permissions to false. +player-commands: + - afk + - afk.auto + - back + - back.ondeath + - balance + - balance.others + - balancetop + - build + - chat.color + - chat.format + - chat.shout + - chat.question + - clearinventory + - compass + - depth + - delhome + - getpos + - geoip.show + - help + - helpop + - home + - home.others + - ignore + - info + - itemdb + - kit + - kits.tools + - list + - mail + - mail.send + - me + - motd + - msg + - msg.color + - nick + - near + - pay + - ping + - protect + - r + - rules + - realname + - seen + - sell + - sethome + - setxmpp + - signs.create.protection + - signs.create.trade + - signs.break.protection + - signs.break.trade + - signs.use.balance + - signs.use.buy + - signs.use.disposal + - signs.use.enchant + - signs.use.free + - signs.use.gamemode + - signs.use.heal + - signs.use.info + - signs.use.kit + - signs.use.mail + - signs.use.protection + - signs.use.repair + - signs.use.sell + - signs.use.time + - signs.use.trade + - signs.use.warp + - signs.use.weather + - spawn + - suicide + - time + - tpa + - tpaccept + - tpahere + - tpdeny + - warp + - warp.list + - world + - worth + - xmpp + +# Use this option to force superperms-based permissions handler regardless of detected installed perms plugin. +# This is useful if you want superperms-based permissions (with wildcards) for custom permissions plugins. +# If you wish to use EssentialsX's built-in permissions using the `player-commands` section above, set this to false. +# Default is true. +use-bukkit-permissions: true + +# When this option is enabled, one-time use kits (ie. delay < 0) will be +# removed from the /kit list when a player can no longer use it +skip-used-one-time-kits-from-kit-list: false + +# When enabled, armor from kits will automatically be equipped as long as the player's armor slots are empty. +kit-auto-equip: false + +# Determines the functionality of the /createkit command. +# If this is true, /createkit will give the user a link with the kit code. +# If this is false, /createkit will add the kit to the kits.yml config file directly. +# Default is false. +pastebin-createkit: false + +# Determines if /createkit will generate kits using NBT item serialization. +# If this is true, /createkit will store items as NBT; otherwise, it will use Essentials' human-readable item format. +# By using NBT serialization, /createkit can store items with complex metadata such as shulker boxes and weapons with custom attributes. +# WARNING: This option only works on 1.15.2+ Paper servers, and it will bypass any custom serializers from other plugins such as Magic. +# WARNING: When creating kits via /createkit with this option enabled, you will not be able to downgrade your server with these kit items. +# This option only affects /createkit - you can still create kits by hand in `kits.yml` using Essentials' human-readable item format. +# Default is false. +use-nbt-serialization-in-createkit: false + +# Essentials Sign Control +# See http://wiki.ess3.net/wiki/Sign_Tutorial for instructions on how to use these. +# To enable signs, remove # symbol. To disable all signs, comment/remove each sign. +# Essentials colored sign support will be enabled when any sign types are enabled. +# Color is not an actual sign, it's for enabling using color codes on signs, when the correct permissions are given. + +enabledSigns: + # - color + # - balance + # - buy + # - sell + # - trade + # - free + # - warp + # - kit + # - mail + # - enchant + # - gamemode + # - heal + # - info + # - spawnmob + # - repair + # - time + # - weather + # - anvil + # - cartography + # - disposal + # - grindstone + # - loom + # - smithing + # - workbench + +# How many times per second can Essentials signs be interacted with per player. +# Values should be between 1-20, 20 being virtually no lag protection. +# Lower numbers will reduce the possibility of lag, but may annoy players. +sign-use-per-second: 4 + +# Allow item IDs on pre-existing signs on 1.13 and above. +# You cannot use item IDs on new signs, but this will allow players to interact with signs that +# were placed before 1.13. +allow-old-id-signs: false + +# List of sign names Essentials should not protect. This feature is especially useful when +# another plugin provides a sign that EssentialsX provides, but Essentials overrides. +# For example, if a plugin provides a [kit] sign, and you wish to use theirs instead of +# Essentials's, then simply add kit below and Essentials will not protect it. +# +# See https://github.com/drtshock/Essentials/pull/699 for more information. +unprotected-sign-names: + # - kit + +# Backup runs a custom batch/bash command at a specified interval. +# The server will save the world before executing the backup command, and disable +# saving during the backup to prevent world corruption or other conflicts. +# Backups can also be triggered manually with /backup. +backup: + # Interval in minutes. + interval: 30 + # If true, the backup task will run even if there are no players online. + always-run: false + # Unless you add a valid backup command or script here, this feature will be useless. + # Use 'save-all' to simply force regular world saving without backup. + # The example command below utilizes rdiff-backup: https://rdiff-backup.net/ + # command: 'rdiff-backup World1 backups/World1' + +# Set this true to enable permission per warp. +per-warp-permission: false + +# Sort output of /list command by groups. +# You can hide and merge the groups displayed in /list by defining the desired behaviour here. +# Detailed instructions and examples can be found on the wiki: http://wiki.ess3.net/wiki/List +list: + # To merge groups, list the groups you wish to merge + # Staff: owner admin moderator + Admins: owner admin + # To limit groups, set a max user limit + # builder: 20 + # To hide groups, set the group as hidden + # default: hidden + # Uncomment the line below to simply list all players with no grouping + # Players: '*' + +# Displays real names in /list next to players who are using a nickname. +real-names-on-list: false + +# More output to the console. +debug: false + +# Set the locale for all messages. +# If you don't set this, the default locale of the server will be used. +# For example, to set language to English, set locale to en, to use the file "messages_en.properties". +# Don't forget to remove the # in front of the line. +# For more information, visit http://wiki.ess3.net/wiki/Locale +# locale: en + +# Turn off god mode when people leave the server. +remove-god-on-disconnect: false + +# Auto-AFK +# After this timeout in seconds, the user will be set as AFK. +# This feature requires the player to have essentials.afk.auto node. +# Set to -1 for no timeout. +auto-afk: 300 + +# Auto-AFK Kick +# After this timeout in seconds, the user will be kicked from the server. +# essentials.afk.kickexempt node overrides this feature. +# Set to -1 for no timeout. +auto-afk-kick: -1 + +# Set this to true, if you want to freeze the player, if the player is AFK. +# Other players or monsters can't push the player out of AFK mode then. +# This will also enable temporary god mode for the AFK player. +# The player has to use the command /afk to leave the AFK mode. +freeze-afk-players: false + +# When the player is AFK, should he be able to pickup items? +# Enable this, when you don't want people idling in mob traps. +disable-item-pickup-while-afk: false + +# This setting controls if a player is marked as active on interaction. +# When this setting is false, the player would need to manually un-AFK using the /afk command. +cancel-afk-on-interact: true + +# Should we automatically remove afk status when a player moves? +# Player will be removed from AFK on chat/command regardless of this setting. +# Disable this to reduce server lag. +cancel-afk-on-move: true + +# Should we automatically remove afk status when a player sends a chat message? +cancel-afk-on-chat: true + +# Should AFK players be ignored when other players are trying to sleep? +# When this setting is false, players won't be able to skip the night if some players are AFK. +# Users with the permission node essentials.sleepingignored will always be ignored. +sleep-ignores-afk-players: true + +# Should vanished players be ignored when other players are trying to sleep? +# When this setting is false, player's won't be able to skip the night if vanished players are not sleeping. +# Users with the permission node essentials.sleepingignored will always be ignored. +sleep-ignores-vanished-player: true + +# Set the player's list name when they are AFK. This is none by default which specifies that Essentials +# should not interfere with the AFK player's list name. +# You may use color codes, use {USERNAME} the player's name or {PLAYER} for the player's displayname. +afk-list-name: "none" + +# When a player enters or exits AFK mode, should the AFK notification be broadcast +# to the entire server, or just to the player? +# When this setting is false, only the player will be notified upon changing their AFK state. +broadcast-afk-message: true + +# You can disable the death messages of Minecraft here. +death-messages: true + +# How should essentials handle players with the essentials.keepinv permission who have items with +# curse of vanishing when they die? +# You can set this to "keep" (to keep the item), "drop" (to drop the item), or "delete" (to delete the item). +# Defaults to "keep" +vanishing-items-policy: keep + +# How should essentials handle players with the essentials.keepinv permission who have items with +# curse of binding when they die? +# You can set this to "keep" (to keep the item), "drop" (to drop the item), or "delete" (to delete the item). +# Defaults to "keep" +binding-items-policy: keep + +# When players die, should they receive the coordinates they died at? +send-info-after-death: false + +# Should players with permissions be able to join and part silently? +# You can control this with essentials.silentjoin and essentials.silentquit permissions if it is enabled. +# In addition, people with essentials.silentjoin.vanish will be vanished on join. +allow-silent-join-quit: false + +# You can set custom join and quit messages here. Set this to "none" to use the default Minecraft message, +# or set this to "" to hide the message entirely. + +# Available placeholders: +# {PLAYER} - The player's displayname. +# {USERNAME} - The player's username. +# {PREFIX} - The player's prefix. +# {SUFFIX} - The player's suffix. +# {ONLINE} - The number of players online. +# {UNIQUE} - The number of unique players to join the server. +# {UPTIME} - The amount of time the server has been online. +custom-join-message: "none" +custom-quit-message: "none" + +# You can set a custom join message for users who join with a new username here. +# This message will only be used if a user has joined before and have since changed their username. +# This will be displayed INSTEAD OF custom-join-message, so if you intend to keep them similar, make sure they match. +# Set this to "none" to use the the "custom-join-message" above for every join. + +# Available placeholders: +# {PLAYER} - The player's displayname. +# {USERNAME} - The player's username. +# {OLDUSERNAME} - The player's old username. +# {PREFIX} - The player's prefix. +# {SUFFIX} - The player's suffix. +# {ONLINE} - The number of players online. +# {UNIQUE} - The number of unique players to join the server. +# {UPTIME} - The amount of time the server has been online. +custom-new-username-message: "none" + +# Should Essentials override the vanilla "Server Full" message with its own from the language file? +# Set to false to keep the vanilla message. +use-custom-server-full-message: true + +# You can disable join and quit messages when the player count reaches a certain limit. +# When the player count is below this number, join/quit messages will always be shown. +# Set this to -1 to always show join and quit messages regardless of player count. +hide-join-quit-messages-above: -1 + +# Add worlds to this list, if you want to automatically disable god mode there. +no-god-in-worlds: +# - world_nether + +# Set to true to enable per-world permissions for teleporting between worlds with essentials commands. +# This applies to /world, /back, /tp[a|o][here|all], but not warps. +# Give someone permission to teleport to a world with essentials.worlds. +# This does not affect the /home command, there is a separate toggle below for this. +world-teleport-permissions: false + +# The number of items given if the quantity parameter is left out in /item or /give. +# If this number is below 1, the maximum stack size size is given. If over-sized stacks. +# are not enabled, any number higher than the maximum stack size results in more than one stack. +default-stack-size: -1 + +# Over-sized stacks are stacks that ignore the normal max stack size. +# They can be obtained using /give and /item, if the player has essentials.oversizedstacks permission. +# How many items should be in an over-sized stack? +oversized-stacksize: 64 + +# Allow repair of enchanted weapons and armor. +# If you set this to false, you can still allow it for certain players using the permission. +# essentials.repair.enchanted +repair-enchanted: true + +# Allow 'unsafe' enchantments in kits and item spawning. +# Warning: Mixing and overleveling some enchantments can cause issues with clients, servers and plugins. +unsafe-enchantments: false + +# Do you want Essentials to keep track of previous location for /back in the teleport listener? +# If you set this to true any plugin that uses teleport will have the previous location registered. +register-back-in-listener: false + +# Delay to wait before people can cause attack damage after logging in. +login-attack-delay: 5 + +# Set the max fly speed, values range from 0.1 to 1.0 +max-fly-speed: 0.8 + +# Set the max walk speed, values range from 0.1 to 1.0 +max-walk-speed: 0.8 + +# Set the maximum amount of mail that can be sent within a minute. +mails-per-minute: 1000 + +# Set the maximum time /mute can be used for in seconds. +# Set to -1 to disable, and essentials.mute.unlimited can be used to override. +max-mute-time: -1 + +# Set the maximum time /tempban can be used for in seconds. +# Set to -1 to disable, and essentials.tempban.unlimited can be used to override. +max-tempban-time: -1 + +# Changes the default /reply functionality. This can be changed on a per-player basis using /rtoggle. +# If true, /r goes to the person you messaged last, otherwise the first person that messaged you. +# If false, /r goes to the last person that messaged you. +last-message-reply-recipient: true + +# If last-message-reply-recipient is enabled for a particular player, +# this specifies the duration, in seconds, that would need to elapse for the +# reply-recipient to update when receiving a message. +# Default is 180 (3 minutes) +last-message-reply-recipient-timeout: 180 + +# Changes the default /reply functionality. +# If true, /reply will not check if the person you're replying to has vanished. +# If false, players will not be able to /reply to players who they can no longer see due to vanish. +last-message-reply-vanished: false + +# Toggles whether or not left clicking mobs with a milk bucket turns them into a baby. +milk-bucket-easter-egg: true + +# Toggles whether or not the fly status message should be sent to players on join +send-fly-enable-on-join: true + +# Set to true to enable per-world permissions for setting time for individual worlds with essentials commands. +# This applies to /time, /day, /eday, /night, /enight, /etime. +# Give someone permission to teleport to a world with essentials.time.world.. +world-time-permissions: false + +# Specify cooldown for both Essentials commands and external commands as well. +# All commands do not start with a Forward Slash (/). Instead of /msg, write msg +# +# Wildcards are supported. E.g. +# - '*i*': 50 +# adds a 50 second cooldown to all commands that include the letter i +# +# EssentialsX supports regex by starting the command with a caret ^ +# For example, to target commands starting with ban and not banip the following would be used: +# '^ban([^ip])( .*)?': 60 # 60 seconds /ban cooldown. +# Note: If you have a command that starts with ^, then you can escape it using backslash (\). e.g. \^command: 123 +command-cooldowns: +# feed: 100 # 100 second cooldown on /feed command +# '*': 5 # 5 Second cooldown on all commands + +# Whether command cooldowns should be persistent past server shutdowns +command-cooldown-persistence: true + +# Whether NPC balances should be listed in balance ranking features such as /balancetop. +# NPC balances can include features like factions from FactionsUUID plugin. +npcs-in-balance-ranking: false + +# Allow bulk buying and selling signs when the player is sneaking. +# This is useful when a sign sells or buys one item at a time and the player wants to sell a bunch at once. +allow-bulk-buy-sell: true + +# Allow selling of items with custom names with the /sell command. +# This may be useful to prevent players accidentally selling named items. +allow-selling-named-items: false + +# Delay for the MOTD display for players on join, in milliseconds. +# This has no effect if the MOTD command or permission are disabled. +delay-motd: 0 + +# A list of commands that should have their complementary confirm commands enabled by default. +# This is empty by default, for the latest list of valid commands see the latest source config.yml. +default-enabled-confirm-commands: +# - pay +# - clearinventory + +# Where should Essentials teleport players when they are freed from jail? +# You can set to "back" to have them teleported to where they were before they were jailed, "spawn" to have them +# teleport to spawn, or "off" to not have them teleport. +teleport-when-freed: back + +# Whether or not jail time should only be counted while the user is online. +# If true, a jailed player's time will only decrement when they are online. +jail-online-time: false + +# Set the timeout, in seconds for players to accept a tpa before the request is cancelled. +# Set to 0 for no timeout. +tpa-accept-cancellation: 120 + +# Allow players to set hats by clicking on their helmet slot. +allow-direct-hat: true + +# Allow in-game players to specify a world when running /broadcastworld. +# If false, running /broadcastworld in-game will always send a message to the player's current world. +# This doesn't affect running the command from the console, where a world is always required. +allow-world-in-broadcastworld: true + +# Consider water blocks as "safe," therefore allowing players to teleport +# using commands such as /home or /spawn to a location that is occupied +# by water blocks +is-water-safe: false + +# Should the usermap try to sanitise usernames before saving them? +# You should only change this to false if you use Minecraft China. +safe-usermap-names: true + +# Should Essentials output logs when a command block executes a command? +# Example: CommandBlock at ,, issued server command: / +log-command-block-commands: true + +# Set the maximum speed for projectiles spawned with /fireball. +max-projectile-speed: 8 + +# Should EssentialsX check for updates? +# If set to true, EssentialsX will show notifications when a new version is available. +# This uses the public GitHub API and no identifying information is sent or stored. +update-check: true + +############################################################ +# +------------------------------------------------------+ # +# | Homes | # +# +------------------------------------------------------+ # +############################################################ + +# Allows people to set their bed during the day. +# This setting has no effect in Minecraft 1.15+, as Minecraft will always allow the player to set their bed location during the day. +update-bed-at-daytime: true + +# Set to true to enable per-world permissions for using homes to teleport between worlds. +# This applies to the /home command only. +# Give someone permission to teleport to a world with essentials.worlds. +world-home-permissions: false + +# Allow players to have multiple homes. +# Players need essentials.sethome.multiple before they can have more than 1 home. +# You can set the default number of multiple homes using the 'default' rank below. +# To remove the home limit entirely, give people 'essentials.sethome.multiple.unlimited'. +# To grant different home amounts to different people, you need to define a 'home-rank' below. +# Create the 'home-rank' below, and give the matching permission: essentials.sethome.multiple. +# For more information, visit http://wiki.ess3.net/wiki/Multihome +sethome-multiple: + default: 3 + vip: 5 + staff: 10 + +# In this example someone with 'essentials.sethome.multiple' and 'essentials.sethome.multiple.vip' will have 5 homes. +# Remember, they MUST have both permission nodes in order to be able to set multiple homes. + +# Controls whether players need the permission "essentials.home.compass" in order to point +# the player's compass at their first home. +# +# Leaving this as false will retain Essentials' original behaviour, which is to always +# change the compass' direction to point towards their first home. +compass-towards-home-perm: false + +# If no home is set, would you like to send the player to spawn? +# If set to false, players will not be teleported when they run /home without setting a home first. +spawn-if-no-home: true + +# Should players be asked to provide confirmation for homes which they attempt to overwrite? +confirm-home-overwrite: false + +############################################################ +# +------------------------------------------------------+ # +# | Economy | # +# +------------------------------------------------------+ # +############################################################ + +# For more information, visit http://wiki.ess3.net/wiki/Essentials_Economy + +# You can control the values of items that are sold to the server by using the /setworth command. + +# Defines the balance with which new players begin. Defaults to 0. +starting-balance: 0 + +# Defines the cost to use the given commands PER USE. +# Some commands like /repair have sub-costs, check the wiki for more information. +command-costs: + # /example costs $1000 PER USE + # example: 1000 + # /kit tools costs $1500 PER USE + # kit-tools: 1500 + +# Set this to a currency symbol you want to use. +# Remember, if you want to use special characters in this document, +# such as accented letters, you MUST save the file as UTF-8, not ANSI. +currency-symbol: '$' + +# Enable this to make the currency symbol appear at the end of the amount rather than at the start. +# For example, the euro symbol typically appears after the current amount. +currency-symbol-suffix: false + +# Set the maximum amount of money a player can have. +# The amount is always limited to 10 trillion because of the limitations of a java double. +max-money: 10000000000000 + +# Set the minimum amount of money a player can have (must be above the negative of max-money). +# Setting this to 0, will disable overdrafts/loans completely. Users need 'essentials.eco.loan' perm to go below 0. +min-money: -10000 + +# Enable this to log all interactions with trade/buy/sell signs and sell command. +economy-log-enabled: false + +# Enable this to also log all transactions from other plugins through Vault. +# This can cause the economy log to fill up quickly so should only be enabled for testing purposes! +economy-log-update-enabled: false + +# Minimum acceptable amount to be used in /pay. +minimum-pay-amount: 0.001 + +# Enable this to block users who try to /pay another user which ignore them. +pay-excludes-ignore-list: false + +# Whether or not users with a balance less than or equal to $0 should be shown in balance-top. +# Setting to false will not show people with balances <= 0 in balance-top. +# NOTE: After reloading the config, you must also run '/baltop force' for this to appear +show-zero-baltop: true + +# The format of currency, excluding symbols. See currency-symbol-format-locale for symbol configuration. +# +# "#,##0.00" is how the majority of countries display currency. +# currency-format: "#,##0.00" + +# Format currency symbols. Some locales use , and . interchangeably. +# Some formats do not display properly in-game due to faulty Minecraft font rendering. +# +# For 1.234,50 use de-DE +# For 1,234.50 use en-US +# For 1'234,50 use fr-ch +# currency-symbol-format-locale: en-US + +############################################################ +# +------------------------------------------------------+ # +# | Help | # +# +------------------------------------------------------+ # +############################################################ + +# Show other plugins commands in help. +non-ess-in-help: true + +# Hide plugins which do not give a permission. +# You can override a true value here for a single plugin by adding a permission to a user/group. +# The individual permission is: essentials.help., anyone with essentials.* or '*' will see all help regardless. +# You can use negative permissions to remove access to just a single plugins help if the following is enabled. +hide-permissionless-help: true + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsX Chat | # +# +------------------------------------------------------+ # +############################################################ + +# You need to install EssentialsX Chat for this section to work. +# See https://essentialsx.net/wiki/Module-Breakdown.html for more information. + +chat: + + # If EssentialsX Chat is installed, this will define how far a player's voice travels, in blocks. Set to 0 to make all chat global. + # Note that users with the "essentials.chat.spy" permission will hear everything, regardless of this setting. + # Users with essentials.chat.shout can override this by prefixing their message with an exclamation mark (!) + # Users with essentials.chat.question can override this by prefixing their message with a question mark (?) + # You can add command costs for shout/question by adding chat-shout and chat-question to the command costs section. + radius: 0 + + # Chat formatting can be done in two ways, you can either define a standard format for all chat. + # Or you can give a group specific chat format, to give some extra variation. + # For more information of chat formatting, check out the wiki: http://wiki.ess3.net/wiki/Chat_Formatting + # Note: Using the {PREFIX} and {SUFFIX} placeholders along with {DISPLAYNAME} may cause double prefixes/suffixes to be shown in chat unless add-prefix-suffix is uncommented and set to false. + + # Available placeholders: + # {MESSAGE} - The content of the chat message. + # {USERNAME} - The sender's username. + # {DISPLAYNAME} - The sender's display name. + # {NICKNAME} - The sender's Essentials nickname. If the sender has no nickname, the username is shown. + # {PREFIX} - The sender's prefix, supplied by a permissions plugin. + # {SUFFIX} - The sender's suffix, supplied by a permissions plugin. + # {GROUP} - The sender's primary group name, supplied by a permissions plugin. + # {WORLD} - The world alias of the sender's current world. See the world-aliases section below for details. + # {WORLDNAME} - The full name of the sender's current world. + # {SHORTWORLDNAME} - The first character of the sender's current world. + # {TEAMNAME} - The sender's scoreboard team name. + # {TEAMPREFIX} - The sender's scoreboard team prefix. + # {TEAMSUFFIX} - The sender's scoreboard team suffix. + + format: '<{DISPLAYNAME}> {MESSAGE}' + #format: '&7[{GROUP}]&r {DISPLAYNAME}&7:&r {MESSAGE}' + #format: '&7{PREFIX}&r {DISPLAYNAME}&r &7{SUFFIX}&r: {MESSAGE}' + + group-formats: + # default: '{WORLDNAME} {DISPLAYNAME}&7:&r {MESSAGE}' + # admins: '{WORLDNAME} &c[{GROUP}]&r {DISPLAYNAME}&7:&c {MESSAGE}' + + # If you are using group formats make sure to remove the '#' to allow the setting to be read. + # Note: Group names are case-sensitive so you must match them up with your permission plugin. + + # You can use permissions to control whether players can use formatting codes in their chat messages. + # See https://essentialsx.net/wiki/Color-Permissions.html for more information. + + # World aliases allow you to replace the world name with something different in the chat format. + # If you are using world aliases, make sure to remove the '#' at the start to allow the setting to be read. + world-aliases: + # plots: "&dP&r" + # creative: "&eC&r" + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsX Protect | # +# +------------------------------------------------------+ # +############################################################ + +# You need to install EssentialsX Protect for this section to work. +# See https://essentialsx.net/wiki/Module-Breakdown.html for more information. + +protect: + + # General physics/behavior modifications. Set these to true to disable behaviours. + prevent: + lava-flow: false + water-flow: false + water-bucket-flow: false + fire-spread: true + lava-fire-spread: true + lava-itemdamage: false + flint-fire: false + lightning-fire-spread: true + portal-creation: false + tnt-explosion: false + tnt-playerdamage: false + tnt-itemdamage: false + tnt-minecart-explosion: false + tnt-minecart-playerdamage: false + tnt-minecart-itemdamage: false + fireball-explosion: false + fireball-fire: false + fireball-playerdamage: false + fireball-itemdamage: false + witherskull-explosion: false + witherskull-playerdamage: false + witherskull-itemdamage: false + wither-spawnexplosion: false + wither-blockreplace: false + creeper-explosion: false + creeper-playerdamage: false + creeper-itemdamage: false + creeper-blockdamage: false + ender-crystal-explosion: false + enderdragon-blockdamage: true + enderman-pickup: false + villager-death: false + bed-explosion: false + respawn-anchor-explosion: false + # Monsters won't follow players. + # permission essentials.protect.entitytarget.bypass disables this. + entitytarget: false + # Prevents zombies from breaking down doors + zombie-door-break: false + # Prevents Ravagers from stealing blocks + ravager-thief: false + # Prevents sheep from turning grass to dirt + sheep-eat-grass: false + # Prevent certain transformations. + transformation: + # Prevent creepers becoming charged when struck by lightning. + charged-creeper: false + # Prevent villagers becoming zombie villagers. + zombie-villager: false + # Prevent zombie villagers being cured. + villager: false + # Prevent villagers becoming witches when struck by lightning. + witch: false + # Prevent pigs becoming zombie pigmen when struck by lightning. + zombie-pigman: false + # Prevent zombies turning into drowneds, and husks turning into zombies. + drowned: false + # Prevent mooshrooms changing colour when struck by lightning. + mooshroom: false + # Prevent the spawning of creatures. If a creature is missing, you can add it following the format below. + spawn: + creeper: false + skeleton: false + spider: false + giant: false + zombie: false + slime: false + ghast: false + pig_zombie: false + enderman: false + cave_spider: false + silverfish: false + blaze: false + magma_cube: false + ender_dragon: false + pig: false + sheep: false + cow: false + chicken: false + squid: false + wolf: false + mushroom_cow: false + snowman: false + ocelot: false + iron_golem: false + villager: false + wither: false + bat: false + witch: false + horse: false + phantom: false + + # Maximum height the creeper should explode. -1 allows them to explode everywhere. + # Set prevent.creeper-explosion to true, if you want to disable creeper explosions. + creeper: + max-height: -1 + + # Disable various default physics and behaviors. + disable: + # Should fall damage be disabled? + fall: false + + # Users with the essentials.protect.pvp permission will still be able to attack each other if this is set to true. + # They will be unable to attack users without that same permission node. + pvp: false + + # Should drowning damage be disabled? + # (Split into two behaviors; generally, you want both set to the same value.) + drown: false + suffocate: false + + # Should damage via lava be disabled? Items that fall into lava will still burn to a crisp. ;) + lavadmg: false + + # Should arrow damage be disabled? + projectiles: false + + # This will disable damage from touching cacti. + contactdmg: false + + # Burn, baby, burn! Should fire damage be disabled? + firedmg: false + + # Should the damage after hit by a lightning be disabled? + lightning: false + + # Should Wither damage be disabled? + wither: false + + # Disable weather options? + weather: + storm: false + thunder: false + lightning: false + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsX AntiBuild | # +# +------------------------------------------------------+ # +############################################################ + + # You need to install EssentialsX AntiBuild for this section to work. + # See https://essentialsx.net/wiki/Module-Breakdown.html and http://wiki.ess3.net/wiki/AntiBuild for more information. + + # Should people without the essentials.build permission be allowed to build? + # Set true to disable building for those people. + # Setting to false means EssentialsAntiBuild will never prevent you from building. + build: true + + # Should people without the essentials.build permission be allowed to use items? + # Set true to disable using for those people. + # Setting to false means EssentialsAntiBuild will never prevent you from using items. + use: true + + # Should we warn people when they are not allowed to build? + warn-on-build-disallow: true + + # For which block types would you like to be alerted? + # You can find a list of items at https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html. + alert: + on-placement: LAVA,TNT,LAVA_BUCKET + on-use: LAVA_BUCKET + on-break: + + blacklist: + + # Which blocks should people be prevented from placing? + placement: LAVA,TNT,LAVA_BUCKET + + # Which items should people be prevented from using? + usage: LAVA_BUCKET + + # Which blocks should people be prevented from breaking? + break: + + # Which blocks should not be moved by pistons? + piston: + + # Which blocks should not be dispensed by dispensers + dispenser: + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsX Spawn + New Players | # +# +------------------------------------------------------+ # +############################################################ + +# You need to install EssentialsX Spawn for this section to work. +# See https://essentialsx.net/wiki/Module-Breakdown.html for more information. + +newbies: + # Should we announce to the server when someone logs in for the first time? + # If so, use this format, replacing {DISPLAYNAME} with the player name. + # If not, set to '' + # announce-format: '' + announce-format: '&dWelcome {DISPLAYNAME}&d to the server!' + + # When we spawn for the first time, which spawnpoint do we use? + # Set to "none" if you want to use the spawn point of the world. + spawnpoint: newbies + + # Do we want to give users anything on first join? Set to '' to disable + # This kit will be given regardless of cost and permissions, and will not trigger the kit delay. + # kit: '' + kit: tools + +# What priority should we use for handling respawns? +# Set this to none, if you want vanilla respawning behaviour. +# Set this to lowest, if you want Multiverse to handle the respawning. +# Set this to high, if you want EssentialsSpawn to handle the respawning. +# Set this to highest, if you want to force EssentialsSpawn to handle the respawning. +# Note: Changes will not apply until after the server is restarted. +respawn-listener-priority: high + +# What priority should we use for handling spawning on joining the server? +# See respawn-listener-priority for possible values. +# Note: Changing this may impact or break spawn-on-join functionality. +# Note: Changes will not apply until after the server is restarted. +spawn-join-listener-priority: high + +# When users die, should they respawn at their first home or bed, instead of the spawnpoint? +respawn-at-home: false + +# When users die, should they respawn at their bed instead of the spawnpoint? +# The value of respawn-at-home (above) has to be true. +respawn-at-home-bed: true + +# When users die, should EssentialsSpawn respect users' respawn anchors? +respawn-at-anchor: false + +# Teleport all joining players to the spawnpoint +spawn-on-join: false +# The following value of `guests` states that all players in group `guests` will be teleported to spawn when joining. +#spawn-on-join: guests +# The following list value states that all players in group `guests` and `admin` are to be teleported to spawn when joining. +#spawn-on-join: +#- guests +#- admin + +# End of file <-- No seriously, you're done with configuration. diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-legacy.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-legacy.yml new file mode 100644 index 000000000..00f9e55d2 --- /dev/null +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/essx-legacy.yml @@ -0,0 +1,728 @@ +############################################################ +# +------------------------------------------------------+ # +# | Notes | # +# +------------------------------------------------------+ # +############################################################ + +# If you want to use special characters in this document, such as accented letters, you MUST save the file as UTF-8, not ANSI. +# If you receive an error when Essentials loads, ensure that: +# - No tabs are present: YAML only allows spaces +# - Indents are correct: YAML hierarchy is based entirely on indentation +# - You have "escaped" all apostrophes in your text: If you want to write "don't", for example, write "don''t" instead (note the doubled apostrophe) +# - Text with symbols is enclosed in single or double quotation marks + +# If you have problems join the Essentials help support channel: http://tiny.cc/EssentialsChat + +############################################################ +# +------------------------------------------------------+ # +# | Essentials (Global) | # +# +------------------------------------------------------+ # +############################################################ + +# A color code between 0-9 or a-f. Set to 'none' to disable. +ops-name-color: '4' + +# The character(s) to prefix all nicknames, so that you know they are not true usernames. +nickname-prefix: '~' + +# The maximum length allowed in nicknames. The nickname prefix is included in this. +max-nick-length: 120 + +# Disable this if you have any other plugin, that modifies the displayname of a user. +change-displayname: true + +# When this option is enabled, the (tab) player list will be updated with the displayname. +# The value of change-displayname (above) has to be true. +#change-playerlist: true + +# When EssentialsChat.jar isn't used, force essentials to add the prefix and suffix from permission plugins to displayname. +# This setting is ignored if EssentialsChat.jar is used, and defaults to 'true'. +# The value of change-displayname (above) has to be true. +# Do not edit this setting unless you know what you are doing! +#add-prefix-suffix: false + +# If the teleport destination is unsafe, should players be teleported to the nearest safe location? +# If this is set to true, Essentials will attempt to teleport players close to the intended destination. +# If this is set to false, attempted teleports to unsafe locations will be cancelled with a warning. +teleport-safety: true + +# The delay, in seconds, required between /home, /tp, etc. +teleport-cooldown: 0 + +# The delay, in seconds, before a user actually teleports. If the user moves or gets attacked in this timeframe, the teleport never occurs. +teleport-delay: 0 + +# The delay, in seconds, a player can't be attacked by other players after they have been teleported by a command. +# This will also prevent the player attacking other players. +teleport-invulnerability: 4 + +# The delay, in seconds, required between /heal or /feed attempts. +heal-cooldown: 60 + +# What to prevent from /item and /give. +# e.g item-spawn-blacklist: 10,11,46 +item-spawn-blacklist: + +# Set this to true if you want permission based item spawn rules. +# Note: The blacklist above will be ignored then. +# Example permissions (these go in your permissions manager): +# - essentials.itemspawn.item-all +# - essentials.itemspawn.item-[itemname] +# - essentials.itemspawn.item-[itemid] +# - essentials.give.item-all +# - essentials.give.item-[itemname] +# - essentials.give.item-[itemid] +# - essentials.unlimited.item-all +# - essentials.unlimited.item-[itemname] +# - essentials.unlimited.item-[itemid] +# - essentials.unlimited.item-bucket # Unlimited liquid placing +# +# For more information, visit http://wiki.ess3.net/wiki/Command_Reference/ICheat#Item.2FGive +permission-based-item-spawn: false + +# Mob limit on the /spawnmob command per execution. +spawnmob-limit: 100 + +# Shall we notify users when using /lightning? +warn-on-smite: true + +# The motd and rules are now configured in the files motd.txt and rules.txt. + +# When a command conflicts with another plugin, by default, Essentials will try to force the OTHER plugin to take priority. +# Commands in this list, will tell Essentials to 'not give up' the command to other plugins. +# In this state, which plugin 'wins' appears to be almost random. +# +# If you have two plugin with the same command and you wish to force Essentials to take over, you need an alias. +# To force essentials to take 'god' alias 'god' to 'egod'. +# See http://wiki.bukkit.org/Commands.yml#aliases for more information. + +overridden-commands: +# - god +# - info + +# Disabling commands here will prevent Essentials handling the command, this will not affect command conflicts. +# You should not have to disable commands used in other plugins, they will automatically get priority. +# See http://wiki.bukkit.org/Commands.yml#aliases to map commands to other plugins. +disabled-commands: +- bob +- sell +- worth +- formula +# - clear + +# These commands will be shown to players with socialSpy enabled. +# You can add commands from other plugins you may want to track or +# remove commands that are used for something you dont want to spy on. +# Set - '*' in order to listen on all possible commands. +socialspy-commands: +- '*' +- -bob + +# If you do not wish to use a permission system, you can define a list of 'player perms' below. +# This list has no effect if you are using a supported permissions system. +# If you are using an unsupported permissions system, simply delete this section. +# Whitelist the commands and permissions you wish to give players by default (everything else is op only). +# These are the permissions without the "essentials." part. +player-commands: +- afk +- afk.auto +- back +- back.ondeath +- balance +- balance.others +- balancetop +- build +- chat.color +- chat.format +- chat.shout +- chat.question +- clearinventory +- compass +- depth +- delhome +- getpos +- geoip.show +- help +- helpop +- home +- home.others +- ignore +- info +- itemdb +- kit +- kits.tools +- list +- mail +- mail.send +- me +- motd +- msg +- msg.color +- nick +- near +- pay +- ping +- protect +- r +- rules +- realname +- seen +- sell +- sethome +- setxmpp +- signs.create.protection +- signs.create.trade +- signs.break.protection +- signs.break.trade +- signs.use.balance +- signs.use.buy +- signs.use.disposal +- signs.use.enchant +- signs.use.free +- signs.use.gamemode +- signs.use.heal +- signs.use.info +- signs.use.kit +- signs.use.mail +- signs.use.protection +- signs.use.repair +- signs.use.sell +- signs.use.time +- signs.use.trade +- signs.use.warp +- signs.use.weather +- spawn +- suicide +- time +- tpa +- tpaccept +- tpahere +- tpdeny +- warp +- warp.list +- world +- worth +- xmpp + +# Note: All items MUST be followed by a quantity! +# All kit names should be lower case, and will be treated as lower in permissions/costs. +# Syntax: - itemID[:DataValue/Durability] Amount [Enchantment:Level].. [itemmeta:value]... +# For Item Meta information visit http://wiki.ess3.net/wiki/Item_Meta +# 'delay' refers to the cooldown between how often you can use each kit, measured in seconds. +# Set delay to -1 for a one time kit. +# For more information, visit http://wiki.ess3.net/wiki/Kits +kits: + tools: + delay: -1 + items: + - 272 1 + - 273 1 + - 274 1 + - 275 1 + dtools: + delay: 600 + items: + - 278 1 efficiency:1 durability:1 fortune:1 name:&4Gigadrill lore:The_drill_that_&npierces|the_heavens + - 277 1 digspeed:3 name:Dwarf lore:Diggy|Diggy|Hole + - 298 1 color:255,255,255 name:Top_Hat lore:Good_day,_Good_day + - 279:780 1 + notch: + delay: 6000 + items: + - 397:3 1 player:Notch + color: + delay: 6000 + items: + - 387 1 title:&4Book_&9o_&6Colors author:KHobbits lore:Ingame_color_codes book:Colors + firework: + delay: 6000 + items: + - 401 1 name:Angry_Creeper color:red fade:green type:creeper power:1 + - 401 1 name:StarryNight color:yellow,orange fade:blue type:star effect:trail,twinkle power:1 + - 401 2 name:SolarWind color:yellow,orange fade:red shape:large effect:twinkle color:yellow,orange fade:red shape:ball effect:trail color:red,purple fade:pink shape:star effect:trail power:1 + +# Essentials Sign Control +# See http://wiki.ess3.net/wiki/Sign_Tutorial for instructions on how to use these. +# To enable signs, remove # symbol. To disable all signs, comment/remove each sign. +# Essentials colored sign support will be enabled when any sign types are enabled. +# Color is not an actual sign, it's for enabling using color codes on signs, when the correct permissions are given. + +enabledSigns: +- color +- balance +- buy +- sell +#- trade +#- free +- disposal +#- warp +#- kit +#- mail +- enchant +#- gamemode +#- heal +#- info +#- spawnmob +#- repair +#- time +#- weather + +# How many times per second can Essentials signs be interacted with per player. +# Values should be between 1-20, 20 being virtually no lag protection. +# Lower numbers will reduce the possibility of lag, but may annoy players. +sign-use-per-second: 4 + +# Backup runs a batch/bash command while saving is disabled. +backup: + # Interval in minutes. + interval: 30 + # Unless you add a valid backup command or script here, this feature will be useless. + # Use 'save-all' to simply force regular world saving without backup. + #command: 'rdiff-backup World1 backups/World1' + +# Set this true to enable permission per warp. +per-warp-permission: false + +# Sort output of /list command by groups. +# You can hide and merge the groups displayed in /list by defining the desired behaviour here. +# Detailed instructions and examples can be found on the wiki: http://wiki.ess3.net/wiki/List +list: + # To merge groups, list the groups you wish to merge + #Staff: owner admin moderator + Staff: owner admin + # To limit groups, set a max user limit + #builder: 20 + # To hide groups, set the group as hidden + #default: hidden + Pro: Jerry + # Uncomment the line below to simply list all players with no grouping + Players: '*' + +# More output to the console. +debug: false + +# Set the locale for all messages. +# If you don't set this, the default locale of the server will be used. +# For example, to set language to English, set locale to en, to use the file "messages_en.properties". +# Don't forget to remove the # in front of the line. +# For more information, visit http://wiki.ess3.net/wiki/Locale +#locale: en + +# Turn off god mode when people leave the server. +remove-god-on-disconnect: false + +# Auto-AFK +# After this timeout in seconds, the user will be set as AFK. +# This feature requires the player to have essentials.afk.auto node. +# Set to -1 for no timeout. +auto-afk: 60 + +# Auto-AFK Kick +# After this timeout in seconds, the user will be kicked from the server. +# essentials.afk.kickexempt node overrides this feature. +# Set to -1 for no timeout. +auto-afk-kick: -1 + +# Set this to true, if you want to freeze the player, if the player is AFK. +# Other players or monsters can't push the player out of AFK mode then. +# This will also enable temporary god mode for the AFK player. +# The player has to use the command /afk to leave the AFK mode. +freeze-afk-players: false + +# When the player is AFK, should he be able to pickup items? +# Enable this, when you don't want people idling in mob traps. +disable-item-pickup-while-afk: false + +# This setting controls if a player is marked as active on interaction. +# When this setting is false, the player would need to manually un-AFK using the /afk command. +cancel-afk-on-interact: true + +# Should we automatically remove afk status when a player moves? +# Player will be removed from AFK on chat/command regardless of this setting. +# Disable this to reduce server lag. +cancel-afk-on-move: true + +# You can disable the death messages of Minecraft here. +death-messages: true + +# Should players with permissions be able to join and part silently? +# You can control this with essentials.silentjoin and essentials.silentquit permissions if it is enabled. +# In addition, people with essentials.silentjoin.vanish will be vanished on join. +allow-silent-join-quit: true + +# You can set a custom join message here, set to "none" to disable. +# You may use color codes, use {USERNAME} the player's name or {PLAYER} for the player's displayname. +custom-join-message: "" + +# You can set a custom quit message here, set to "none" to disable. +# You may use color codes, use {USERNAME} the player's name or {PLAYER} for the player's displayname. +custom-quit-message: "" + +# Add worlds to this list, if you want to automatically disable god mode there. +no-god-in-worlds: +# - world_nether + +# Set to true to enable per-world permissions for teleporting between worlds with essentials commands. +# This applies to /world, /back, /tp[a|o][here|all], but not warps. +# Give someone permission to teleport to a world with essentials.worlds. +# This does not affect the /home command, there is a separate toggle below for this. +world-teleport-permissions: false + +# The number of items given if the quantity parameter is left out in /item or /give. +# If this number is below 1, the maximum stack size size is given. If over-sized stacks. +# are not enabled, any number higher than the maximum stack size results in more than one stack. +default-stack-size: -1 + +# Over-sized stacks are stacks that ignore the normal max stack size. +# They can be obtained using /give and /item, if the player has essentials.oversizedstacks permission. +# How many items should be in an over-sized stack? +oversized-stacksize: 64 + +# Allow repair of enchanted weapons and armor. +# If you set this to false, you can still allow it for certain players using the permission. +# essentials.repair.enchanted +repair-enchanted: true + +# Allow 'unsafe' enchantments in kits and item spawning. +# Warning: Mixing and overleveling some enchantments can cause issues with clients, servers and plugins. +unsafe-enchantments: true + +#Do you want Essentials to keep track of previous location for /back in the teleport listener? +#If you set this to true any plugin that uses teleport will have the previous location registered. +register-back-in-listener: false + +#Delay to wait before people can cause attack damage after logging in. +login-attack-delay: 5 + +#Set the max fly speed, values range from 0.1 to 1.0 +max-fly-speed: 0.8 + +#Set the max walk speed, values range from 0.1 to 1.0 +max-walk-speed: 0.8 + +#Set the maximum amount of mail that can be sent within a minute. +mails-per-minute: 1000 + +# Set the maximum time /tempban can be used for in seconds. +# Set to -1 to disable, and essentials.tempban.unlimited can be used to override. +max-tempban-time: -1 + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsHome | # +# +------------------------------------------------------+ # +############################################################ + +# Allows people to set their bed at daytime. +update-bed-at-daytime: true + +# Set to true to enable per-world permissions for using homes to teleport between worlds. +# This applies to the /home only. +# Give someone permission to teleport to a world with essentials.worlds. +world-home-permissions: false + +# Allow players to have multiple homes. +# Players need essentials.sethome.multiple before they can have more than 1 home. +# You can set the default number of multiple homes using the 'default' rank below. +# To remove the home limit entirely, give people 'essentials.sethome.multiple.unlimited'. +# To grant different home amounts to different people, you need to define a 'home-rank' below. +# Create the 'home-rank' below, and give the matching permission: essentials.sethome.multiple. +# For more information, visit http://wiki.ess3.net/wiki/Multihome +sethome-multiple: + default: 2 + vip: 2 + staff: 2 + buy5: 5 + buy10: 10 + +# In this example someone with 'essentials.sethome.multiple' and 'essentials.sethome.multiple.vip' will have 5 homes. +# Remember, they MUST have both permission nodes in order to be able to set multiple homes. + +# Set the timeout, in seconds for players to accept a tpa before the request is cancelled. +# Set to 0 for no timeout. +tpa-accept-cancellation: 120 + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsEco | # +# +------------------------------------------------------+ # +############################################################ + +# For more information, visit http://wiki.ess3.net/wiki/Essentials_Economy + +# Defines the balance with which new players begin. Defaults to 0. +starting-balance: 0 + +# worth-# defines the value of an item when it is sold to the server via /sell. +# These are now defined in worth.yml + +# Defines the cost to use the given commands PER USE. +# Some commands like /repair have sub-costs, check the wiki for more information. +command-costs: +# /example costs $1000 PER USE +#example: 1000 +# /kit tools costs $1500 PER USE +#kit-tools: 1500 + +# Set this to a currency symbol you want to use. +# Remember, if you want to use special characters in this document, +# such as accented letters, you MUST save the file as UTF-8, not ANSI. +currency-symbol: '$' + +# Set the maximum amount of money a player can have. +# The amount is always limited to 10 trillion because of the limitations of a java double. +max-money: 10000000000000 + +# Set the minimum amount of money a player can have (must be above the negative of max-money). +# Setting this to 0, will disable overdrafts/loans completely. Users need 'essentials.eco.loan' perm to go below 0. +min-money: 0 + +# Enable this to log all interactions with trade/buy/sell signs and sell command. +economy-log-enabled: false + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsHelp | # +# +------------------------------------------------------+ # +############################################################ + +# Show other plugins commands in help. +non-ess-in-help: true + +# Hide plugins which do not give a permission. +# You can override a true value here for a single plugin by adding a permission to a user/group. +# The individual permission is: essentials.help., anyone with essentials.* or '*' will see all help regardless. +# You can use negative permissions to remove access to just a single plugins help if the following is enabled. +hide-permissionless-help: true + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsChat | # +# +------------------------------------------------------+ # +############################################################ + +# This section requires the EssentialsChat.jar to work. + +chat: + + # If EssentialsChat is installed, this will define how far a player's voice travels, in blocks. Set to 0 to make all chat global. + # Note that users with the "essentials.chat.spy" permission will hear everything, regardless of this setting. + # Users with essentials.chat.shout can override this by prefixing text with an exclamation mark (!) + # Users with essentials.chat.question can override this by prefixing text with a question mark (?) + # You can add command costs for shout/question by adding chat-shout and chat-question to the command costs section." + radius: 0 + + # Chat formatting can be done in two ways, you can either define a standard format for all chat. + # Or you can give a group specific chat format, to give some extra variation. + # For more information of chat formatting, check out the wiki: http://wiki.ess3.net/wiki/Chat_Formatting + + format: '{DISPLAYNAME}: {MESSAGE}' + #format: '&7[{GROUP}]&r {DISPLAYNAME}&7:&r {MESSAGE}' + + group-formats: + default: '{DISPLAYNAME}&7:&7 {MESSAGE}' + tardis: '{DISPLAYNAME}&7:&7 {MESSAGE}' + Helper: '{DISPLAYNAME}&7:&3 {MESSAGE}' + Mod: '{DISPLAYNAME}&7:&3 {MESSAGE}' + Admin: '{DISPLAYNAME}&7:&c {MESSAGE}' + owner: '{DISPLAYNAME}&7:&c {MESSAGE}' + + # If you are using group formats make sure to remove the '#' to allow the setting to be read. + +############################################################ +# +------------------------------------------------------+ # +# | EssentialsProtect | # +# +------------------------------------------------------+ # +############################################################ + +# This section requires the EssentialsProtect.jar to work. + +protect: + + # General physics/behavior modifications. + prevent: + lava-flow: false + water-flow: false + water-bucket-flow: false + fire-spread: true + lava-fire-spread: true + flint-fire: false + lightning-fire-spread: true + portal-creation: false + tnt-explosion: false + tnt-playerdamage: false + tnt-minecart-explosion: false + tnt-minecart-playerdamage: false + fireball-explosion: false + fireball-fire: false + fireball-playerdamage: false + witherskull-explosion: false + witherskull-playerdamage: false + wither-spawnexplosion: false + wither-blockreplace: false + creeper-explosion: false + creeper-playerdamage: false + creeper-blockdamage: false + enderdragon-blockdamage: true + enderman-pickup: false + villager-death: false + # Monsters won't follow players. + # permission essentials.protect.entitytarget.bypass disables this. + entitytarget: false + # Prevent the spawning of creatures. + spawn: + creeper: false + skeleton: false + spider: false + giant: false + zombie: false + slime: false + ghast: false + pig_zombie: false + enderman: false + cave_spider: false + silverfish: false + blaze: false + magma_cube: false + ender_dragon: false + pig: false + sheep: false + cow: false + chicken: false + squid: false + wolf: false + mushroom_cow: false + snowman: false + ocelot: false + iron_golem: false + villager: false + wither: false + bat: false + witch: false + horse: false + + # Maximum height the creeper should explode. -1 allows them to explode everywhere. + # Set prevent.creeper-explosion to true, if you want to disable creeper explosions. + creeper: + max-height: -1 + + # Disable various default physics and behaviors. + disable: + # Should fall damage be disabled? + fall: false + + # Users with the essentials.protect.pvp permission will still be able to attack each other if this is set to true. + # They will be unable to attack users without that same permission node. + pvp: false + + # Should drowning damage be disabled? + # (Split into two behaviors; generally, you want both set to the same value.) + drown: false + suffocate: false + + # Should damage via lava be disabled? Items that fall into lava will still burn to a crisp. ;) + lavadmg: false + + # Should arrow damage be disabled? + projectiles: false + + # This will disable damage from touching cacti. + contactdmg: false + + # Burn, baby, burn! Should fire damage be disabled? + firedmg: false + + # Should the damage after hit by a lightning be disabled? + lightning: false + + # Should Wither damage be disabled? + wither: false + + # Disable weather options? + weather: + storm: false + thunder: false + lightning: false + + ############################################################ + # +------------------------------------------------------+ # + # | EssentialsAntiBuild | # + # +------------------------------------------------------+ # + ############################################################ + + # This section requires the EssentialsAntiBuild.jar to work. + + # Disable various default physics and behaviors + # For more information, visit http://wiki.ess3.net/wiki/AntiBuild + + # Should people with build: false in permissions be allowed to build? + # Set true to disable building for those people. + # Setting to false means EssentialsAntiBuild will never prevent you from building. + build: true + + # Should people with build: false in permissions be allowed to use items? + # Set true to disable using for those people. + # Setting to false means EssentialsAntiBuild will never prevent you from using items. + use: true + + # Should we tell people they are not allowed to build? + warn-on-build-disallow: true + + # For which block types would you like to be alerted? + # You can find a list of IDs in plugins/Essentials/items.csv after loading Essentials for the first time. + # 10 = lava :: 11 = still lava :: 46 = TNT :: 327 = lava bucket + alert: + on-placement: 10,11,46,327 + on-use: 327 + on-break: + + blacklist: + + # Which blocks should people be prevented from placing? + placement: 10,11,46,327 + + # Which items should people be prevented from using? + usage: 327 + + # Which blocks should people be prevented from breaking? + break: + + # Which blocks should not be pushed by pistons? + piston: + + # Which blocks should not be dispensed by dispensers + dispenser: + +############################################################ +# +------------------------------------------------------+ # +# | Essentials Spawn / New Players | # +# +------------------------------------------------------+ # +############################################################ + +# This section requires essentialsspawn.jar to work. + +newbies: + # Should we announce to the server when someone logs in for the first time? + # If so, use this format, replacing {DISPLAYNAME} with the player name. + # If not, set to '' + #announce-format: '' + announce-format: '&dWelcome {DISPLAYNAME}&d to the server!' + + # When we spawn for the first time, which spawnpoint do we use? + # Set to "none" if you want to use the spawn point of the world. + spawnpoint: newbies + + # Do we want to give users anything on first join? Set to '' to disable + # This kit will be given regardless of cost and permissions, and will not trigger the kit delay. + #kit: '' + kit: tools + +# Set this to lowest, if you want Multiverse to handle the respawning. +# Set this to high, if you want EssentialsSpawn to handle the respawning. +# Set this to highest, if you want to force EssentialsSpawn to handle the respawning. +respawn-listener-priority: high + +# When users die, should they respawn at their first home or bed, instead of the spawnpoint? +respawn-at-home: false + +# End of file <-- No seriously, you're done with configuration. diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/mobcleaner-example.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/mobcleaner-example.yml new file mode 100644 index 000000000..3a3a9d803 --- /dev/null +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/mobcleaner-example.yml @@ -0,0 +1,138 @@ +# This config was generated for version 1.9.0 +# Find the latest config at https://github.com/JasonHorkles/EntityClearer/blob/main/config.yml + +# This plugin uses bStats metrics +# https://bstats.org/plugin/bukkit/EntityClearer/10915 +# Metrics can be disabled in the bStats config.yml + +# Interval in minutes the clear task should run +interval: 15 + +# The sound to play when showing the warnings +# See https://minecraft.fandom.com/wiki/Sounds.json#Java_Edition_values for all sounds +# Set to '' for no sound +sound: 'ui.button.click' + +# The worlds to check and remove entities from +# Make sure to add your world(s) if they're not already there +# See https://papermc.io/javadocs/paper/1.16/org/bukkit/entity/EntityType.html for all entities +worlds: + world: + # The entities to be removed from the world + entities: + - BEE + - CAVE_SPIDER + - CHICKEN + - CREEPER + - SHEEP + - SKELETON + - SLIME + - SPIDER + - SQUID + - WOLF + - ZOMBIE + + # Should only entities with a specific spawn reason be removed? + # Setting this to false will check for any spawn reason + spawn-reason: + enabled: false + # See https://papermc.io/javadocs/paper/1.16/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html for all spawn reasons + reasons: + - SPAWNER + - SPAWNER_EGG + world_nether: + entities: + - BLAZE + - ZOMBIFIED_PIGLIN + spawn-reason: + enabled: false + reasons: [] +# creative: +# entities: +# - ARMOR_STAND +# - ARROW +# - BOAT +# - DROPPED_ITEM +# - MINECART +# - MINECART_CHEST +# - MINECART_FURNACE +# - MINECART_HOPPER +# - MINECART_TNT +# spawn-reason: +# enabled: false +# reasons: [] + +nearby-entities: + # Should the plugin only remove entities that have multiple entities nearby? + # This is useful to only remove large groups of entities while leaving smaller entity groups alone + # Using this would prevent every entity from being removed then just respawning again naturally + enabled: true + + # Only apply to worlds with the mob spawning gamerule enabled? + gamerule-enabled-only: false + + # The distance in blocks that the plugin should check for extra entities + x: 3 + y: 3 + z: 3 + + # How many additional entities must be around the first entity to be removed? + count: 4 + +# Should using the command /clearentities start the countdown or instantly remove the entities? +countdown-on-command: true + +# Should named entities be removed? +remove-named: false + +messages: + # Should there be action bar messages? + actionbar: true + + # Should there be chat messages? + chat: false + actionbar-message: "&6&lCommon entities will be removed in &e&l{SECONDS} &6&lsecond{S}!" + actionbar-completed-message: "&6&lRemoved &e&l{ENTITIES} &6&lentities!" + chat-message: "&cCommon entities will be removed in &7{SECONDS} &csecond{S}!" + chat-completed-message: "&cRemoved &7{ENTITIES} ¢ities!" + +# When should the warning messages send? +# Time is in seconds remaining before the clear task +warning-messages: + 60-seconds: false + 45-seconds: false + 30-seconds: true + 15-seconds: false + 5-seconds: true + 4-seconds: true + 3-seconds: true + 2-seconds: true + 1-second: true + +# The command(s) to run after the entity clearing task runs +commands: +# - nuke + +low-tps: + # Should the entity removal task be triggered when the TPS is low? + enabled: false + + # Below what TPS should the plugin remove the entities? + threshold: 17 + + # Should the plugin remove the entities instantly or trigger the countdown? + # Warning: If the TPS is too low and remove-instantly is false, the plugin may + # not be able to remove the entities before the server crashes + remove-instantly: true + + # Should there be a chat message sent to players with the `entityclearer.lowtps` + # permission stating that the TPS is low? + chat: true + chat-message: "&c&lWarning: TPS low &8&l(&7&l{TPS}&8&l)&c&l! Removing entities..." + +############################################### + +# Debug stuff +# You shouldn't need to touch this unless specifically asked to do so :) +print-stack-traces: false +debug: false From 3cdcc0445aaa2a97e0d65e502e2fa2a9e08f5654 Mon Sep 17 00:00:00 2001 From: zml Date: Sat, 14 Oct 2023 18:53:46 -0700 Subject: [PATCH 10/17] fix(format/yaml): Re-serialize complex keys properly --- .../org/spongepowered/configurate/yaml/YamlRepresenter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java index 7945aef78..10d662d39 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java @@ -58,7 +58,7 @@ public Node representData(final Object nodeObject) { for (Map.Entry ent : node.childrenMap().entrySet()) { // SnakeYAML supports both key and value comments. Add the comments on the key final Node value = represent(ent.getValue()); - final Node key = represent(String.valueOf(ent.getKey())); + final Node key = represent(ent.getKey()); key.setBlockComments(value.getBlockComments()); value.setBlockComments(Collections.emptyList()); From 81058813329abc29135a1d99ddec6afd393f6bd5 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 15 Oct 2023 11:41:35 +0200 Subject: [PATCH 11/17] Empty comments should be empty line --- .../org/spongepowered/configurate/yaml/YamlRepresenter.java | 3 +++ .../configurate/yaml/YamlConfigurationLoaderTest.java | 1 + 2 files changed, 4 insertions(+) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java index 10d662d39..106a30ed0 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java @@ -90,6 +90,9 @@ public Node representData(final Object nodeObject) { } private CommentLine commentLineFor(final String comment) { + if (comment.isEmpty()) { + return new CommentLine(null, null, "", CommentType.BLANK_LINE); + } // prepend a space before the comment: // before: #hello // after: # hello diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index debabc641..b9e636c01 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -210,6 +210,7 @@ void testRoundtripEssXLegacy(final @TempDir Path tempDir) throws IOException { .path(destination) .url(source) .nodeStyle(NodeStyle.BLOCK) + .indent(2) .build(); final ConfigurationNode sourceNode = loader.load(); From 3de536835b752e03d4677fdeede15d0adb227cef Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 15 Oct 2023 11:46:36 +0200 Subject: [PATCH 12/17] Represent null as empty string instead of literal null --- .../configurate/yaml/YamlRepresenter.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java index 106a30ed0..b1aee8475 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java @@ -23,11 +23,13 @@ import org.spongepowered.configurate.ConfigurationNode; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.DumperOptions.ScalarStyle; import org.yaml.snakeyaml.comments.CommentLine; import org.yaml.snakeyaml.comments.CommentType; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.ScalarNode; import org.yaml.snakeyaml.nodes.SequenceNode; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.representer.Represent; @@ -45,6 +47,7 @@ final class YamlRepresenter extends Representer { YamlRepresenter(final DumperOptions options) { super(options); multiRepresenters.put(ConfigurationNode.class, new ConfigurationNodeRepresent()); + nullRepresenter = new EmptyNullRepresenter(); } private final class ConfigurationNodeRepresent implements Represent { @@ -100,4 +103,11 @@ private CommentLine commentLineFor(final String comment) { } } + private static final class EmptyNullRepresenter implements Represent { + @Override + public Node representData(Object data) { + return new ScalarNode(Tag.NULL, "", null, null, ScalarStyle.PLAIN); + } + } + } From c33572d682dfdedd9bf8a106f8ff02ecfc27e5c2 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 15 Oct 2023 12:02:16 +0200 Subject: [PATCH 13/17] Made the line length configurable --- .../yaml/YamlConfigurationLoader.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java index f594f8c3e..9a64e571f 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java @@ -72,6 +72,8 @@ public static Builder builder() { *
Equivalent to {@link #nodeStyle(NodeStyle)}
*
<prefix>.yaml.comments-enabled
*
Equivalent to {@link #commentsEnabled(boolean)}
+ *
<prefix>.yaml.line-length
+ *
Equivalent to {@link #lineLength(int)}
* * * @since 4.0.0 @@ -80,6 +82,7 @@ public static final class Builder extends AbstractConfigurationLoader.BuilderThe default value is {@code 150}

+ * + * @param lineLength the maximum length of a configuration line + * @return this builder (for chaining) + * @since 4.2.0 + */ + public Builder lineLength(final int lineLength) { + this.lineLength = lineLength; + return this; + } + + /** + * Get the maximum length of a configuration line. + * + * @return the maximum length of a configuration line + * @see #lineLength(int) for details on the line length + * @since 4.2.0 + */ + public int lineLength() { + return this.lineLength; + } + @Override public YamlConfigurationLoader build() { return new YamlConfigurationLoader(this); @@ -207,6 +236,7 @@ private YamlConfigurationLoader(final Builder builder) { final DumperOptions opts = builder.options; opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.style)); opts.setProcessComments(builder.commentsEnabled()); + opts.setWidth(builder.lineLength()); // the constructor needs ConfigurationOptions, which is only available when called (loadInternal) this.constructor = ThreadLocal.withInitial(() -> new YamlConstructor(loaderOpts)); this.yaml = ThreadLocal.withInitial(() -> new Yaml(this.constructor.get(), new YamlRepresenter(opts), opts, loaderOpts)); From ffcbfcbd9fa12bc028cb0a3ddb4a599a80d6e08e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 16 Oct 2023 11:01:44 +0200 Subject: [PATCH 14/17] Fix checkstyle --- .../org/spongepowered/configurate/yaml/YamlConstructor.java | 1 - .../org/spongepowered/configurate/yaml/YamlRepresenter.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java index 1f944a278..f9c139fd0 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -63,7 +63,6 @@ protected Object constructObjectNoCheck(final Node yamlNode) { node.raw(Collections.emptyMap()); ((MappingNode) yamlNode).getValue().forEach(tuple -> { - // I don't think it's possible to have a non-scalar node as key final ConfigurationNode keyNode = (ConfigurationNode) this.constructObject(tuple.getKeyNode()); final Node valueNode = tuple.getValueNode(); diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java index b1aee8475..c2d0d014b 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java @@ -105,7 +105,7 @@ private CommentLine commentLineFor(final String comment) { private static final class EmptyNullRepresenter implements Represent { @Override - public Node representData(Object data) { + public Node representData(final Object data) { return new ScalarNode(Tag.NULL, "", null, null, ScalarStyle.PLAIN); } } From f8acad91c8eee4e21bf7115c6907e8f3a32c1dc4 Mon Sep 17 00:00:00 2001 From: zml Date: Sun, 15 Oct 2023 20:03:41 -0700 Subject: [PATCH 15/17] chore(yaml): Clear up some stream usage --- .../configurate/yaml/YamlConstructor.java | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java index f9c139fd0..9a09482a9 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -21,18 +21,19 @@ import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.loader.AbstractConfigurationLoader; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.comments.CommentLine; import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeId; +import org.yaml.snakeyaml.nodes.NodeTuple; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; -import java.util.stream.Collectors; class YamlConstructor extends Constructor { @@ -62,7 +63,7 @@ protected Object constructObjectNoCheck(final Node yamlNode) { // make sure to mark it as a map type, even if the map itself is empty node.raw(Collections.emptyMap()); - ((MappingNode) yamlNode).getValue().forEach(tuple -> { + for (final NodeTuple tuple : ((MappingNode) yamlNode).getValue()) { final ConfigurationNode keyNode = (ConfigurationNode) this.constructObject(tuple.getKeyNode()); final Node valueNode = tuple.getValueNode(); @@ -70,7 +71,7 @@ protected Object constructObjectNoCheck(final Node yamlNode) { node.node(keyNode.raw()) .from((ConfigurationNode) constructObject(valueNode)) .comment(commentFor(tuple.getKeyNode().getBlockComments())); - }); + } return node.comment(commentFor(yamlNode.getBlockComments())); } @@ -80,9 +81,9 @@ protected Object constructObjectNoCheck(final Node yamlNode) { // make sure to mark it as a list type, even if the collection itself is empty node.raw(Collections.emptyList()); - ((Collection) raw).forEach(value -> { + for (final Object value : (Collection) raw) { node.appendListNode().from((ConfigurationNode) value); - }); + } } else { node.raw(raw); } @@ -94,16 +95,20 @@ protected Object constructObjectNoCheck(final Node yamlNode) { if (commentLines == null || commentLines.isEmpty()) { return null; } - return commentLines.stream() - .map(input -> { - final String lineStripped = removeLineBreaksForLine(input.getValue()); - if (!lineStripped.isEmpty() && lineStripped.charAt(0) == ' ') { - return lineStripped.substring(1); - } else { - return lineStripped; - } - }) - .collect(Collectors.joining("\n")); + + final StringBuilder outputBuilder = new StringBuilder(); + for (final CommentLine line : commentLines) { + if (outputBuilder.length() > 0) { + outputBuilder.append(AbstractConfigurationLoader.CONFIGURATE_LINE_SEPARATOR); + } + final String lineStripped = removeLineBreaksForLine(line.getValue()); + if (!lineStripped.isEmpty() && lineStripped.charAt(0) == ' ') { + outputBuilder.append(lineStripped, 1, lineStripped.length()); + } else { + outputBuilder.append(lineStripped); + } + } + return outputBuilder.toString(); } private static String removeLineBreaksForLine(final String line) { From e5986b47a15baaeb3d0a735ec303ee5b3414b846 Mon Sep 17 00:00:00 2001 From: zml Date: Sun, 15 Oct 2023 22:51:24 -0700 Subject: [PATCH 16/17] chore(yaml): Tweak formatting, preserve scalar + node styles --- .../configurate/yaml/NodeStyle.java | 13 ++ .../configurate/yaml/ScalarStyle.java | 116 ++++++++++++++++++ .../yaml/YamlConfigurationLoader.java | 24 +++- .../configurate/yaml/YamlConstructor.java | 14 ++- .../configurate/yaml/YamlRepresenter.java | 47 ++++++- .../yaml/YamlConfigurationLoaderTest.java | 2 + 6 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/NodeStyle.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/NodeStyle.java index a269d280a..8fc9277dc 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/NodeStyle.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/NodeStyle.java @@ -47,4 +47,17 @@ static DumperOptions.FlowStyle asSnakeYaml(final @Nullable NodeStyle style) { return style == null ? DumperOptions.FlowStyle.AUTO : style.snake; } + static @Nullable NodeStyle fromSnakeYaml(final DumperOptions.FlowStyle style) { + switch (style) { + case AUTO: + return null; + case BLOCK: + return BLOCK; + case FLOW: + return FLOW; + default: + throw new IllegalArgumentException("Unknown style " + style); + } + } + } diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java new file mode 100644 index 000000000..a0c6a2859 --- /dev/null +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java @@ -0,0 +1,116 @@ +/* + * Configurate + * Copyright (C) zml and Configurate contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.spongepowered.configurate.yaml; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.yaml.snakeyaml.DumperOptions; + +import java.util.EnumMap; +import java.util.Map; + +/** + * Style that can be used to represent a scalar. + * + * @since 4.2.0 + */ +public enum ScalarStyle { + + /** + * A double-quoted string. + * + *
"hello world"
+ * + * @since 4.2.0 + */ + DOUBLE_QUOTED(DumperOptions.ScalarStyle.DOUBLE_QUOTED), + + /** + * A single-quoted string. + * + *
'hello world'
+ * + * @since 4.2.0 + */ + SINGLE_QUOTED(DumperOptions.ScalarStyle.SINGLE_QUOTED), + + /** + * String without any quotation. + * + *

This may be ambiguous with non-string types.

+ * + * @since 4.2.0 + */ + UNQUOTED(DumperOptions.ScalarStyle.PLAIN), + + /** + * Folded scalar. + * + *
{@code
+     * key: >
+     *   folded scalar
+     *   line breaks collapsed
+     * }
+ * + * @since 4.2.0 + */ + FOLDED(DumperOptions.ScalarStyle.FOLDED), + + /** + * Literal scalar. + * + *
{@code
+     * key: |
+     *   literal scalar
+     *   line breaks preserved
+     * }
+ * + * @since 4.2.0 + */ + LITERAL(DumperOptions.ScalarStyle.LITERAL) + ; + + private static final Map BY_SNAKE = new EnumMap<>(DumperOptions.ScalarStyle.class); + private final DumperOptions.ScalarStyle snake; + + ScalarStyle(final DumperOptions.ScalarStyle snake) { + this.snake = snake; + } + + static DumperOptions.ScalarStyle asSnakeYaml( + final @Nullable ScalarStyle style, + final DumperOptions.@Nullable ScalarStyle fallback + ) { + if (style != null) { + return style.snake; + } else if (fallback != null) { + return fallback; + } else { + return DumperOptions.ScalarStyle.PLAIN; + } + } + + static ScalarStyle fromSnakeYaml(final DumperOptions.ScalarStyle style) { + return BY_SNAKE.getOrDefault(style, UNQUOTED); + } + + static { + for (final ScalarStyle style : values()) { + BY_SNAKE.put(style.snake, style); + } + } + +} diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java index 9a64e571f..beb27bd0d 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java @@ -20,6 +20,7 @@ import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.RepresentationHint; import org.spongepowered.configurate.loader.AbstractConfigurationLoader; import org.spongepowered.configurate.loader.CommentHandler; import org.spongepowered.configurate.loader.CommentHandlers; @@ -53,6 +54,25 @@ public final class YamlConfigurationLoader extends AbstractConfigurationLoaderIf the chosen scalar style would produce syntactically invalid YAML, a + * valid one will replace it.

+ * + * @since 4.2.0 + */ + public static final RepresentationHint SCALAR_STYLE = RepresentationHint.of("configurate:yaml/scalarstyle", ScalarStyle.class); + + /** + * The YAML node style to use for collection nodes. A {@code null} value + * will instruct the emitter to fall back to the + * {@link Builder#nodeStyle()} setting. + * + * @since 4.2.0 + */ + public static final RepresentationHint NODE_STYLE = RepresentationHint.of("configurate:yaml/nodestyle", NodeStyle.class); + /** * Creates a new {@link YamlConfigurationLoader} builder. * @@ -237,9 +257,11 @@ private YamlConfigurationLoader(final Builder builder) { opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.style)); opts.setProcessComments(builder.commentsEnabled()); opts.setWidth(builder.lineLength()); + opts.setIndicatorIndent(builder.indent()); + opts.setIndentWithIndicator(true); // the constructor needs ConfigurationOptions, which is only available when called (loadInternal) this.constructor = ThreadLocal.withInitial(() -> new YamlConstructor(loaderOpts)); - this.yaml = ThreadLocal.withInitial(() -> new Yaml(this.constructor.get(), new YamlRepresenter(opts), opts, loaderOpts)); + this.yaml = ThreadLocal.withInitial(() -> new Yaml(this.constructor.get(), new YamlRepresenter(true, opts), opts, loaderOpts)); } @Override diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java index 9a09482a9..3818f80c9 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java @@ -29,6 +29,8 @@ import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeId; import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.ScalarNode; +import org.yaml.snakeyaml.nodes.SequenceNode; import java.util.Collection; import java.util.Collections; @@ -62,8 +64,12 @@ protected Object constructObjectNoCheck(final Node yamlNode) { if (yamlNode.getNodeId() == NodeId.mapping) { // make sure to mark it as a map type, even if the map itself is empty node.raw(Collections.emptyMap()); + final MappingNode mapping = (MappingNode) yamlNode; + if (mapping.getFlowStyle() != null) { + node.hint(YamlConfigurationLoader.NODE_STYLE, NodeStyle.fromSnakeYaml(mapping.getFlowStyle())); + } - for (final NodeTuple tuple : ((MappingNode) yamlNode).getValue()) { + for (final NodeTuple tuple : mapping.getValue()) { final ConfigurationNode keyNode = (ConfigurationNode) this.constructObject(tuple.getKeyNode()); final Node valueNode = tuple.getValueNode(); @@ -80,11 +86,17 @@ protected Object constructObjectNoCheck(final Node yamlNode) { if (raw instanceof Collection) { // make sure to mark it as a list type, even if the collection itself is empty node.raw(Collections.emptyList()); + if (((SequenceNode) yamlNode).getFlowStyle() != null) { + node.hint(YamlConfigurationLoader.NODE_STYLE, NodeStyle.fromSnakeYaml(((SequenceNode) yamlNode).getFlowStyle())); + } for (final Object value : (Collection) raw) { node.appendListNode().from((ConfigurationNode) value); } } else { + if (yamlNode instanceof ScalarNode) { + node.hint(YamlConfigurationLoader.SCALAR_STYLE, ScalarStyle.fromSnakeYaml(((ScalarNode) yamlNode).getScalarStyle())); + } node.raw(raw); } diff --git a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java index c2d0d014b..a018bb349 100644 --- a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java +++ b/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java @@ -44,8 +44,13 @@ final class YamlRepresenter extends Representer { - YamlRepresenter(final DumperOptions options) { + private static final CommentLine BLANK_LINE = new CommentLine(null, null, "", CommentType.BLANK_LINE); + + private final boolean padComments; + + YamlRepresenter(final boolean padComments, final DumperOptions options) { super(options); + this.padComments = padComments; multiRepresenters.put(ConfigurationNode.class, new ConfigurationNodeRepresent()); nullRepresenter = new EmptyNullRepresenter(); } @@ -58,24 +63,49 @@ public Node representData(final Object nodeObject) { final Node yamlNode; if (node.isMap()) { final List children = new ArrayList<>(); + boolean first = true; for (Map.Entry ent : node.childrenMap().entrySet()) { // SnakeYAML supports both key and value comments. Add the comments on the key final Node value = represent(ent.getValue()); final Node key = represent(ent.getKey()); key.setBlockComments(value.getBlockComments()); value.setBlockComments(Collections.emptyList()); + if ( + !first + && !(node.parent() != null && node.parent().isList()) + && key.getBlockComments() != null + && !key.getBlockComments().isEmpty() + ) { + key.getBlockComments().add(0, BLANK_LINE); + } + first = false; children.add(new NodeTuple(key, value)); } - yamlNode = new MappingNode(Tag.MAP, children, FlowStyle.AUTO); + yamlNode = new MappingNode(Tag.MAP, children, this.flowStyle(node)); } else if (node.isList()) { final List children = new ArrayList<>(); for (ConfigurationNode ent : node.childrenList()) { children.add(represent(ent)); } - yamlNode = new SequenceNode(Tag.SEQ, children, FlowStyle.AUTO); + yamlNode = new SequenceNode(Tag.SEQ, children, this.flowStyle(node)); } else { - yamlNode = represent(node.rawScalar()); + final Node optionNode = represent(node.rawScalar()); + final org.spongepowered.configurate.yaml.@Nullable ScalarStyle requestedStyle + = node.ownHint(YamlConfigurationLoader.SCALAR_STYLE); + if (optionNode instanceof ScalarNode && requestedStyle != null) { + final ScalarNode scalar = (ScalarNode) optionNode; + yamlNode = new ScalarNode( + scalar.getTag(), + scalar.getValue(), + scalar.getStartMark(), + scalar.getEndMark(), + org.spongepowered.configurate.yaml.ScalarStyle.asSnakeYaml(requestedStyle, scalar.getScalarStyle()) + ); + } else { + yamlNode = optionNode; + } + } if (node instanceof CommentedConfigurationNodeIntermediary) { @@ -92,9 +122,16 @@ public Node representData(final Object nodeObject) { return yamlNode; } + private FlowStyle flowStyle(final ConfigurationNode node) { + final @Nullable NodeStyle requested = node.ownHint(YamlConfigurationLoader.NODE_STYLE); + return NodeStyle.asSnakeYaml(requested); + } + private CommentLine commentLineFor(final String comment) { if (comment.isEmpty()) { - return new CommentLine(null, null, "", CommentType.BLANK_LINE); + return BLANK_LINE; + } else if (!YamlRepresenter.this.padComments || comment.charAt(0) == '#') { + return new CommentLine(null, null, comment, CommentType.BLOCK); } // prepend a space before the comment: // before: #hello diff --git a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java index b9e636c01..a5a087f9f 100644 --- a/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java +++ b/format/yaml/src/test/java/org/spongepowered/configurate/yaml/YamlConfigurationLoaderTest.java @@ -192,6 +192,7 @@ void testRoundtripEssX(final @TempDir Path tempDir) throws IOException { final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() .path(destination) .url(source) + .indent(2) .nodeStyle(NodeStyle.BLOCK) .build(); @@ -227,6 +228,7 @@ void testRoundtripMobCleaner(final @TempDir Path tempDir) throws IOException { final YamlConfigurationLoader loader = YamlConfigurationLoader.builder() .path(destination) .url(source) + .indent(2) .nodeStyle(NodeStyle.BLOCK) .build(); From ce367360aa7cabbb083566d15a5e59d4b3f9802a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 1 Feb 2024 22:03:05 +0100 Subject: [PATCH 17/17] Update test files to match latest changes --- .../org/spongepowered/configurate/yaml/comments-test.yml | 6 +++--- .../org/spongepowered/configurate/yaml/write-expected.yml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-test.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-test.yml index 8d02ab2b1..cea852f3a 100644 --- a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-test.yml +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/comments-test.yml @@ -4,6 +4,6 @@ waffles-with-syrup: # comments ingredients: # would you've guessed the ingredients? - - waffles - # I certainly didn't - - syrup + - waffles + # I certainly didn't + - syrup diff --git a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/write-expected.yml b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/write-expected.yml index e3ea843b4..3aea74347 100644 --- a/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/write-expected.yml +++ b/format/yaml/src/test/resources/org/spongepowered/configurate/yaml/write-expected.yml @@ -2,7 +2,7 @@ mapping: first: hello second: world list: -- 1 -- 2 -- 3 -- 4 + - 1 + - 2 + - 3 + - 4