Skip to content

Commit 8cfd7d5

Browse files
authored
Fix Yaml lists (#216)
* fix yaml lists * unsychronize
1 parent ba2a194 commit 8cfd7d5

File tree

6 files changed

+78
-21
lines changed

6 files changed

+78
-21
lines changed

avaje-config/src/main/java/io/avaje/config/YamlLoaderSimple.java

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package io.avaje.config;
22

3+
import static java.util.stream.Collectors.joining;
4+
35
import java.io.IOException;
46
import java.io.InputStream;
57
import java.io.InputStreamReader;
68
import java.io.LineNumberReader;
79
import java.io.Reader;
810
import java.io.UncheckedIOException;
11+
import java.util.ArrayDeque;
912
import java.util.ArrayList;
13+
import java.util.Deque;
14+
import java.util.Iterator;
1015
import java.util.LinkedHashMap;
1116
import java.util.List;
1217
import java.util.Map;
13-
import java.util.Stack;
1418
import java.util.StringJoiner;
1519

1620
import org.jspecify.annotations.NullMarked;
@@ -42,12 +46,13 @@ enum MultiLineTrim {
4246
enum State {
4347
RequireKey,
4448
MultiLine,
49+
List,
4550
KeyOrValue,
4651
RequireTopKey
4752
}
4853

4954
private final Map<String, String> keyValues = new LinkedHashMap<>();
50-
private final Stack<Key> keyStack = new Stack<>();
55+
private final Deque<Key> keyStack = new ArrayDeque<>();
5156
private final List<String> multiLines = new ArrayList<>();
5257

5358
private State state = State.RequireKey;
@@ -80,7 +85,7 @@ private void processLine(String line) {
8085
} else {
8186
currentLine++;
8287
readIndent(line);
83-
if (state == State.MultiLine) {
88+
if (state == State.MultiLine || state == State.List) {
8489
processMultiLine(line);
8590
} else {
8691
processNext(line);
@@ -92,6 +97,9 @@ private void checkFinalMultiLine() {
9297
if (state == State.MultiLine) {
9398
addKeyVal(multiLineValue());
9499
}
100+
if (state == State.List) {
101+
addKeyVal(listValue());
102+
}
95103
}
96104

97105
private void processMultiLine(String line) {
@@ -112,18 +120,32 @@ private void processMultiLine(String line) {
112120
}
113121

114122
private void multiLineEnd(String line) {
115-
addKeyVal(multiLineValue());
123+
if (state == State.MultiLine) addKeyVal(multiLineValue());
124+
else {
125+
addKeyVal(listValue());
126+
}
116127
processNext(line);
117128
}
118129

130+
private String listValue() {
131+
if (multiLines.isEmpty()) {
132+
return "";
133+
}
134+
multiLineTrimTrailing();
135+
var result =
136+
multiLines.stream().map(s -> s.trim().substring(1).stripLeading()).collect(joining(","));
137+
multiLineEnd();
138+
return result;
139+
}
140+
119141
private String multiLineValue() {
120142
if (multiLines.isEmpty()) {
121143
return "";
122144
}
123145
if (multiLineTrim != MultiLineTrim.Keep) {
124146
multiLineTrimTrailing();
125147
}
126-
String join = (multiLineTrim == MultiLineTrim.Implicit) ? " " : "\n";
148+
String join = multiLineTrim == MultiLineTrim.Implicit ? " " : "\n";
127149
StringBuilder sb = new StringBuilder();
128150
int lastIndex = multiLines.size() - 1;
129151
for (int i = 0; i <= lastIndex; i++) {
@@ -184,6 +206,8 @@ private void processNext(String line) {
184206
if (trimmedValue.startsWith("|")) {
185207
multilineStart(multiLineTrimMode(trimmedValue));
186208

209+
} else if (trimmedValue.startsWith("-")) {
210+
listStart(multiLineTrimMode(trimmedValue));
187211
} else if (trimmedValue.isEmpty() || trimmedValue.startsWith("#")) {
188212
// empty or comment
189213
state = State.KeyOrValue;
@@ -230,7 +254,11 @@ private void processNonKey(String line) {
230254
if (currentIndent <= keyIndent) {
231255
throw new IllegalStateException("Value not indented enough for key " + fullKey() + " at line: " + currentLine + " line[" + line + "]");
232256
}
233-
multilineStart(MultiLineTrim.Implicit);
257+
if (line.stripLeading().charAt(0) == '-') {
258+
listStart(MultiLineTrim.Implicit);
259+
} else {
260+
multilineStart(MultiLineTrim.Implicit);
261+
}
234262
multiLineIndent = currentIndent;
235263
multiLines.add(line);
236264
}
@@ -241,6 +269,12 @@ private void multilineStart(MultiLineTrim trim) {
241269
multiLineTrim = trim;
242270
}
243271

272+
private void listStart(MultiLineTrim trim) {
273+
state = State.List;
274+
multiLineIndent = 0;
275+
multiLineTrim = trim;
276+
}
277+
244278
private void multiLineEnd() {
245279
state = State.RequireKey;
246280
multiLineIndent = 0;
@@ -281,8 +315,9 @@ private String unquoteValue(char quoteChar, String value) {
281315

282316
private String fullKey() {
283317
StringJoiner fullKey = new StringJoiner(".");
284-
for (Key next : keyStack) {
285-
fullKey.add(next.key());
318+
Iterator<Key> it = keyStack.descendingIterator();
319+
while (it.hasNext()) {
320+
fullKey.add(it.next().key());
286321
}
287322
return fullKey.toString();
288323
}
@@ -302,10 +337,7 @@ private String trimKey(String indentKey) {
302337
}
303338

304339
private String unquoteKey(String value) {
305-
if (value.startsWith("'") && value.endsWith("'")) {
306-
return value.substring(1, value.length() - 1);
307-
}
308-
if (value.startsWith("\"") && value.endsWith("\"")) {
340+
if (value.startsWith("'") && value.endsWith("'") || value.startsWith("\"") && value.endsWith("\"")) {
309341
return value.substring(1, value.length() - 1);
310342
}
311343
return value;

avaje-config/src/main/java/io/avaje/config/YamlLoaderSnake.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.InputStream;
44
import java.io.Reader;
5+
import java.util.Collection;
56
import java.util.LinkedHashMap;
67
import java.util.Map;
78

@@ -70,6 +71,8 @@ private void addScalar(String key, Object val) {
7071
add(key, (String) val);
7172
} else if (val instanceof Number || val instanceof Boolean) {
7273
add(key, val.toString());
74+
} else if (val instanceof Collection<?>) {
75+
add(key, String.join(",", (Iterable<String>) val));
7376
}
7477
}
7578

avaje-config/src/test/java/io/avaje/config/ConfigTest.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.avaje.config;
22

3-
import org.junit.jupiter.api.Disabled;
4-
import org.junit.jupiter.api.Test;
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
55

66
import java.net.URI;
77
import java.time.Duration;
@@ -11,8 +11,8 @@
1111
import java.util.Properties;
1212
import java.util.concurrent.atomic.AtomicReference;
1313

14-
import static org.assertj.core.api.Assertions.assertThat;
15-
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
import org.junit.jupiter.api.Disabled;
15+
import org.junit.jupiter.api.Test;
1616

1717
class ConfigTest {
1818

@@ -48,6 +48,11 @@ void getNullable() {
4848
assertThat(entry).isEmpty();
4949
}
5050

51+
@Test
52+
void getSnakeList() {
53+
assertThat(Config.list().of("metal.gear")).contains("snake?", "Snake!?", "SNAAAAKE!!");
54+
}
55+
5156
@Test
5257
void getNullableExists() {
5358
assertThat(Config.getNullable("IDoNotExist1", "SomeVal")).isEqualTo("SomeVal");
@@ -104,7 +109,7 @@ void eventBuilderPublish() {
104109
void onChangeEventListener() {
105110
assertThat(Config.getOptional("MySystemProp5")).isEmpty();
106111
AtomicReference<ModificationEvent> capturedEvent = new AtomicReference<>();
107-
Config.onChange((capturedEvent::set));
112+
Config.onChange(capturedEvent::set);
108113
Config.setProperty("MySystemProp5", "hi5");
109114

110115
ModificationEvent event = capturedEvent.get();
@@ -141,7 +146,7 @@ void asProperties() {
141146
assertThat(System.getProperty("myapp.bar.barDouble")).isEqualTo("33.3");
142147

143148
assertThat(properties).containsKeys("config.load.systemProperties", "config.watch.enabled", "myExternalLoader", "myapp.activateFoo", "myapp.bar.barDouble", "myapp.bar.barRules", "myapp.fooHome", "myapp.fooName", "system.excluded.properties");
144-
assertThat(properties).hasSize(12);
149+
assertThat(properties).hasSize(13);
145150
}
146151

147152
@Test

avaje-config/src/test/java/io/avaje/config/YamlParserTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class YamlParserTest {
1616
void simpleYamlParserMultiLoad() {
1717
YamlLoaderSimple parser = new YamlLoaderSimple();
1818
Map<String, String> load1 = parser.load(res("/yaml/basic.yaml"));
19-
assertThat(load1).hasSize(5);
19+
assertThat(load1).hasSize(6);
2020
basic(load1);
2121

2222
Map<String, String> load2 = parser.load(res("/yaml/key-comment.yaml"));
@@ -31,11 +31,19 @@ void basic() {
3131
}
3232

3333
void basic(final Map<String, String> map) {
34-
assertThat(map).containsOnlyKeys("name", "properties.key1", "properties.key2", "sorted.1", "sorted.2");
34+
assertThat(map)
35+
.containsOnlyKeys(
36+
"name",
37+
"properties.key1",
38+
"properties.key2",
39+
"sorted.1",
40+
"sorted.2",
41+
"root.directories");
3542
assertThat(map.get("name")).isEqualTo("Name123");
3643
assertThat(map.get("properties.key1")).isEqualTo("value1");
3744
assertThat(map.get("properties.key2")).isEqualTo("value2");
3845
assertThat(map.get("sorted.1")).isEqualTo("one");
46+
assertThat(map.get("root.directories")).isEqualTo("/one/two/three,/four/five/six");
3947
}
4048

4149
@Test

avaje-config/src/test/resources/application-test.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ myapp:
1010
config.load.systemProperties: true
1111
system.excluded.properties: myapp.bar.barRules,myapp.fooName
1212

13+
metal:
14+
gear:
15+
- snake?
16+
- Snake!?
17+
- SNAAAAKE!!

avaje-config/src/test/resources/yaml/basic.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ properties:
44
key1: value1
55
sorted:
66
'1': one
7-
'2': two
7+
'2': two
8+
root:
9+
directories:
10+
- /one/two/three
11+
- /four/five/six

0 commit comments

Comments
 (0)