1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-30 00:57:55 +00:00

Try to unify our config files a bit

I've tried so many rewrites of the config system over the last few
months, in an attempt to get started on #1727. All of them stink, so
this is an attempt to apply some of the cleanup.

 - Move some of the common logic into ConfigFile. This means we now
   store more information ourselves for Forge, rather than reading it
   out of the ForgeConfigSpec.

 - Don't include the Range/Allowed keys in the translation key. This was
   mostly there because of how we read comments from Forge, but it never
   made much sense.

 - Remove our separate Trie structure, and just encode the tree as part
   of the children of a Group.
This commit is contained in:
Jonathan Coates
2024-11-24 21:53:04 +00:00
parent 1963e0160f
commit ea670cc358
6 changed files with 217 additions and 301 deletions

View File

@@ -11,7 +11,6 @@ import com.electronwill.nightconfig.core.file.FileWatcher;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.util.Trie;
import org.apache.commons.lang3.function.TriFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,6 +21,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -29,18 +29,17 @@ import java.util.stream.Stream;
/**
* A {@link ConfigFile} which sits directly on top of NightConfig.
*/
public class FabricConfigFile implements ConfigFile {
public final class FabricConfigFile extends ConfigFile {
private static final Logger LOG = LoggerFactory.getLogger(FabricConfigFile.class);
private final ConfigSpec spec;
private final Trie<String, Entry> entries;
private final ConfigListener onChange;
private @Nullable CommentedFileConfig config;
public FabricConfigFile(ConfigSpec spec, Trie<String, Entry> entries, ConfigListener onChange) {
private FabricConfigFile(ConfigSpec spec, Map<String, Entry> entries, ConfigListener onChange) {
super(entries);
this.spec = spec;
this.entries = entries;
this.onChange = onChange;
}
@@ -64,7 +63,7 @@ public class FabricConfigFile implements ConfigFile {
@SuppressWarnings("unchecked")
private Stream<ValueImpl<?>> values() {
return (Stream<ValueImpl<?>>) (Stream<?>) entries.stream().filter(ValueImpl.class::isInstance);
return (Stream<ValueImpl<?>>) (Stream<?>) entries().filter(ValueImpl.class::isInstance);
}
public synchronized void unload() {
@@ -92,7 +91,7 @@ public class FabricConfigFile implements ConfigFile {
// Ensure the config file matches the spec
var isNewFile = config.isEmpty();
entries.stream().forEach(x -> config.setComment(x.path, x.comment));
entries().forEach(x -> config.setComment(x.path(), x instanceof ValueImpl<?> v ? v.fullComment : x.comment()));
var corrected = isNewFile ? spec.correct(config) : spec.correct(config, (action, entryPath, oldValue, newValue) -> {
LOG.warn("Incorrect key {} was corrected from {} to {}", String.join(".", entryPath), oldValue, newValue);
});
@@ -104,148 +103,77 @@ public class FabricConfigFile implements ConfigFile {
return corrected > 0;
}
@Override
public Stream<ConfigFile.Entry> entries() {
return entries.stream().map(x -> (ConfigFile.Entry) x);
}
@Nullable
@Override
public ConfigFile.Entry getEntry(String path) {
return (ConfigFile.Entry) entries.getValue(SPLITTER.split(path));
}
static class Builder extends ConfigFile.Builder {
private final ConfigSpec spec = new ConfigSpec();
private final Trie<String, Entry> entries = new Trie<>();
private @Nullable String pendingComment;
private String getFullPath(String path) {
var key = new StringBuilder();
for (var group : groupStack) key.append(group).append('.');
key.append(path);
return key.toString();
}
@Override
public ConfigFile.Builder comment(String comment) {
if (pendingComment != null) throw new IllegalStateException("Already have a comment");
pendingComment = comment;
return this;
}
private String takeComment() {
var comment = pendingComment;
if (comment == null) throw new IllegalStateException("No comment specified");
pendingComment = null;
return comment;
}
private String takeComment(String suffix) {
var comment = pendingComment == null ? "" : pendingComment + "\n";
pendingComment = null;
return comment + suffix;
}
@Override
public void push(String name) {
var path = getFullPath(name);
var splitPath = SPLITTER.split(path);
entries.setValue(splitPath, new GroupImpl(path, takeComment()));
super.push(name);
}
@Override
public ConfigFile.Builder worldRestart() {
return this;
}
private <T> Value<T> defineValue(String fullPath, String comment, T defaultValue, TriFunction<Config, String, T, T> getter) {
var value = new ValueImpl<T>(fullPath, comment, defaultValue, getter);
entries.setValue(SPLITTER.split(fullPath), value);
private <T> Value<T> defineValue(String name, String comment, @Nullable String suffix, T defaultValue, TriFunction<Config, String, T, T> getter) {
var fullComment = suffix == null ? comment : comment + "\n" + suffix;
var value = new ValueImpl<T>(getPath(name), comment, fullComment, defaultValue, getter);
groupStack.getLast().children().put(name, value);
return value;
}
@Override
public <T> Value<T> define(String path, T defaultValue) {
var fullPath = getFullPath(path);
spec.define(fullPath, defaultValue);
return defineValue(fullPath, takeComment(), defaultValue, Config::getOrElse);
public <T> Value<T> define(String name, T defaultValue) {
var path = getPath(name);
spec.define(path, defaultValue);
return defineValue(name, takeComment(), null, defaultValue, Config::getOrElse);
}
@Override
public Value<Boolean> define(String path, boolean defaultValue) {
var fullPath = getFullPath(path);
spec.define(fullPath, defaultValue, x -> x instanceof Boolean);
return defineValue(fullPath, takeComment(), defaultValue, UnmodifiableConfig::getOrElse);
public Value<Boolean> define(String name, boolean defaultValue) {
var path = getPath(name);
spec.define(path, defaultValue, x -> x instanceof Boolean);
return defineValue(name, takeComment(), null, defaultValue, UnmodifiableConfig::getOrElse);
}
@Override
public Value<Integer> defineInRange(String path, int defaultValue, int min, int max) {
var fullPath = getFullPath(path);
spec.defineInRange(fullPath, defaultValue, min, max);
public Value<Integer> defineInRange(String name, int defaultValue, int min, int max) {
var path = getPath(name);
spec.defineInRange(path, defaultValue, min, max);
var suffix = max == Integer.MAX_VALUE ? "Range: > " + min : "Range: " + min + " ~ " + max;
return defineValue(fullPath, takeComment(suffix), defaultValue, UnmodifiableConfig::getIntOrElse);
return defineValue(name, takeComment(), suffix, defaultValue, UnmodifiableConfig::getIntOrElse);
}
@Override
public <T> Value<List<? extends T>> defineList(String path, List<? extends T> defaultValue, Predicate<Object> elementValidator) {
var fullPath = getFullPath(path);
spec.defineList(fullPath, defaultValue, elementValidator);
return defineValue(fullPath, takeComment(), defaultValue, Config::getOrElse);
public <T> Value<List<? extends T>> defineList(String name, List<? extends T> defaultValue, Predicate<Object> elementValidator) {
var path = getPath(name);
spec.defineList(path, defaultValue, elementValidator);
return defineValue(name, takeComment(), null, defaultValue, Config::getOrElse);
}
@Override
public <V extends Enum<V>> Value<V> defineEnum(String path, V defaultValue) {
var fullPath = getFullPath(path);
spec.define(fullPath, defaultValue, o -> o != null && o != NullObject.NULL_OBJECT && EnumGetMethod.NAME_IGNORECASE.validate(o, defaultValue.getDeclaringClass()));
public <V extends Enum<V>> Value<V> defineEnum(String name, V defaultValue) {
var path = getPath(name);
spec.define(path, defaultValue, o -> o != null && o != NullObject.NULL_OBJECT && EnumGetMethod.NAME_IGNORECASE.validate(o, defaultValue.getDeclaringClass()));
var suffix = "Allowed Values: " + Arrays.stream(defaultValue.getDeclaringClass().getEnumConstants()).map(Enum::name).collect(Collectors.joining(", "));
return defineValue(fullPath, takeComment(suffix), defaultValue, (c, p, d) -> c.getEnumOrElse(p, d, EnumGetMethod.NAME_IGNORECASE));
return defineValue(name, takeComment(), suffix, defaultValue, (c, p, d) -> c.getEnumOrElse(p, d, EnumGetMethod.NAME_IGNORECASE));
}
@Override
public ConfigFile build(ConfigListener onChange) {
return new FabricConfigFile(spec, entries, onChange);
var children = groupStack.removeLast().children();
if (!groupStack.isEmpty()) throw new IllegalStateException("Mismatched config push/pop");
return new FabricConfigFile(spec, children, onChange);
}
}
private static class Entry {
final String path;
final String comment;
Entry(String path, String comment) {
this.path = path;
this.comment = comment;
}
@SuppressWarnings("UnusedMethod")
public final String translationKey() {
return TRANSLATION_PREFIX + path;
}
@SuppressWarnings("UnusedMethod")
public final String comment() {
return comment;
}
}
private static final class GroupImpl extends Entry implements Group {
private GroupImpl(String path, String comment) {
super(path, comment);
}
}
private static final class ValueImpl<T> extends Entry implements Value<T> {
private static final class ValueImpl<T> extends Value<T> {
private @Nullable T value;
private final T defaultValue;
private final TriFunction<Config, String, T, T> get;
private final String fullComment;
private ValueImpl(String path, String comment, T defaultValue, TriFunction<Config, String, T, T> get) {
private ValueImpl(String path, String comment, String fullComment, T defaultValue, TriFunction<Config, String, T, T> get) {
super(path, comment);
this.fullComment = fullComment;
this.defaultValue = defaultValue;
this.get = get;
}