Skip to content

Commit 5d2db39

Browse files
committed
yaml: whitespace/comment fixes
1 parent 0ece4c2 commit 5d2db39

File tree

13 files changed

+204
-130
lines changed

13 files changed

+204
-130
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Configurate
3+
* Copyright (C) zml and Configurate contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.spongepowered.configurate.yaml;
18+
19+
final class MergeTag {
20+
21+
static final MergeTag INSTANCE = new MergeTag();
22+
23+
private MergeTag() {
24+
}
25+
26+
}

format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ static ScalarStyle fromSnakeYaml(final DumperOptions.ScalarStyle style) {
113113
}
114114

115115
static {
116-
for (ScalarStyle style : values()) {
116+
for (final ScalarStyle style : values()) {
117117
BY_SNAKE.put(style.snake, style);
118118
}
119119
}

format/yaml/src/main/java/org/spongepowered/configurate/yaml/Tag.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ public String toString(final String own) {
6969
this(tagUri, supportedTypes, pattern, null);
7070
}
7171

72-
Scalar(final URI tagUri, final Set<Class<? extends V>> supportedTypes, final @Nullable Pattern pattern, final @Nullable ScalarStyle preferredScalarStyle) {
72+
Scalar(
73+
final URI tagUri,
74+
final Set<Class<? extends V>> supportedTypes,
75+
final @Nullable Pattern pattern,
76+
final @Nullable ScalarStyle preferredScalarStyle
77+
) {
7378
super(tagUri, supportedTypes);
7479
this.pattern = pattern;
7580
this.preferredScalarStyle = preferredScalarStyle;
@@ -89,7 +94,8 @@ public String toString(final String own) {
8994
}
9095

9196
/**
92-
* Get the preferred scalar style to use for this type, when none is specifically used.
97+
* Get the preferred scalar style to use for this type, when none is
98+
* specifically used.
9399
*
94100
* @return the preferred scalar style
95101
* @since 4.2.0

format/yaml/src/main/java/org/spongepowered/configurate/yaml/Yaml11Tags.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,16 @@ public String toString(final byte[] own) {
7373
*
7474
* @implNote Canonically, these are y|n in YAML 1.1, but because YAML 1.2
7575
* will only support true|false, we will treat those as the default
76-
* output format.
76+
* output format. We also modify the regex to only automatically match
77+
* true/false and on/off in order to avoid cases of confusion (such as
78+
* the classic Norway problem).
7779
* @see <a href="https://yaml.org/type/bool.html">tag:yaml.org,2002:bool</a>
7880
* @since 4.2.0
7981
*/
8082
public static final Tag.Scalar<Boolean> BOOL = new Tag.Scalar<Boolean>(
8183
yamlOrg("bool"),
8284
UnmodifiableCollections.toSet(Boolean.class),
83-
Pattern.compile("y|Y|yes|Yes|YES|n|N|no|No|NO"
84-
+ "|true|True|TRUE|false|False|FALSE"
85+
Pattern.compile("true|True|TRUE|false|False|FALSE"
8586
+ "|on|On|ON|off|Off|OFF")
8687
) {
8788
private final Set<String> trues = UnmodifiableCollections.toSet(
@@ -189,13 +190,13 @@ public String toString(final Number own) {
189190
// used as map key, where the next node will be a reference that should be merged in to this node
190191

191192
@Override
192-
public Object fromString(final String input) throws ParsingException {
193-
throw new ParsingException(ParsingException.UNKNOWN_POS, ParsingException.UNKNOWN_POS, null, "Merge keys are not yet implemented", null);
193+
public Object fromString(final String input) {
194+
return MergeTag.INSTANCE;
194195
}
195196

196197
@Override
197-
public String toString(final Object own) {
198-
return own.toString();
198+
public String toString(final Object own) throws ParsingException {
199+
throw new ParsingException(ParsingException.UNKNOWN_POS, ParsingException.UNKNOWN_POS, null, "Merge keys cannot be serialized", null);
199200
}
200201
};
201202

format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
import org.spongepowered.configurate.util.UnmodifiableCollections;
3131
import org.yaml.snakeyaml.DumperOptions;
3232
import org.yaml.snakeyaml.LoaderOptions;
33-
import org.yaml.snakeyaml.reader.StreamReader;
3433
import org.yaml.snakeyaml.events.DocumentStartEvent;
34+
import org.yaml.snakeyaml.reader.StreamReader;
3535

3636
import java.io.BufferedReader;
3737
import java.io.Writer;
@@ -210,6 +210,8 @@ protected void populate(final LoaderOptionSource options) {
210210
if (declared != null) {
211211
this.style = declared;
212212
}
213+
214+
this.enableComments = options.getBoolean(false, "yaml", "comments-enabled");
213215
}
214216

215217
/**
@@ -317,7 +319,6 @@ public YamlConfigurationLoader build() {
317319
private final DumperOptions options;
318320
private final YamlVisitor visitor;
319321
private final @Nullable NodeStyle defaultNodeStyle;
320-
private final boolean enableComments;
321322

322323
private YamlConfigurationLoader(final Builder builder) {
323324
super(builder, new CommentHandler[] {CommentHandlers.HASH});
@@ -326,19 +327,18 @@ private YamlConfigurationLoader(final Builder builder) {
326327
opts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.nodeStyle()));
327328
opts.setProcessComments(builder.commentsEnabled());
328329
this.defaultNodeStyle = builder.nodeStyle();
329-
this.enableComments = builder.commentsEnabled();
330330
this.options = opts;
331331
this.loader = new LoaderOptions()
332332
.setAcceptTabs(true)
333333
.setProcessComments(builder.commentsEnabled());
334334
this.loader.setCodePointLimit(Integer.MAX_VALUE);
335-
this.visitor = new YamlVisitor(this.enableComments, true, Yaml11Tags.REPOSITORY);
335+
this.visitor = new YamlVisitor(true, Yaml11Tags.REPOSITORY);
336336
}
337337

338338
@Override
339339
protected void loadInternal(final CommentedConfigurationNode node, final BufferedReader reader) throws ParsingException {
340340
// Match the superclass implementation, except we substitute our own scanner implementation
341-
final YamlParserComposer parser = new YamlParserComposer(new StreamReader(reader), this.loader, Yaml11Tags.REPOSITORY, this.enableComments);
341+
final YamlParserComposer parser = new YamlParserComposer(new StreamReader(reader), this.loader, Yaml11Tags.REPOSITORY);
342342
parser.singleDocumentStream(node);
343343
}
344344

format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlParserComposer.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ final class YamlParserComposer extends ParserImpl {
6666
private static final int FRAME_STACK_INCREMENT = 8;
6767

6868
private @Nullable StringBuilder commentCollector;
69-
private final boolean processComments;
7069
private final boolean stripLeadingCommentWhitespace = true;
7170
final Map<String, ConfigurationNode> aliases = new HashMap<>();
7271
final TagRepository tags;
@@ -75,9 +74,8 @@ final class YamlParserComposer extends ParserImpl {
7574
private Frame[] frames = new Frame[INITIAL_STACK_SIZE];
7675
private int framePointer = -1;
7776

78-
YamlParserComposer(final StreamReader reader, final LoaderOptions opts, final TagRepository tags, final boolean enableComments) {
77+
YamlParserComposer(final StreamReader reader, final LoaderOptions opts, final TagRepository tags) {
7978
super(new ScannerImpl(reader, opts));
80-
this.processComments = enableComments;
8179
this.tags = tags;
8280
}
8381

@@ -226,6 +224,9 @@ static class Frame {
226224

227225
static final int SUPPRESS_COMMENTS = 1; // whether to associate comment events with this node
228226
static final int SAVE_NODE = 1 << 1; // don't clear node when popping
227+
static final int MERGE_REFERENCE_VALUE = 1 << 2; // when values have an anchor
228+
229+
static final int UNINHERITABLE_FLAGS = MERGE_REFERENCE_VALUE;
229230

230231
@MonotonicNonNull ComposerState state;
231232

@@ -236,13 +237,13 @@ static class Frame {
236237
* tag resolution.</p>
237238
*/
238239
@Nullable Tag resolvedTag;
239-
ConfigurationNode node;
240+
@MonotonicNonNull ConfigurationNode node;
240241
int flags;
241242

242243
void init(final ComposerState state, final Frame parent) {
243244
this.state = state;
244245
this.node = parent.node;
245-
this.flags = parent.flags;
246+
this.flags = parent.flags & ~UNINHERITABLE_FLAGS;
246247
this.resolvedTag = null;
247248
}
248249

@@ -323,10 +324,6 @@ void applyComment(final @Nullable String comment, final ConfigurationNode node)
323324
}
324325

325326
void collectComments() {
326-
if (!this.processComments) {
327-
return;
328-
}
329-
330327
while (this.peekEvent().is(Event.ID.Comment)) {
331328
final CommentEvent event = (CommentEvent) this.getEvent();
332329
if (event.getCommentType() != CommentType.BLANK_LINE) {
@@ -342,6 +339,10 @@ void collectComments() {
342339
} else {
343340
commentCollector.append(event.getValue());
344341
}
342+
} else if (this.commentCollector != null
343+
&& this.commentCollector.length() > 0
344+
&& this.peekEvent().is(Event.ID.Comment)) { // mid-comment blank line
345+
this.commentCollector.append("\n");
345346
}
346347
}
347348
}
@@ -607,6 +608,11 @@ public Frame accept(final Frame head, final YamlParserComposer self) throws Pars
607608
if (keyHolder == null) {
608609
throw new IllegalStateException("null keyHolder");
609610
}
611+
// if merge key, set a flag on the next value state
612+
if (keyHolder.ownHint(YamlConfigurationLoader.TAG) == Yaml11Tags.MERGE) {
613+
head.addFlag(Frame.MERGE_REFERENCE_VALUE);
614+
}
615+
610616
final @Nullable Object key = keyHolder.raw();
611617
if (key == null) {
612618
throw head.makeError(self.scanner.peekToken().getStartMark(), "'null' is not permitted as a mapping key", null);
@@ -683,8 +689,14 @@ private Alias() {
683689
if (target == null) {
684690
throw head.makeError(event.getStartMark(), "Unknown anchor '" + event.getAnchor() + "'", null);
685691
}
686-
head.node.from(target); // TODO: Reference node types
687-
head.node.hint(YamlConfigurationLoader.ANCHOR_ID, null); // don't duplicate alias
692+
final ConfigurationNode into;
693+
if (head.hasFlag(Frame.MERGE_REFERENCE_VALUE)) {
694+
into = head.node.parent();
695+
} else {
696+
into = head.node;
697+
}
698+
into.from(target); // TODO: Reference node types
699+
into.hint(YamlConfigurationLoader.ANCHOR_ID, null); // don't duplicate alias
688700
return null;
689701
}
690702

format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlVisitor.java

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,17 @@ final class YamlVisitor implements ConfigurationVisitor<YamlVisitor.State, Void,
5252
null,
5353
null
5454
);
55-
private static final CommentEvent COMMENT_BLANK_LINE = new CommentEvent(CommentType.BLOCK, "", null, null);
55+
private static final CommentEvent COMMENT_BLANK_LINE = new CommentEvent(CommentType.BLANK_LINE, "", null, null);
5656
static final StreamStartEvent STREAM_START = new StreamStartEvent(null, null);
5757
static final StreamEndEvent STREAM_END = new StreamEndEvent(null, null);
5858
static final DocumentEndEvent DOCUMENT_END = new DocumentEndEvent(null, null, false);
5959
private static final SequenceEndEvent SEQUENCE_END = new SequenceEndEvent(null, null);
6060
private static final MappingEndEvent MAPPING_END = new MappingEndEvent(null, null);
6161

6262
private final boolean shouldPadComments;
63-
private final boolean enableComments;
6463
private final TagRepository tags;
6564

66-
YamlVisitor(final boolean enableComments, final boolean shouldPadComments, final TagRepository tags) {
67-
this.enableComments = enableComments;
65+
YamlVisitor(final boolean shouldPadComments, final TagRepository tags) {
6866
this.shouldPadComments = shouldPadComments;
6967
this.tags = tags;
7068
}
@@ -81,12 +79,17 @@ public void beginVisit(final ConfigurationNode node, final State state) {
8179

8280
@Override
8381
public void enterNode(final ConfigurationNode node, final State state) throws ConfigurateException {
84-
if (node instanceof CommentedConfigurationNodeIntermediary<@NonNull ?> && this.enableComments) {
82+
if (node instanceof CommentedConfigurationNodeIntermediary<@NonNull ?> && state.options.isProcessComments()) {
8583
final @Nullable String comment = ((CommentedConfigurationNodeIntermediary<@NonNull ?>) node).comment();
8684
if (comment != null) {
87-
if (this.shouldPadComments && node != state.start && !node.parent().isList()) {
88-
// todo: try and avoid emitting a blank line when we're the first element of a mapping?
89-
state.emit(WHITESPACE);
85+
if (this.shouldPadComments && node != state.start) {
86+
if (!state.first) {
87+
if (!node.parent().isList()) {
88+
state.emit(WHITESPACE);
89+
}
90+
} else {
91+
state.first = false;
92+
}
9093
}
9194
for (final String line : COMMENT_SPLIT.split(comment, -1)) {
9295
if (line.isEmpty()) {
@@ -111,13 +114,14 @@ public void enterNode(final ConfigurationNode node, final State state) throws Co
111114
@Override
112115
public void enterMappingNode(final ConfigurationNode node, final State state) throws ConfigurateException {
113116
final TagRepository.AnalyzedTag analysis = this.tags.analyze(node);
117+
state.first = true;
114118
state.emit(new MappingStartEvent(
115119
this.anchor(node),
116120
analysis.actual().tagUri().toString(),
117121
analysis.implicit(),
118122
null,
119123
null,
120-
NodeStyle.asSnakeYaml(this.determineStyle(node, state))
124+
NodeStyle.asSnakeYaml(state.determineStyle(node))
121125
));
122126
}
123127

@@ -130,7 +134,7 @@ public void enterListNode(final ConfigurationNode node, final State state) throw
130134
analysis.implicit(),
131135
null,
132136
null,
133-
NodeStyle.asSnakeYaml(this.determineStyle(node, state))
137+
NodeStyle.asSnakeYaml(state.determineStyle(node))
134138
));
135139
}
136140

@@ -168,6 +172,7 @@ public void enterScalarNode(final ConfigurationNode node, final State state) thr
168172

169173
@Override
170174
public void exitMappingNode(final ConfigurationNode node, final State state) throws ConfigurateException {
175+
state.first = false; // only true if empty map
171176
state.emit(MAPPING_END);
172177
}
173178

@@ -181,29 +186,32 @@ public Void endVisit(final State state) {
181186
return null;
182187
}
183188

184-
private @Nullable NodeStyle determineStyle(final ConfigurationNode node, final State state) {
185-
// todo: some basic rules:
186-
// - if a node has any children with comments, convert it to block style
187-
// - when the default style is `AUTO` and `flowLevel` == 0,
188-
final @Nullable NodeStyle style = node.hint(YamlConfigurationLoader.NODE_STYLE);
189-
return style == null ? state.defaultStyle : style;
190-
}
191-
192189
private @Nullable String anchor(final ConfigurationNode node) {
193190
return node.hint(YamlConfigurationLoader.ANCHOR_ID);
194191
}
195192

196-
static class State {
193+
static final class State {
197194
private final Emitter emit;
195+
final DumperOptions options;
198196
@Nullable ConfigurationNode start;
199197
final @Nullable NodeStyle defaultStyle;
200198
ConfigurationNode mapKeyHolder;
199+
boolean first; // reset to true at the beginning of each mapping node
201200

202201
State(final DumperOptions options, final Writer writer, final @Nullable NodeStyle defaultStyle) {
203202
this.emit = new Emitter(writer, options);
203+
this.options = options;
204204
this.defaultStyle = defaultStyle;
205205
}
206206

207+
@Nullable NodeStyle determineStyle(final ConfigurationNode node) {
208+
// todo: some basic rules:
209+
// - if a node has any children with comments, convert it to block style
210+
// - when the default style is `AUTO` and `flowLevel` == 0,
211+
final @Nullable NodeStyle style = node.hint(YamlConfigurationLoader.NODE_STYLE);
212+
return style == null ? this.defaultStyle : style;
213+
}
214+
207215
public void emit(final Event event) throws ConfigurateException {
208216
try {
209217
this.emit.emit(event);

format/yaml/src/test/groovy/org/spongepowered/configurate/yaml/IntegrationTests.groovy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class IntegrationTests implements YamlTest {
2828

2929
@Test
3030
void testEssentialsXDefault() {
31-
def input = this.class.getResourceAsStream("essx-example.yml").text
31+
def input = this.class.getResourceAsStream("essx-example.yml").getText('utf-8')
3232
def node = parseString(input)
3333

3434
def serialized = dump(node)
@@ -38,7 +38,7 @@ class IntegrationTests implements YamlTest {
3838

3939
@Test
4040
void testEssentialsXLegacy() {
41-
def input = this.class.getResourceAsStream("essx-legacy.yml").text
41+
def input = this.class.getResourceAsStream("essx-legacy.yml").getText('utf-8')
4242
def node = parseString(input)
4343

4444
def serialized = dump(node)
@@ -48,7 +48,7 @@ class IntegrationTests implements YamlTest {
4848

4949
@Test
5050
void testMobCleaner() {
51-
def input = this.class.getResourceAsStream("mobcleaner-example.yml").text
51+
def input = this.class.getResourceAsStream("mobcleaner-example.yml").getText('utf-8')
5252
def node = parseString(input)
5353

5454
def serialized = dump(node)

0 commit comments

Comments
 (0)