diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Formatter.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Formatter.java new file mode 100644 index 000000000..7ea83bf68 --- /dev/null +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Formatter.java @@ -0,0 +1,96 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage.tag.resolver; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.Locale; +import net.kyori.adventure.text.minimessage.tag.Tag; +import org.jetbrains.annotations.NotNull; + +/** + * Tag resolvers producing tags that insert formatted values. + * + *

These are effectively placeholders.

+ * + * @since 4.10.0 + */ +public final class Formatter { + private Formatter() { + } + + /** + * Creates a replacement that inserts a number as a component. The component will be formatted by the provided DecimalFormat. + * + *

This tag expects a format as attribute. Refer to {@link DecimalFormat} for usable patterns.

+ * + *

You can specify the decimal and grouping separator by adding those as another attribute.

+ * + *

This replacement is auto-closing, so its style will not influence the style of following components.

+ * + * @param key the key + * @param number the number + * @return the placeholder + * @since 4.10.0 + */ + public static TagResolver formatNumber(final @NotNull String key, final Number number) { + return TagResolver.resolver(key, (argumentQueue, context) -> { + final String format = argumentQueue.popOr("Format expected").value(); + final DecimalFormat decimalFormat = new DecimalFormat(format); + final DecimalFormatSymbols formatSymbols = new DecimalFormatSymbols(Locale.ROOT); + if (argumentQueue.hasNext()) { + final String symbols = argumentQueue.pop().value(); + if (symbols.length() >= 1) { + formatSymbols.setDecimalSeparator(symbols.charAt(0)); + } + if (symbols.length() >= 2) { + formatSymbols.setGroupingSeparator(symbols.charAt(1)); + } + } + decimalFormat.setDecimalFormatSymbols(formatSymbols); + return Tag.selfClosingInserting(context.deserialize(decimalFormat.format(number))); + }); + } + + /** + * Creates a replacement that inserts a date or a time as a component. The component will be formatted by the provided Date Format. + * + *

This tag expects a format as attribute. Refer to {@link DateTimeFormatter} for usable patterns.

+ * + *

This replacement is auto-closing, so its style will not influence the style of following components.

+ * + * @param key the key + * @param time the time + * @return the placeholder + * @since 4.10.0 + */ + public static TagResolver formatDate(final @NotNull String key, final TemporalAccessor time) { + return TagResolver.resolver(key, (argumentQueue, context) -> { + final String format = argumentQueue.popOr("Format expected.").value(); + return Tag.selfClosingInserting(context.deserialize(DateTimeFormatter.ofPattern(format).format(time))); + }); + } +} diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Placeholder.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Placeholder.java index 1cf93eaf2..6ba2444ab 100644 --- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Placeholder.java +++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/resolver/Placeholder.java @@ -23,11 +23,6 @@ */ package net.kyori.adventure.text.minimessage.tag.resolver; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.util.Locale; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.minimessage.tag.Tag; @@ -86,42 +81,4 @@ private Placeholder() { public static TagResolver.@NotNull Single component(final @NotNull String key, final @NotNull ComponentLike value) { return TagResolver.resolver(key, Tag.selfClosingInserting(value)); } - - /** - * Creates a replacement that inserts a number as a component. The component will be formatted by the provided DecimalFormat. - * - *

Refer to {@link DecimalFormat} for usable patterns.

- * - *

This replacement is auto-closing, so its style will not influence the style of following components.

- * - * @param key the key - * @param number the number - * @return the placeholder - * @since 4.10.0 - */ - public static TagResolver formatNumber(final @NotNull String key, final Number number) { - return TagResolver.resolver(key, (argumentQueue, context) -> { - final String format = argumentQueue.popOr("Format expected.").value(); - return Tag.selfClosingInserting(Component.text(new DecimalFormat(format, new DecimalFormatSymbols(Locale.ROOT)).format(number))); - }); - } - - /** - * Creates a replacement that inserts a date or a time as a component. The component will be formatted by the provided Date Format. - * - *

Refer to {@link DateTimeFormatter} for usable patterns.

- * - *

This replacement is auto-closing, so its style will not influence the style of following components.

- * - * @param key the key - * @param time the time - * @return the placeholder - * @since 4.10.0 - */ - public static TagResolver formatDate(final @NotNull String key, final TemporalAccessor time) { - return TagResolver.resolver(key, (argumentQueue, context) -> { - final String format = argumentQueue.popOr("Format expected.").value(); - return Tag.selfClosingInserting(Component.text(DateTimeFormatter.ofPattern(format).format(time))); - }); - } } diff --git a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/FormatterTest.java b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/FormatterTest.java new file mode 100644 index 000000000..efb62f9b1 --- /dev/null +++ b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/FormatterTest.java @@ -0,0 +1,91 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.minimessage.tag; + +import java.time.LocalDateTime; +import java.time.Month; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.AbstractTest; +import org.junit.jupiter.api.Test; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.minimessage.tag.resolver.Formatter.formatDate; +import static net.kyori.adventure.text.minimessage.tag.resolver.Formatter.formatNumber; + +public class FormatterTest extends AbstractTest { + @Test + void testNumberFormatter() { + final String input = " is a nice number"; + final Component expected = text("20.00 is a nice number"); + + this.assertParsedEquals( + expected, + input, + formatNumber("double", 20d) + ); + } + + @Test + void testNumberFormatterWithDecimal() { + final String input = " is a nice number"; + final Component expected = text("2.000,00 is a nice number"); + + this.assertParsedEquals( + expected, + input, + formatNumber("double", 2000d) + ); + } + + @Test + void testNumberFormatterNegative() { + final String input = "#.00;-#.00'> is a nice number"; + final Component expectedNegative = text().append(text("-5.00", NamedTextColor.RED), text(" is a nice number", NamedTextColor.BLUE)).build(); + final Component expectedPositive = text().append(text("5.00", NamedTextColor.GREEN), text(" is a nice number", NamedTextColor.BLUE)).build(); + + this.assertParsedEquals( + expectedNegative, + input, + formatNumber("double", -5) + ); + this.assertParsedEquals( + expectedPositive, + input, + formatNumber("double", 5) + ); + } + + @Test + void testDateFormatter() { + final String input = " is a date"; + final Component expected = text("2022-02-26 21:00:00 is a date"); + + this.assertParsedEquals( + expected, + input, + formatDate("date", LocalDateTime.of(2022, Month.FEBRUARY, 26, 21, 0, 0)) + ); + } +} diff --git a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/PlaceholderTest.java b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/PlaceholderTest.java index 9158ef02b..4d7db249f 100644 --- a/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/PlaceholderTest.java +++ b/text-minimessage/src/test/java/net/kyori/adventure/text/minimessage/tag/PlaceholderTest.java @@ -23,8 +23,6 @@ */ package net.kyori.adventure.text.minimessage.tag; -import java.time.LocalDateTime; -import java.time.Month; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.minimessage.AbstractTest; @@ -39,8 +37,6 @@ import static net.kyori.adventure.text.format.NamedTextColor.GREEN; import static net.kyori.adventure.text.format.NamedTextColor.RED; import static net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component; -import static net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.formatDate; -import static net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.formatNumber; import static net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -137,28 +133,4 @@ void testRepeatedResolvingOfStringPlaceholders() { component("feline", text("cat")) ); } - - @Test - void testNumberFormatter() { - final String input = " is a nice number"; - final Component expected = text("20.00 is a nice number"); - - this.assertParsedEquals( - expected, - input, - formatNumber("double", 20d) - ); - } - - @Test - void testDateFormatter() { - final String input = " is a date"; - final Component expected = text("2022-02-26 21:00:00 is a date"); - - this.assertParsedEquals( - expected, - input, - formatDate("date", LocalDateTime.of(2022, Month.FEBRUARY, 26, 21, 0, 0)) - ); - } }