Skip to content
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

minimessage: Add a reset tag via a parser directive #682

Merged
merged 1 commit into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,14 @@ interface Builder extends Buildable.Builder<MiniMessage> {
@NotNull Builder editTags(final @NotNull Consumer<TagResolver.Builder> adder);

/**
* Allows to enable strict mode (disabled by default).
* Enables strict mode (disabled by default).
*
* <p>By default, MiniMessage will allow {@link net.kyori.adventure.text.minimessage.tag.Inserting#allowsChildren() child-allowing} tags to be implicitly closed. When strict mode
* is enabled, all child-allowing tags which are {@code <opened>} must be explicitly {@code </closed>} as well.</p>
*
* <p>Additionally, the {@link net.kyori.adventure.text.minimessage.tag.ParserDirective#RESET reset tag} is disabled in this mode.
* Any usage of this tag will throw a parser exception if strict mode is enabled.</p>
*
* @param strict if strict mode should be enabled
* @return this builder
* @since 4.10.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import net.kyori.adventure.text.minimessage.parser.node.TagPart;
import net.kyori.adventure.text.minimessage.parser.node.TextNode;
import net.kyori.adventure.text.minimessage.tag.Inserting;
import net.kyori.adventure.text.minimessage.tag.ParserDirective;
import net.kyori.adventure.text.minimessage.tag.Tag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
Expand All @@ -53,8 +54,6 @@
@ApiStatus.Internal
public final class TokenParser {
private static final int MAX_DEPTH = 16;
public static final String RESET = "reset";
public static final String RESET_2 = "r";
// minimessage tags
public static final char TAG_START = '<';
public static final char TAG_END = '>';
Expand Down Expand Up @@ -364,33 +363,30 @@ private static ElementNode buildTree(

case OPEN_TAG:
final TagNode tagNode = new TagNode(node, token, message, tagProvider);
if (isReset(tagNode.name())) {
// <reset> tags get special treatment and don't appear in the tree
// instead, they close all currently open tags

if (strict) {
throw new ParsingExceptionImpl("<reset> tags are not allowed when strict mode is enabled", message, token);
}
node = root;
} else {
if (tagNameChecker.test(tagNode.name())) {
final Tag transformation = tagProvider.resolve(tagNode);
if (transformation == null) {
// something went wrong, ignore it
// if strict mode is enabled this will throw an exception for us
node.addChild(new TextNode(node, token, message));
} else {
// This is a recognized tag, goes in the tree
tagNode.tag(transformation);
node.addChild(tagNode);
if (!(transformation instanceof Inserting) || ((Inserting) transformation).allowsChildren()) {
node = tagNode; // TODO: self-terminating tags (i.e. <tag/>) don't set this, so they don't have children
}
if (tagNameChecker.test(tagNode.name())) {
final Tag tag = tagProvider.resolve(tagNode);
if (tag == null) {
// something went wrong, ignore it
// if strict mode is enabled this will throw an exception for us
node.addChild(new TextNode(node, token, message));
} else if (tag == ParserDirective.RESET) {
// <reset> tags get special treatment and don't appear in the tree
// instead, they close all currently open tags
if (strict) {
throw new ParsingExceptionImpl("<reset> tags are not allowed when strict mode is enabled", message, token);
}
node = root;
} else {
// not recognized, plain text
node.addChild(new TextNode(node, token, message));
// This is a recognized tag, goes in the tree
tagNode.tag(tag);
node.addChild(tagNode);
if (!(tag instanceof Inserting) || ((Inserting) tag).allowsChildren()) {
node = tagNode; // TODO: self-terminating tags (i.e. <tag/>) don't set this, so they don't have children
}
}
} else {
// not recognized, plain text
node.addChild(new TextNode(node, token, message));
}
break; // OPEN_TAG

Expand All @@ -407,12 +403,15 @@ private static ElementNode buildTree(
}

final String closeTagName = closeValues.get(0);
if (isReset(closeTagName)) {
// This is a synthetic node, closing it means nothing in the context of building a tree
continue;
}

if (!tagNameChecker.test(closeTagName)) {
if (tagNameChecker.test(closeTagName)) {
final Tag tag = tagProvider.resolve(closeTagName);

if (tag == ParserDirective.RESET) {
// This is a synthetic node, closing it means nothing in the context of building a tree
continue;
}
} else {
// tag does not exist, so treat it as text
node.addChild(new TextNode(node, token, message));
continue;
Expand Down Expand Up @@ -488,10 +487,6 @@ private static ElementNode buildTree(
return root;
}

private static boolean isReset(final String input) {
return input.equalsIgnoreCase(RESET) || input.equalsIgnoreCase(RESET_2);
}

/**
* Determine if a set of close string parts closes the given list of open tag parts. If the open parts starts with
* the set of close parts, then this method returns {@code true}.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 org.jetbrains.annotations.ApiStatus;

/**
* Tags implementing this interface are used to provide directives, or instructions, to the parser directly.
*
* @see #RESET
* @since 4.10.0
*/
@ApiStatus.NonExtendable
public /* sealed */ interface ParserDirective extends Tag {
/**
* Instructs the parser to reset all style, events, insertions, etc.
*
* <p>If {@link net.kyori.adventure.text.minimessage.MiniMessage.Builder#strict(boolean) strict mode} is enabled, usage of
* this tag is disallowed and a parse exception will be thrown if this tag is present.</p>
*
* @since 4.10.0
*/
Tag RESET = new ParserDirective() {
@Override
public String toString() {
return "ParserDirective.RESET";
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@
/**
* A tag definition for the MiniMessage language.
*
* <p>All implementations of {@code Tag} must implement one of {@link Inserting}, {@link Modifying}, or {@link PreProcess}.</p>
* <p>All implementations of {@code Tag} must implement one of {@link Inserting}, {@link Modifying},
* {@link ParserDirective} or {@link PreProcess}.</p>
*
* @since 4.10.0
*/
public /* sealed */ interface Tag /* permits Inserting, Modifying, PreProcess, /internal/ AbstractTag */ {
public /* sealed */ interface Tag /* permits Inserting, Modifying, ParserDirective, PreProcess, /internal/ AbstractTag */ {

/**
* Create a tag that inserts the content literally into the parse string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.stream.Stream;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.tag.ParserDirective;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;

/**
Expand All @@ -39,6 +40,8 @@
* @since 4.10.0
*/
public final class StandardTags {
private static final String RESET_TAG = "reset";

private StandardTags() {
}

Expand All @@ -62,6 +65,7 @@ private StandardTags() {
private static final TagResolver FONT = TagResolver.resolver(FontTag.FONT, FontTag::create);
private static final TagResolver GRADIENT = TagResolver.resolver(GradientTag.GRADIENT, GradientTag::create);
private static final TagResolver RAINBOW = TagResolver.resolver(RainbowTag.RAINBOW, RainbowTag::create);
private static final TagResolver RESET = TagResolver.resolver(RESET_TAG, ParserDirective.RESET);
private static final TagResolver ALL = TagResolver.builder()
.resolvers(
HOVER_EVENT,
Expand All @@ -73,7 +77,8 @@ private StandardTags() {
FONT,
DECORATION,
GRADIENT,
RAINBOW
RAINBOW,
RESET
)
.build();

Expand Down Expand Up @@ -171,6 +176,16 @@ public static TagResolver rainbow() {
return RAINBOW;
}

/**
* Get a resolver for the {@value RESET_TAG} tag.
*
* @return a resolver for the {@value RESET_TAG} tag.
* @since 4.10.0
*/
public static TagResolver reset() {
return RESET;
}

/**
* Get a resolver that handles all default standard tags.
*
Expand Down