-
Notifications
You must be signed in to change notification settings - Fork 23
Native Support for Adventure Library #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
a16c29a
5943851
857be59
dbb4807
0b47e1e
9683ceb
4081dcf
50a1adf
e6dd5bd
1f81d39
2e6490a
60883de
c5ff729
ddf147e
5347252
6add010
35c6c5f
0350ec4
e016dad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| plugins { | ||
| `core-config` | ||
| `libs-config` | ||
| } | ||
|
|
||
| val adventureVersion = "4.26.1" | ||
|
|
||
| dependencies { | ||
| api(project(":configlib-core")) | ||
| compileOnly("net.kyori:adventure-api:$adventureVersion") | ||
| compileOnly("net.kyori:adventure-text-minimessage:$adventureVersion") | ||
| compileOnly("net.kyori:adventure-text-serializer-legacy:$adventureVersion") | ||
| compileOnly("net.kyori:adventure-text-serializer-gson:$adventureVersion") | ||
| compileOnly("net.kyori:adventure-text-serializer-plain:${adventureVersion}") | ||
|
|
||
| testImplementation(project(":configlib-yaml")) | ||
| testImplementation("net.kyori:adventure-api:$adventureVersion") | ||
| testImplementation("net.kyori:adventure-text-minimessage:$adventureVersion") | ||
| testImplementation("net.kyori:adventure-text-serializer-legacy:$adventureVersion") | ||
| testImplementation("net.kyori:adventure-text-serializer-gson:$adventureVersion") | ||
| testImplementation("net.kyori:adventure-text-serializer-plain:${adventureVersion}") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package de.exlll.configlib; | ||
|
|
||
| import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; | ||
|
|
||
| import java.util.function.Predicate; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| /** | ||
| * Represents the different text formats supported for Adventure Component | ||
| * serialization. | ||
| */ | ||
| public enum AdventureComponentFormat { | ||
| /** | ||
| * MiniMessage format with tags like {@code <red>} or {@code <bold>}. | ||
| */ | ||
| MINI_MESSAGE(Patterns.MINI_MESSAGE_PATTERN.asPredicate()), | ||
| /** | ||
| * Translation key format for translatable components. | ||
| */ | ||
| TRANSLATION_KEY(input -> true), // translation keys can be any format | ||
| /** | ||
zvyap marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * Legacy format using ampersand ({@code &}) as the color code prefix. | ||
| */ | ||
| LEGACY_AMPERSAND(input -> | ||
| input.indexOf(LegacyComponentSerializer.AMPERSAND_CHAR) != -1), | ||
| /** | ||
| * Legacy format using section symbol ({@code §}) as the color code prefix. | ||
| */ | ||
| LEGACY_SECTION(input -> | ||
| input.indexOf(LegacyComponentSerializer.SECTION_CHAR) != -1), | ||
| /** | ||
| * Minecraft JSON format for components. | ||
| */ | ||
| MINECRAFT_JSON(input -> { | ||
| input = input.trim(); | ||
| return input.startsWith("{") && input.endsWith("}"); | ||
| }); | ||
|
|
||
| // Hack to avoid compiler error while singleton pattern initialization | ||
| private static class Patterns { | ||
| // Pattern to detect any <tag> in a string | ||
| static final Pattern MINI_MESSAGE_PATTERN = | ||
| Pattern.compile("<[a-zA-Z0-9_:-]+(?::[^<>]+)?>"); | ||
| } | ||
|
|
||
| private final Predicate<String> inputPredicate; | ||
|
|
||
| AdventureComponentFormat(Predicate<String> inputPredicate) { | ||
| this.inputPredicate = inputPredicate; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if the given input string matches this format. | ||
| * | ||
| * @param input the input string to check | ||
| * @return true if the input matches this format, false otherwise | ||
| */ | ||
| public boolean matches(String input) { | ||
| if (input == null) { | ||
| return false; | ||
| } | ||
| return inputPredicate.test(input); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| package de.exlll.configlib; | ||
|
|
||
| import net.kyori.adventure.text.Component; | ||
| import net.kyori.adventure.text.TranslatableComponent; | ||
| import net.kyori.adventure.text.minimessage.MiniMessage; | ||
| import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; | ||
| import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; | ||
| import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| /** | ||
| * Serializer for Adventure {@link Component} objects. | ||
| * Supports multiple formats including MiniMessage, legacy, and JSON. | ||
| */ | ||
| public final class AdventureComponentSerializer implements Serializer<Component, String> { | ||
| private final List<AdventureComponentFormat> serializeOrder; | ||
| private final List<AdventureComponentFormat> deserializeOrder; | ||
|
|
||
| /** | ||
| * Creates a new ComponentSerializer with separate format orders for | ||
| * serialization and deserialization. | ||
| * | ||
| * @param serializeOrder the order of formats to try when serializing | ||
| * @param deserializeOrder the order of formats to try when deserializing | ||
| */ | ||
| public AdventureComponentSerializer(List<AdventureComponentFormat> serializeOrder, | ||
| List<AdventureComponentFormat> deserializeOrder) { | ||
| this.serializeOrder = List.copyOf(serializeOrder); | ||
| this.deserializeOrder = List.copyOf(deserializeOrder); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new ComponentSerializer using the same format order for | ||
| * both serialization and deserialization. | ||
| * | ||
| * @param formats the formats to use, in order of preference | ||
| */ | ||
| public AdventureComponentSerializer(AdventureComponentFormat... formats) { | ||
| this(Arrays.asList(formats), Arrays.asList(formats)); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should make a defensive copy here, too. Calling
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I already called List.copyOf in another constructor. This constructor just converts the array to a List to pass it to the other constructor. or I miss understanding here? |
||
| } | ||
|
|
||
| @Override | ||
| public String serialize(Component element) { | ||
| if (element == null) { | ||
| return null; | ||
| } | ||
|
|
||
| for (AdventureComponentFormat format : serializeOrder) { | ||
| return serialize(element, format); | ||
| } | ||
|
|
||
| // Fallback to MiniMessage | ||
| return MiniMessage.miniMessage().serialize(element); | ||
| } | ||
|
|
||
| @Override | ||
| public Component deserialize(String element) { | ||
| if (element == null) { | ||
| return null; | ||
| } | ||
|
|
||
| for (AdventureComponentFormat format : deserializeOrder) { | ||
| if (!format.matches(element)) { | ||
| continue; | ||
| } | ||
|
|
||
| return deserialize(element, format); | ||
| } | ||
|
|
||
| // Fallback to MiniMessage | ||
| return MiniMessage.miniMessage().deserialize(element); | ||
| } | ||
|
|
||
| private String serialize(Component component, AdventureComponentFormat format) { | ||
| return switch (format) { | ||
| case MINI_MESSAGE -> MiniMessage.miniMessage().serialize(component); | ||
| case LEGACY_AMPERSAND -> | ||
| LegacyComponentSerializer.legacyAmpersand().serialize(component); | ||
| case LEGACY_SECTION -> | ||
| LegacyComponentSerializer.legacySection().serialize(component); | ||
| case MINECRAFT_JSON -> GsonComponentSerializer.gson().serialize(component); | ||
| case TRANSLATION_KEY -> | ||
| component instanceof TranslatableComponent translatableComponent | ||
| ? translatableComponent.key() | ||
| : PlainTextComponentSerializer.plainText().serialize(component); | ||
| }; | ||
zvyap marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private Component deserialize(String string, AdventureComponentFormat format) { | ||
| return switch (format) { | ||
| case MINI_MESSAGE -> | ||
| MiniMessage.miniMessage().deserialize(string); | ||
| case LEGACY_AMPERSAND -> | ||
| LegacyComponentSerializer.legacyAmpersand().deserialize(string); | ||
| case LEGACY_SECTION -> | ||
| LegacyComponentSerializer.legacySection().deserialize(string); | ||
| case MINECRAFT_JSON -> | ||
| GsonComponentSerializer.gson().deserialize(string); | ||
| case TRANSLATION_KEY -> | ||
| Component.translatable(string); | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package de.exlll.configlib; | ||
|
|
||
| import net.kyori.adventure.key.Key; | ||
| import net.kyori.adventure.sound.Sound; | ||
| import net.kyori.adventure.text.Component; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| /** | ||
| * Utility class providing default serializers for Adventure library types. | ||
| */ | ||
| public final class AdventureConfigLib { | ||
| // Use MiniMessage as the default format since MiniMessage covered all | ||
| // component type | ||
| private static final List<AdventureComponentFormat> DEFAULT_FORMAT_ORDER = List.of( | ||
| AdventureComponentFormat.MINI_MESSAGE | ||
| ); | ||
|
|
||
| private AdventureConfigLib() { | ||
| } | ||
|
|
||
| /** | ||
| * Adds default Adventure serializers to the configuration builder. | ||
| * | ||
| * @param builder the configuration properties builder | ||
| * @param <B> the builder type | ||
| * @return the builder with default serializers added | ||
| */ | ||
| public static <B extends ConfigurationProperties.Builder<B>> | ||
| ConfigurationProperties.Builder<B> addDefaults( | ||
| ConfigurationProperties.Builder<B> builder) { | ||
| return addDefaults(builder, DEFAULT_FORMAT_ORDER, DEFAULT_FORMAT_ORDER); | ||
| } | ||
zvyap marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Adds default Adventure serializers to the configuration builder with custom | ||
| * format orders. | ||
| * | ||
| * @param builder the configuration properties builder | ||
| * @param serializeOrder the order of formats to try when serializing | ||
| * components | ||
| * @param deserializeOrder the order of formats to try when deserializing | ||
| * components | ||
| * @param <B> the builder type | ||
| * @return the builder with default serializers added | ||
| */ | ||
| public static <B extends ConfigurationProperties.Builder<B>> | ||
| ConfigurationProperties.Builder<B> addDefaults( | ||
| ConfigurationProperties.Builder<B> builder, | ||
| List<AdventureComponentFormat> serializeOrder, | ||
| List<AdventureComponentFormat> deserializeOrder) { | ||
| builder.addSerializer(Component.class, | ||
| new AdventureComponentSerializer(serializeOrder, deserializeOrder)); | ||
| builder.addSerializer(Key.class, new AdventureKeySerializer()); | ||
| builder.addSerializer(Sound.class, new AdventureSoundSerializer()); | ||
| return builder; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package de.exlll.configlib; | ||
|
|
||
| import net.kyori.adventure.key.Key; | ||
|
|
||
| import java.util.OptionalInt; | ||
|
|
||
| /** | ||
| * Serializer for Adventure {@link Key} objects. | ||
| */ | ||
| public final class AdventureKeySerializer implements Serializer<Key, String> { | ||
|
|
||
| private final String defaultNamespace; | ||
|
|
||
| /** | ||
| * Creates a new KeySerializer with the specified default namespace. | ||
| * | ||
| * @param defaultNamespace the default namespace to use when deserializing keys | ||
| * without a namespace | ||
| * @throws IllegalArgumentException if the namespace is invalid | ||
| */ | ||
| public AdventureKeySerializer(String defaultNamespace) { | ||
| this.defaultNamespace = defaultNamespace; | ||
| OptionalInt result = Key.checkNamespace(defaultNamespace); | ||
| if (result.isPresent()) { | ||
| throw new IllegalArgumentException( | ||
| "Invalid namespace at index " + result.getAsInt() + ": " | ||
| + defaultNamespace); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new KeySerializer using Adventure's default namespace (minecraft). | ||
| */ | ||
| public AdventureKeySerializer() { | ||
| this.defaultNamespace = null; // Use Adventure's default namespace | ||
| } | ||
|
|
||
| @Override | ||
| public String serialize(Key element) { | ||
| return element.asString(); | ||
| } | ||
|
|
||
| @Override | ||
| public Key deserialize(String element) { | ||
| if (this.defaultNamespace == null) { | ||
| return Key.key(element); | ||
| } | ||
|
|
||
zvyap marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return Key.key(this.defaultNamespace, element); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.