Skip to content

Commit

Permalink
config-api
Browse files Browse the repository at this point in the history
  • Loading branch information
TexBlock committed Aug 17, 2024
1 parent 3afbc8e commit 65bc1f9
Show file tree
Hide file tree
Showing 21 changed files with 1,169 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@


#### [HOW TO ADD MODULE](./ADD-MODULE.md)


## Another
- Config API part based [`quilt-config`](https://github.com/QuiltMC/quilt-config)
4 changes: 2 additions & 2 deletions command-neo/src/main/resources/META-INF/neoforge.mods.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ logoFile = "icon.png"
authors = "Kessoku Tea Time"
displayURL = "https://modrinth.com/mod/kessoku-lib"

[[dependencies.kessoku-event-base]]
[[dependencies.kessoku_command]]
modId = "neoforge"
type = "required"
versionRange = "[21.0,)"
ordering = "NONE"
side = "BOTH"

[[dependencies.kessoku-event-base]]
[[dependencies.kessoku_command]]
modId = "minecraft"
type = "required"
versionRange = "[1.21,)"
Expand Down
16 changes: 16 additions & 0 deletions config-common/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apply from: rootProject.file("gradle/scripts/kessokulib-common.gradle")

group = "band.kessoku.lib.config"
base.archivesName = rootProject.name + "-config"

repositories {
maven { url = "https://maven.quiltmc.org/repository/release/" }
}

dependencies {
moduleImplementation(project(":base-common"))
moduleImplementation(project(":platform-common"))

implementation(libs.bundles.night.config)
implementation(libs.bundles.quilt.config)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package band.kessoku.lib.config;

import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class KessokuConfig {
public static final String MOD_ID = "kessoku_config";
public static final Marker MARKER = MarkerFactory.getMarker("[KessokuConfig]");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package band.kessoku.lib.config.api;

import band.kessoku.lib.config.impl.ModConfigHelperImpl;
import org.quiltmc.config.api.Config;
import org.quiltmc.config.api.ReflectiveConfig;
import org.quiltmc.config.impl.ConfigImpl;
import org.quiltmc.config.implementor_api.ConfigFactory;

import java.nio.file.Path;
import java.nio.file.Paths;

public final class ModConfigHelper {
/**
* Creates and registers a config file
*
* @param family the mod owning the resulting config file
* @param id the configs id
* @param path additional path elements to include as part of this configs file, e.g.
* if the path is empty, the config file might be ".minecraft/config/example_mod/id.toml"
* if the path is "client/gui", the config file might be ".minecraft/config/example_mod/client/gui/id.toml"
* @param creators any number of {@link Config.Creator}s that can be used to configure the resulting config
*/
public static Config create(String family, String id, Path path, Config.Creator... creators) {
return ConfigImpl.create(ModConfigHelperImpl.getConfigEnvironment(), family, id, path, creators);
}

/**
* Creates and registers a config file
*
* @param family the mod owning the resulting config file
* @param id the configs id
* @param creators any number of {@link Config.Creator}s that can be used to configure the resulting config
*/
public static Config create(String family, String id, Config.Creator... creators) {
return create(family, id, Paths.get(""), creators);
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the config's id
* @param path additional path elements to include as part of this configs file, e.g.
* if the path is empty, the config file might be ".minecraft/config/example_mod/id.toml"
* if the path is "client/gui", the config file might be ".minecraft/config/example_mod/client/gui/id.toml"
* @param before a {@link Config.Creator} that can be used to configure the resulting config further
* @param configCreatorClass a class as described above
* @param after a {@link Config.Creator} that can be used to configure the resulting config further
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Path path, Config.Creator before, Class<C> configCreatorClass, Config.Creator after) {
return ConfigFactory.create(ModConfigHelperImpl.getConfigEnvironment(), family, id, path, before, configCreatorClass, after);
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the config's id
* @param path additional path elements to include as part of this configs file, e.g.
* if the path is empty, the config file might be ".minecraft/config/example_mod/id.toml"
* if the path is "client/gui", the config file might be ".minecraft/config/example_mod/client/gui/id.toml"
* @param before a {@link Config.Creator} that can be used to configure the resulting config further
* @param configCreatorClass a class as described above
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Path path, Config.Creator before, Class<C> configCreatorClass) {
return create(family, id, path, before, configCreatorClass, builder -> {});
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the configs id
* @param path additional path elements to include as part of this configs file, e.g.
* if the path is empty, the config file might be ".minecraft/config/example_mod/id.toml"
* if the path is "client/gui", the config file might be ".minecraft/config/example_mod/client/gui/id.toml"
* @param configCreatorClass a class as described above
* @param after a {@link Config.Creator} that can be used to configure the resulting config further
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Path path, Class<C> configCreatorClass, Config.Creator after) {
return create(family, id, path, builder -> {}, configCreatorClass, after);
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the config's id
* @param path additional path elements to include as part of this configs file, e.g.
* if the path is empty, the config file might be ".minecraft/config/example_mod/id.toml"
* if the path is "client/gui", the config file might be ".minecraft/config/example_mod/client/gui/id.toml"
* @param configCreatorClass a class as described above
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Path path, Class<C> configCreatorClass) {
return create(family, id, path, builder -> {}, configCreatorClass, builder -> {});
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the config's id
* @param before a {@link Config.Creator} that can be used to configure the resulting config further
* @param configCreatorClass a class as described above
* @param after a {@link Config.Creator} that can be used to configure the resulting config further
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Config.Creator before, Class<C> configCreatorClass, Config.Creator after) {
return create(family, id, Paths.get(""), before, configCreatorClass, after);
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the config's id
* @param before a {@link Config.Creator} that can be used to configure the resulting config further
* @param configCreatorClass a class as described above
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Config.Creator before, Class<C> configCreatorClass) {
return create(family, id, Paths.get(""), before, configCreatorClass, builder -> {});
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the config's id
* @param configCreatorClass a class as described above
* @param after a {@link Config.Creator} that can be used to configure the resulting config further
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Class<C> configCreatorClass, Config.Creator after) {
return create(family, id, Paths.get(""), builder -> {}, configCreatorClass, after);
}

/**
* Creates and registers a config with a class that contains its WrappedValues as fields.
*
* @param family the mod owning the resulting config file
* @param id the config's id
* @param configCreatorClass a class as described above
* @return a {@link ReflectiveConfig <C>}
*/
public static <C extends ReflectiveConfig> C create(String family, String id, Class<C> configCreatorClass) {
return create(family, id, Paths.get(""), builder -> {}, configCreatorClass, builder -> {});
}

private ModConfigHelper() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package band.kessoku.lib.config.api.serializers.night;

import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
import com.electronwill.nightconfig.core.UnmodifiableCommentedConfig;
import com.electronwill.nightconfig.core.io.ConfigParser;
import com.electronwill.nightconfig.core.io.ConfigWriter;
import com.electronwill.nightconfig.hocon.HoconParser;
import com.electronwill.nightconfig.hocon.HoconWriter;
import org.quiltmc.config.api.Config;
import org.quiltmc.config.api.Constraint;
import org.quiltmc.config.api.MarshallingUtils;
import org.quiltmc.config.api.Serializer;
import org.quiltmc.config.api.annotations.Comment;
import org.quiltmc.config.api.values.*;
import org.quiltmc.config.impl.util.SerializerUtils;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

/**
* A default serializer that writes in the <a href="https://github.com/lightbend/config/blob/master/HOCON.md">HOCON format</a>.
*
* @implNote When passing entries to {@link com.electronwill.nightconfig.core.Config#add(String, Object)}, the string key is automatically split at each dot ({@code .}).
* This completely breaks TOML serialization, since we allow dots in keys, using either {@link org.quiltmc.config.api.annotations.SerializedName} or {@link ValueMap}, whose keys are not validated for certain characters.
* To get around this, use {@link com.electronwill.nightconfig.core.Config#add(List, Object)} via passing your key into {@link #toNightConfigSerializable(ValueKey)}.
*/
public final class HoconSerializer implements Serializer {
public static final HoconSerializer INSTANCE = new HoconSerializer();
private final ConfigParser<CommentedConfig> parser = new HoconParser();
private final ConfigWriter writer = new HoconWriter();

private HoconSerializer() {

}

@Override
public String getFileExtension() {
return "hocon";
}

@Override
public void serialize(Config config, OutputStream to) {
this.writer.write(write(config, createCommentedConfig(), config.nodes()), to);
}

@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public void deserialize(Config config, InputStream from) {
CommentedConfig read = this.parser.parse(from);

for (TrackedValue<?> trackedValue : config.values()) {
List<ValueKey> keyOptions = SerializerUtils.getPossibleKeys(config, trackedValue);

for (ValueKey key : keyOptions) {
String stringKey = key.toString();

if (read.contains(stringKey)) {
((TrackedValue) trackedValue).setValue(MarshallingUtils.coerce(read.get(stringKey), trackedValue.getDefaultValue(), (CommentedConfig c, MarshallingUtils.MapEntryConsumer entryConsumer) ->
c.entrySet().forEach(e -> entryConsumer.put(e.getKey(), e.getValue()))), false);
}
}
}
}

private static List<Object> convertList(List<?> list) {
List<Object> result = new ArrayList<>(list.size());

for (Object value : list) {
result.add(convertAny(value));
}

return result;
}

private static UnmodifiableCommentedConfig convertMap(ValueMap<?> map) {
CommentedConfig result = createCommentedConfig();

for (Map.Entry<String, ?> entry : map.entrySet()) {
List<String> key = new ArrayList<>();
key.add(entry.getKey());
result.add(key, convertAny(entry.getValue()));
}

return result;
}

private static Object convertAny(Object value) {
if (value instanceof ValueMap) {
return convertMap((ValueMap<?>) value);
} else if (value instanceof ValueList) {
return convertList((ValueList<?>) value);
} else if (value instanceof ConfigSerializableObject) {
return convertAny(((ConfigSerializableObject<?>) value).getRepresentation());
} else {
return value;
}
}

private static CommentedConfig write(Config config, CommentedConfig commentedConfig, Iterable<ValueTreeNode> nodes) {
for (ValueTreeNode node : nodes) {
List<String> comments = new ArrayList<>();

if (node.hasMetadata(Comment.TYPE)) {
for (String string : node.metadata(Comment.TYPE)) {
comments.add(string);
}
}

ValueKey key = SerializerUtils.getSerializedKey(config, node);

if (node instanceof TrackedValue<?>) {
TrackedValue<?> value = (TrackedValue<?>) node;
Object defaultValue = value.getDefaultValue();

SerializerUtils.createEnumOptionsComment(defaultValue).ifPresent(comments::add);

for (Constraint<?> constraint : value.constraints()) {
comments.add(constraint.getRepresentation());
}

Optional<String> defaultValueComment = SerializerUtils.getDefaultValueString(defaultValue);
defaultValueComment.ifPresent(s -> comments.add("default: " + s));

commentedConfig.add(toNightConfigSerializable(key), convertAny(value.getRealValue()));
} else {
write(config, commentedConfig, ((ValueTreeNode.Section) node));
}

if (!comments.isEmpty()) {
commentedConfig.setComment(toNightConfigSerializable(key), " " + String.join("\n ", comments));
}
}

return commentedConfig;
}

private static CommentedConfig createCommentedConfig() {
return InMemoryCommentedFormat.defaultInstance().createConfig(LinkedHashMap::new);
}

private static List<String> toNightConfigSerializable(ValueKey key) {
List<String> listKey = new ArrayList<>();
key.forEach(listKey::add);
return listKey;
}
}
Loading

0 comments on commit 65bc1f9

Please sign in to comment.