Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2be39cc
feat: start work on named arguments in tags
Strokkur424 Sep 17, 2025
0778118
feat: modify token parser to parse named arguments
Strokkur424 Sep 18, 2025
7811e0e
chore: introduce new TokenType to uniquely distinguish value-less tog…
Strokkur424 Sep 18, 2025
e3dfad9
feat: (WIP) abstracting away TagProvider into QueuedTagProvider and N…
Strokkur424 Sep 18, 2025
f44b15c
fix: (WIP) parser should now theoretically be able to distinguish nam…
Strokkur424 Sep 19, 2025
57437e6
feat: flesh out parsing logic further and fix a bunch of issues
Strokkur424 Sep 19, 2025
08cbeaf
feat: add test for basic named argument parsing
Strokkur424 Sep 19, 2025
265ad37
feat: start adding more tests
Strokkur424 Sep 19, 2025
2a67407
fix: <red > tag with space being recognized as valid tag
Strokkur424 Sep 19, 2025
84bd76c
feat: add a bunch more tests
Strokkur424 Sep 19, 2025
aac5f9a
chore: fix all compile time issues
Strokkur424 Sep 19, 2025
557be89
feat: add test
Strokkur424 Sep 19, 2025
bc563a1
chore: cleanup diff and rename to sequential
Strokkur424 Sep 19, 2025
dc151dc
chore: add a bunch more tests
Strokkur424 Sep 20, 2025
2fec655
feat: add inverted flag arguments
Strokkur424 Sep 20, 2025
8dfb4a3
feat: split the claiming resolvers into named and sequenced resolvers
Strokkur424 Sep 20, 2025
a23bb5f
feat: add missing context newException method and try to parse tag wi…
Strokkur424 Sep 20, 2025
70fdce2
feat: add isFlagPresent
Strokkur424 Sep 20, 2025
233e9fe
feat: add style tag
Strokkur424 Sep 20, 2025
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
Prev Previous commit
Next Next commit
feat: flesh out parsing logic further and fix a bunch of issues
  • Loading branch information
Strokkur424 committed Sep 19, 2025
commit 57437e67ec80f1e6ed126114416975d1a3825242
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,18 @@ private static void parseSecondPass(final String message, final List<Token> toke
currentStringChar = (char) codePoint;
} else if (codePoint == ' ') {
if (namedArguments == TriState.NOT_SET) {
// Having a whitespace here is nice and all, but there is a slight issue. In the event of a tag just looking like this <name >,
// it should not actually be interpreted as a named argument tag, since it has no arguments which would actually use that.
// We can simply check whether the remainer of this message is blank.
final String substring = message.substring(marker + 1, endIndex);
if (isBlank(substring)) {
i += substring.length();
break;
}

insert(token, new Token(marker, i, TokenType.TAG_VALUE));
namedArguments = TriState.TRUE;
marker = i;
marker = i + 1;
break;
} else if (namedArguments == TriState.FALSE) {
// If the arguments are unnamed, spaces are to be interpreted literally
Expand Down Expand Up @@ -413,6 +423,17 @@ private static void parseSecondPass(final String message, final List<Token> toke
// anything not matched is the final part
if (token.childTokens() == null || token.childTokens().isEmpty()) {
insert(token, new Token(startIndex, endIndex, TokenType.TAG_VALUE));
} else if (namedArguments == TriState.TRUE) {
if (marker < endIndex) {
if (nextNormalIsArgumentValue) {
insert(token, new Token(marker, endIndex, TokenType.TAG_VALUE_NAME));
} else {
// If there are only whitespace characters remaining, we do not want to create a new token here, as it would be empty
if (!isBlank(message.substring(marker, endIndex))) {
insert(token, new Token(marker, endIndex, TokenType.TAG_VALUE_TOGGLE));
}
}
}
} else {
final int end = token.childTokens().get(token.childTokens().size() - 1).endIndex();
if (end != endIndex) {
Expand All @@ -422,6 +443,19 @@ private static void parseSecondPass(final String message, final List<Token> toke
}
}

private static boolean isBlank(final CharSequence cs) {
int index = 0;
boolean isBlank = true;
while (index < cs.length()) {
if (!Character.isWhitespace(cs.charAt(index++))) {
isBlank = false;
break;
}
}

return isBlank;
}

/*
* Build a tree from the OPEN_TAG and CLOSE_TAG tokens
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,11 @@ public void accept(final int start, final int end, final @NotNull TokenType toke
}

// we might care if it's a pre-process!
if (this.tagProvider instanceof TokenParser.QueuedTagProvider) {
final @Nullable Tag replacement = this.tagProvider.resolveQueued(TagProvider.sanitizePlaceholderName(tag), parts, tokens.get(0));
final @Nullable Tag replacement = this.tagProvider.resolveQueued(TagProvider.sanitizePlaceholderName(tag), parts, tokens.get(0));

if (replacement instanceof PreProcess) {
this.builder.append(Objects.requireNonNull(((PreProcess) replacement).value(), "PreProcess replacements cannot return null"));
return;
}
if (replacement instanceof PreProcess) {
this.builder.append(Objects.requireNonNull(((PreProcess) replacement).value(), "PreProcess replacements cannot return null"));
return;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ public boolean has(final @NotNull String name) {
};
}

static @NotNull TagResolver namedResolver(final @NotNull String name, final @NotNull BiFunction<NamedArgumentMap, Context, Tag> handler) {
return namedResolver(Collections.singleton(name), handler);
}

static @NotNull TagResolver namedResolver(final @NotNull Set<String> names, final @NotNull BiFunction<NamedArgumentMap, Context, Tag> handler) {
final Set<String> ownNames = new HashSet<>(names);
for (final String name : ownNames) {
Expand Down Expand Up @@ -340,15 +344,15 @@ interface Queued extends TagResolver {
@Override
@Nullable
default Tag resolveNamed(final @NotNull String name, final @NotNull NamedArgumentMap arguments, final @NotNull Context ctx) throws ParsingException {
throw new UnsupportedOperationException();
return null;
}
}

interface Named extends TagResolver {
@Override
@Nullable
default Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException {
throw new UnsupportedOperationException();
return null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
import net.kyori.adventure.text.minimessage.internal.parser.TokenType;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.NamedArgumentMap;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.minimessage.tree.Node;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
Expand Down Expand Up @@ -571,19 +573,7 @@ void testValidTagNames() {
void invalidPreprocessTagNames() {
final String input = "Some<##>of<>these<tag>are<3 >tags";
final Component expected = Component.text("Some<##>of<>these(meow)are<3 >tags");
final TagResolver alwaysMatchingResolver = new TagResolver.Queued() {
@Override
public Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException {
return Tag.preProcessParsed("(meow)");
}

@Override
public boolean has(final @NotNull String name) {
return true;
}
};

this.assertParsedEquals(expected, input, alwaysMatchingResolver);
this.assertParsedEquals(expected, input, new AlwaysMatchingResolver());
}

// https://github.com/KyoriPowered/adventure/issues/1011
Expand All @@ -594,4 +584,21 @@ void testNonTerminatingQuoteArgument() {

this.assertParsedEquals(expected, input);
}

private static final class AlwaysMatchingResolver implements TagResolver.Queued, TagResolver.Named {
@Override
public @NotNull Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException {
return Tag.preProcessParsed("(meow)");
}

@Override
public @NotNull Tag resolveNamed(final @NotNull String name, final @NotNull NamedArgumentMap arguments, final @NotNull Context ctx) throws ParsingException {
return Tag.preProcessParsed("(meow)");
}

@Override
public boolean has(final @NotNull String name) {
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

import com.google.common.collect.testing.google.TestStringBiMapGenerator;
import net.kyori.adventure.pointer.Pointered;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
Expand Down Expand Up @@ -439,6 +441,38 @@ void debugModeMoreComplexNoError() {
assertTrue(messages.contains("}"));
}

@Test
void debugNamedArguments() {
final String input = "<head name=Strokkur24 disable_outer_layer> I have a <red>red</red> text!";

final StringBuilder sb = new StringBuilder();
MiniMessage.builder()
.tags(TagResolver.resolver(
// At the time of writing, the <head> tag did not yet exist.
TagResolver.namedResolver("head", (args, ctx) -> Tag.selfClosingInserting(Component.text("dummy"))),
TagResolver.standard()
)).debug(sb::append).build().deserialize(input);
final List<String> messages = Arrays.asList(sb.toString().split("\n"));

assertTrue(messages.contains("Beginning parsing message <head name=Strokkur24 disable_outer_layer> I have a <red>red</red> text!"));
assertTrue(messages.contains("Attempting to match node as queued 'red' at column 0"));
assertTrue(anyMatch(messages, it -> it.startsWith("Successfully matched node 'red' to tag ")));
assertTrue(messages.contains("Attempting to match node as named 'head' at column 0"));
assertTrue(anyMatch(messages, it -> it.startsWith("Successfully matched node 'head' to tag ")));
assertTrue(messages.contains("Attempting to match node as queued 'red' at column 52"));
assertTrue(anyMatch(messages, it -> it.startsWith("Successfully matched node 'red' to tag ")));
assertTrue(messages.contains("Text parsed into element tree:"));
assertTrue(messages.contains("Node {"));
assertTrue(messages.contains(" TagNode('head', 'name', 'Strokkur24', 'disable_outer_layer') {"));
assertTrue(messages.contains(" }"));
assertTrue(messages.contains(" TextNode(' I have a ')"));
assertTrue(messages.contains(" TagNode('red') {"));
assertTrue(messages.contains(" TextNode('red')"));
assertTrue(messages.contains(" }"));
assertTrue(messages.contains(" TextNode(' text!')"));
assertTrue(messages.contains("}"));
}

static class TestTarget1 implements Pointered {
public String data;
}
Expand Down