Skip to content

Commit 8cbe035

Browse files
committed
Initial commit.
0 parents  commit 8cbe035

File tree

4 files changed

+280
-0
lines changed

4 files changed

+280
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target/
2+
/.classpath
3+
/.project

.settings/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/org.eclipse.jdt.core.prefs
2+
/org.eclipse.m2e.core.prefs

pom.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<modelVersion>4.0.0</modelVersion>
3+
<groupId>CommentedConfiguration</groupId>
4+
<artifactId>CommentedConfiguration</artifactId>
5+
<version>1.0.0</version>
6+
7+
<repositories>
8+
<repository>
9+
<id>spigot-repo</id>
10+
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
11+
</repository>
12+
</repositories>
13+
<dependencies>
14+
<dependency>
15+
<groupId>org.spigotmc</groupId>
16+
<artifactId>spigot-api</artifactId>
17+
<version>1.18.1-R0.1-SNAPSHOT</version>
18+
<scope>provided</scope>
19+
</dependency>
20+
</dependencies>
21+
</project>
22+
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
package io.github.townyadvanced;
2+
3+
import java.io.IOException;
4+
import java.nio.charset.StandardCharsets;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.nio.file.StandardOpenOption;
8+
import java.util.ArrayList;
9+
import java.util.HashMap;
10+
import java.util.List;
11+
12+
import org.bukkit.configuration.InvalidConfigurationException;
13+
import org.bukkit.configuration.file.YamlConfiguration;
14+
import org.bukkit.configuration.file.YamlConstructor;
15+
import org.bukkit.configuration.file.YamlRepresenter;
16+
import org.bukkit.plugin.Plugin;
17+
import org.yaml.snakeyaml.DumperOptions;
18+
import org.yaml.snakeyaml.Yaml;
19+
import org.yaml.snakeyaml.representer.Representer;
20+
21+
/**
22+
* @author dumptruckman
23+
* @author Articdive
24+
* @author LlmDl
25+
*/
26+
public class CommentedConfiguration extends YamlConfiguration {
27+
private final HashMap<String, String> comments = new HashMap<>();
28+
private final Path path;
29+
private final Plugin plugin;
30+
31+
private final DumperOptions yamlOptions = new DumperOptions();
32+
private final Representer yamlRepresenter = new YamlRepresenter();
33+
private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions);
34+
35+
public CommentedConfiguration(Plugin plugin, Path path) {
36+
super();
37+
this.plugin = plugin;
38+
this.path = path;
39+
40+
try {
41+
// Spigot 1.18.1 added SnakeYaml's ability to use Comments in yaml.
42+
// They have it enabled by default, we need to stop it happening.
43+
yamlOptions.setProcessComments(false);
44+
} catch (NoSuchMethodError ignored) {}
45+
}
46+
47+
/**
48+
* Load the yaml configuration file into memory.
49+
* @return true if file is able to load.
50+
*/
51+
public boolean load() {
52+
return loadFile();
53+
}
54+
55+
private boolean loadFile() {
56+
try {
57+
this.load(path.toFile());
58+
return true;
59+
} catch (InvalidConfigurationException | IOException e) {
60+
plugin.getLogger().warning(String.format("Loading error: Failed to load file %s (does it pass a yaml parser?).", path));
61+
plugin.getLogger().warning("https://jsonformatter.org/yaml-parser");
62+
plugin.getLogger().warning(e.getMessage());
63+
return false;
64+
}
65+
}
66+
67+
/**
68+
* Save the yaml configuration file from memory to file.
69+
*/
70+
public void save() {
71+
72+
// Save the config just like normal
73+
boolean saved = saveFile();
74+
75+
// if there's comments to add and it saved fine, we need to add comments
76+
if (!comments.isEmpty() && saved) {
77+
78+
// String list of each line in the config file
79+
List<String> yamlContents;
80+
try {
81+
yamlContents = Files.readAllLines(path, StandardCharsets.UTF_8);
82+
} catch (IOException e) {
83+
plugin.getLogger().warning(String.format("Failed to read file %s.", path));
84+
plugin.getLogger().warning(e.getMessage());
85+
yamlContents = new ArrayList<>();
86+
}
87+
88+
// This will hold the newly formatted line
89+
StringBuilder newContents = readConfigToString(yamlContents);
90+
91+
// Write to file
92+
try {
93+
Files.write(path, newContents.toString().getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
94+
} catch (IOException e) {
95+
plugin.getLogger().warning(String.format("Saving error: Failed to write to file %s.", path));
96+
plugin.getLogger().warning(e.getMessage());
97+
}
98+
}
99+
}
100+
101+
private boolean saveFile() {
102+
try {
103+
this.save(path.toFile());
104+
return true;
105+
} catch (Exception e) {
106+
return false;
107+
}
108+
}
109+
110+
private StringBuilder readConfigToString(List<String> yamlContents) {
111+
// This will hold the newly formatted line
112+
StringBuilder newContents = new StringBuilder();
113+
// This holds the current path the lines are at in the config
114+
String currentPath = "";
115+
// This flags if the line is a node or unknown text.
116+
boolean node;
117+
// The depth of the path. (number of words separated by periods - 1)
118+
int depth = 0;
119+
120+
// Loop through the config lines
121+
for (String line : yamlContents) {
122+
// If the line is a node (and not something like a list value)
123+
if (line.contains(": ") || (line.length() > 1 && line.charAt(line.length() - 1) == ':')) {
124+
125+
// This is a node so flag it as one
126+
node = true;
127+
128+
// Grab the index of the end of the node name
129+
int index;
130+
index = line.indexOf(": ");
131+
if (index < 0) {
132+
index = line.length() - 1;
133+
}
134+
// If currentPath is empty, store the node name as the currentPath.
135+
if (currentPath.isEmpty()) {
136+
currentPath = line.substring(0, index);
137+
} else {
138+
// Calculate the whitespace preceding the node name
139+
int whiteSpace = 0;
140+
for (int n = 0; n < line.length(); n++) {
141+
if (line.charAt(n) == ' ') {
142+
whiteSpace++;
143+
} else {
144+
break;
145+
}
146+
}
147+
// Find out if the current depth (whitespace * 2) is greater/lesser/equal to the previous depth
148+
if (whiteSpace / 2 > depth) {
149+
// Path is deeper. Add a . and the node name
150+
currentPath += "." + line.substring(whiteSpace, index);
151+
depth++;
152+
} else if (whiteSpace / 2 < depth) {
153+
// Path is shallower, calculate current depth from whitespace (whitespace / 2) and subtract that many levels from the currentPath
154+
int newDepth = whiteSpace / 2;
155+
for (int i = 0; i < depth - newDepth; i++) {
156+
currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
157+
}
158+
// Grab the index of the final period
159+
int lastIndex = currentPath.lastIndexOf(".");
160+
if (lastIndex < 0) {
161+
// if there isn't a final period, set the current path to nothing because we're at root
162+
currentPath = "";
163+
} else {
164+
// If there is a final period, replace everything after it with nothing
165+
currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
166+
currentPath += ".";
167+
}
168+
// Add the new node name to the path
169+
currentPath += line.substring(whiteSpace, index);
170+
// Reset the depth
171+
depth = newDepth;
172+
} else {
173+
// Path is same depth, replace the last path node name to the current node name
174+
int lastIndex = currentPath.lastIndexOf(".");
175+
if (lastIndex < 0) {
176+
// if there isn't a final period, set the current path to nothing because we're at root
177+
currentPath = "";
178+
} else {
179+
// If there is a final period, replace everything after it with nothing
180+
currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
181+
currentPath += ".";
182+
}
183+
currentPath += line.substring(whiteSpace, index);
184+
}
185+
}
186+
} else {
187+
node = false;
188+
}
189+
190+
if (node) {
191+
// If there's a comment for the current path, retrieve it and flag that path as already commented
192+
String comment = comments.get(currentPath);
193+
194+
if (comment != null) {
195+
// Add the comment to the beginning of the current line
196+
line = comment + System.getProperty("line.separator") + line;
197+
}
198+
}
199+
// Add the line to the total config String
200+
newContents.append(line).append(System.getProperty("line.separator"));
201+
}
202+
203+
/*
204+
* Due to a Bukkit Bug with the Configuration we just need to remove any extra
205+
* comments at the start of a file.
206+
*/
207+
while (newContents.toString().startsWith(" " + System.getProperty("line.separator")))
208+
newContents = new StringBuilder(newContents.toString().replaceFirst(" " + System.getProperty("line.separator"), ""));
209+
210+
return newContents;
211+
}
212+
213+
/**
214+
* Adds a comment just before the specified path. The comment can be
215+
* multiple lines. An empty string will indicate a blank line.
216+
*
217+
* @param path Configuration path to add comment.
218+
* @param commentLines Comments to add. One String per line.
219+
*/
220+
public void addComment(String path, String... commentLines) {
221+
222+
StringBuilder commentstring = new StringBuilder();
223+
StringBuilder leadingSpaces = new StringBuilder();
224+
for (int n = 0; n < path.length(); n++) {
225+
if (path.charAt(n) == '.') {
226+
leadingSpaces.append(" ");
227+
}
228+
}
229+
for (String line : commentLines) {
230+
if (!line.isEmpty()) {
231+
line = leadingSpaces + line;
232+
} else {
233+
line = " ";
234+
}
235+
if (commentstring.length() > 0) {
236+
commentstring.append(System.getProperty("line.separator"));
237+
}
238+
commentstring.append(line);
239+
}
240+
comments.put(path, commentstring.toString());
241+
}
242+
243+
@Override
244+
public String saveToString() {
245+
yamlOptions.setIndent(options().indent());
246+
yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
247+
yamlOptions.setWidth(10000);
248+
yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
249+
250+
String dump = yaml.dump(getValues(false));
251+
return dump.equals("{}\n") ? "" : dump;
252+
}
253+
}

0 commit comments

Comments
 (0)