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

Implement MiniMessage preprocessor and cleanup parsing #749

Merged
merged 7 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -52,6 +52,7 @@ class ContextImpl implements Context {
private String message;
private final MiniMessage miniMessage;
private final TagResolver tagResolver;
private final UnaryOperator<String> preProcessor;
private final UnaryOperator<Component> postProcessor;

ContextImpl(
Expand All @@ -60,13 +61,14 @@ class ContextImpl implements Context {
final String message,
final MiniMessage miniMessage,
final @NotNull TagResolver extraTags,
final UnaryOperator<Component> postProcessor
) {
final UnaryOperator<String> preProcessor,
final UnaryOperator<Component> postProcessor) {
bluelhf marked this conversation as resolved.
Show resolved Hide resolved
this.strict = strict;
this.debugOutput = debugOutput;
this.message = message;
this.miniMessage = miniMessage;
this.tagResolver = extraTags;
this.preProcessor = preProcessor == null ? UnaryOperator.identity() : preProcessor;
this.postProcessor = postProcessor == null ? UnaryOperator.identity() : postProcessor;
}

Expand All @@ -76,9 +78,10 @@ static ContextImpl of(
final String input,
final MiniMessageImpl miniMessage,
final TagResolver extraTags,
final UnaryOperator<String> preProcessor,
final UnaryOperator<Component> postProcessor
) {
return new ContextImpl(strict, debugOutput, input, miniMessage, extraTags, postProcessor);
return new ContextImpl(strict, debugOutput, input, miniMessage, extraTags, preProcessor, postProcessor);
}

public boolean strict() {
Expand All @@ -105,6 +108,10 @@ public UnaryOperator<Component> postProcessor() {
return this.postProcessor;
}

public UnaryOperator<String> preProcessor() {
return this.preProcessor;
}

@Override
public @NotNull Component deserialize(final @NotNull String message) {
return this.miniMessage.deserialize(requireNonNull(message, "message"), this.tagResolver);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,16 @@ interface Builder extends AbstractBuilder<MiniMessage> {
*/
@NotNull Builder postProcessor(final @NotNull UnaryOperator<Component> postProcessor);

/**
* Specify a function that takes the string at the start of the parser process.
* <p>By default, this does absolutely nothing.</p>
*
* @param preProcessor method run at the start of parsing
* @return this builder
* @since 4.11.0
*/
@NotNull Builder preProcessor(final @NotNull UnaryOperator<String> preProcessor);

/**
* Builds the serializer.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,41 +54,44 @@ final class MiniMessageImpl implements MiniMessage {
static final class Instances {
static final MiniMessage INSTANCE = SERVICE
.map(Provider::miniMessage)
.orElseGet(() -> new MiniMessageImpl(TagResolver.standard(), false, null, DEFAULT_COMPACTING_METHOD));
.orElseGet(() -> new MiniMessageImpl(TagResolver.standard(), false, null, DEFAULT_NO_OP, DEFAULT_COMPACTING_METHOD));
}

static final UnaryOperator<String> DEFAULT_NO_OP = UnaryOperator.identity();
static final UnaryOperator<Component> DEFAULT_COMPACTING_METHOD = Component::compact;

private final boolean strict;
private final @Nullable Consumer<String> debugOutput;
private final UnaryOperator<Component> postProcessor;
private final UnaryOperator<String> preProcessor;
final MiniMessageParser parser;

MiniMessageImpl(final @NotNull TagResolver resolver, final boolean strict, final @Nullable Consumer<String> debugOutput, final @NotNull UnaryOperator<Component> postProcessor) {
MiniMessageImpl(final @NotNull TagResolver resolver, final boolean strict, final @Nullable Consumer<String> debugOutput, final @NotNull UnaryOperator<String> preProcessor, final @NotNull UnaryOperator<Component> postProcessor) {
this.parser = new MiniMessageParser(resolver);
this.strict = strict;
this.debugOutput = debugOutput;
this.preProcessor = preProcessor;
this.postProcessor = postProcessor;
}

@Override
public @NotNull Component deserialize(final @NotNull String input) {
return this.parser.parseFormat(input, this.newContext(input, null));
return this.parser.parseFormat(this.newContext(input, null));
}

@Override
public @NotNull Component deserialize(final @NotNull String input, final @NotNull TagResolver tagResolver) {
return this.parser.parseFormat(input, this.newContext(input, requireNonNull(tagResolver, "tagResolver")));
return this.parser.parseFormat(this.newContext(input, requireNonNull(tagResolver, "tagResolver")));
}

@Override
public Node.@NotNull Root deserializeToTree(final @NotNull String input) {
return this.parser.parseToTree(input, this.newContext(input, null));
return this.parser.parseToTree(this.newContext(input, null));
}

@Override
public Node.@NotNull Root deserializeToTree(final @NotNull String input, final @NotNull TagResolver tagResolver) {
return this.parser.parseToTree(input, this.newContext(input, requireNonNull(tagResolver, "tagResolver")));
return this.parser.parseToTree(this.newContext(input, requireNonNull(tagResolver, "tagResolver")));
}

@Override
Expand All @@ -113,30 +116,30 @@ private SerializableResolver serialResolver(final @Nullable TagResolver extraRes

@Override
public @NotNull String escapeTags(final @NotNull String input) {
return this.parser.escapeTokens(input, this.newContext(input, null));
return this.parser.escapeTokens(this.newContext(input, null));
}

@Override
public @NotNull String escapeTags(final @NotNull String input, final @NotNull TagResolver tagResolver) {
return this.parser.escapeTokens(input, this.newContext(input, tagResolver));
return this.parser.escapeTokens(this.newContext(input, tagResolver));
}

@Override
public @NotNull String stripTags(final @NotNull String input) {
return this.parser.stripTokens(input, this.newContext(input, null));
return this.parser.stripTokens(this.newContext(input, null));
}

@Override
public @NotNull String stripTags(final @NotNull String input, final @NotNull TagResolver tagResolver) {
return this.parser.stripTokens(input, this.newContext(input, tagResolver));
return this.parser.stripTokens(this.newContext(input, tagResolver));
}

private @NotNull ContextImpl newContext(final @NotNull String input, final @Nullable TagResolver resolver) {
requireNonNull(input, "input");
if (resolver == null) {
return ContextImpl.of(this.strict, this.debugOutput, input, this, TagResolver.empty(), this.postProcessor);
return ContextImpl.of(this.strict, this.debugOutput, input, this, TagResolver.empty(), this.preProcessor, this.postProcessor);
} else {
return ContextImpl.of(this.strict, this.debugOutput, input, this, resolver, this.postProcessor);
return ContextImpl.of(this.strict, this.debugOutput, input, this, resolver, this.preProcessor, this.postProcessor);
}
}

Expand All @@ -145,6 +148,7 @@ static final class BuilderImpl implements Builder {
private boolean strict = false;
private Consumer<String> debug = null;
private UnaryOperator<Component> postProcessor = DEFAULT_COMPACTING_METHOD;
private UnaryOperator<String> preProcessor = DEFAULT_NO_OP;

BuilderImpl() {
BUILDER.accept(this);
Expand All @@ -156,6 +160,7 @@ static final class BuilderImpl implements Builder {
this.strict = serializer.strict;
this.debug = serializer.debugOutput;
this.postProcessor = serializer.postProcessor;
this.preProcessor = serializer.preProcessor;
}

@Override
Expand Down Expand Up @@ -191,9 +196,15 @@ static final class BuilderImpl implements Builder {
return this;
}

@Override
public @NotNull Builder preProcessor(final @NotNull UnaryOperator<String> preProcessor) {
this.preProcessor = Objects.requireNonNull(preProcessor, "preProcessor");
return this;
}

@Override
public @NotNull MiniMessage build() {
return new MiniMessageImpl(this.tagResolver, this.strict, this.debug, this.postProcessor);
return new MiniMessageImpl(this.tagResolver, this.strict, this.debug, this.preProcessor, this.postProcessor);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ final class MiniMessageParser {
this.tagResolver = tagResolver;
}

@NotNull String escapeTokens(final @NotNull String richMessage, final @NotNull ContextImpl context) {
final StringBuilder sb = new StringBuilder(richMessage.length());
this.escapeTokens(sb, richMessage, context);
@NotNull String escapeTokens(final @NotNull ContextImpl context) {
final StringBuilder sb = new StringBuilder(context.message().length());
this.escapeTokens(sb, context);
return sb.toString();
}

void escapeTokens(final StringBuilder sb, final @NotNull String richMessage, final @NotNull ContextImpl context) {
void escapeTokens(final StringBuilder sb, final @NotNull ContextImpl context) {
this.escapeTokens(sb, context.message(), context);
}

private void escapeTokens(final StringBuilder sb, final String richMessage, final ContextImpl context) {
this.processTokens(sb, richMessage, context, (token, builder) -> {
builder.append('\\').append(TokenParser.TAG_START);
if (token.type() == TokenType.CLOSE_TAG) {
Expand All @@ -82,12 +86,16 @@ void escapeTokens(final StringBuilder sb, final @NotNull String richMessage, fin
});
}

@NotNull String stripTokens(final @NotNull String richMessage, final @NotNull ContextImpl context) {
final StringBuilder sb = new StringBuilder(richMessage.length());
this.processTokens(sb, richMessage, context, (token, builder) -> {});
@NotNull String stripTokens(final @NotNull ContextImpl context) {
final StringBuilder sb = new StringBuilder(context.message().length());
this.processTokens(sb, context, (token, builder) -> {});
return sb.toString();
}

private void processTokens(final @NotNull StringBuilder sb, final @NotNull ContextImpl context, final BiConsumer<Token, StringBuilder> tagHandler) {
this.processTokens(sb, context.message(), context, tagHandler);
}

private void processTokens(final @NotNull StringBuilder sb, final @NotNull String richMessage, final @NotNull ContextImpl context, final BiConsumer<Token, StringBuilder> tagHandler) {
final TagResolver combinedResolver = TagResolver.resolver(this.tagResolver, context.extraTags());
final List<Token> root = TokenParser.tokenize(richMessage);
Expand Down Expand Up @@ -117,12 +125,13 @@ private void processTokens(final @NotNull StringBuilder sb, final @NotNull Strin
}
}

@NotNull RootNode parseToTree(final @NotNull String richMessage, final @NotNull ContextImpl context) {
@NotNull RootNode parseToTree(final @NotNull ContextImpl context) {
final TagResolver combinedResolver = TagResolver.resolver(this.tagResolver, context.extraTags());
final String processedMessage = context.preProcessor().apply(context.message());
final Consumer<String> debug = context.debugOutput();
if (debug != null) {
debug.accept("Beginning parsing message ");
debug.accept(richMessage);
debug.accept(processedMessage);
debug.accept("\n");
}

Expand Down Expand Up @@ -183,10 +192,10 @@ private void processTokens(final @NotNull StringBuilder sb, final @NotNull Strin
return combinedResolver.has(sanitized);
};

final String preProcessed = TokenParser.resolvePreProcessTags(richMessage, transformationFactory);
final String preProcessed = TokenParser.resolvePreProcessTags(processedMessage, transformationFactory);
context.message(preProcessed);
// Then, once MiniMessage placeholders have been inserted, we can do the real parse
final RootNode root = TokenParser.parse(transformationFactory, tagNameChecker, preProcessed, richMessage, context.strict());
final RootNode root = TokenParser.parse(transformationFactory, tagNameChecker, preProcessed, processedMessage, context.strict());

if (debug != null) {
debug.accept("Text parsed into element tree:\n");
Expand All @@ -196,8 +205,8 @@ private void processTokens(final @NotNull StringBuilder sb, final @NotNull Strin
return root;
}

@NotNull Component parseFormat(final @NotNull String richMessage, final @NotNull ContextImpl context) {
final ElementNode root = this.parseToTree(richMessage, context);
@NotNull Component parseFormat(final @NotNull ContextImpl context) {
final ElementNode root = this.parseToTree(context);
return Objects.requireNonNull(context.postProcessor().apply(this.treeToComponent(root, context)), "Post-processor must not return null");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
package net.kyori.adventure.text.minimessage;

import java.util.Collections;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
Expand Down Expand Up @@ -68,7 +69,7 @@ protected final String prettyPrint(final Component component) {
}

public static Context dummyContext(final String originalMessage) {
return ContextImpl.of(false, null, originalMessage, (MiniMessageImpl) PARSER, TagResolver.empty(), Component::compact);
return ContextImpl.of(false, null, originalMessage, (MiniMessageImpl) PARSER, TagResolver.empty(), UnaryOperator.identity(), Component::compact);
}

public static ArgumentQueue emptyArgumentQueue(final Context context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ void testPlaceholderComponentMixed() {
this.assertParsedEquals(miniMessage, expected, input, t1, t2);
}

@Test
void testPreprocessing() {
final Component expected = MiniMessage.miniMessage().deserialize("<red>Hello, world!</red>");

final String input = "Hello";
final MiniMessage miniMessage = MiniMessage.builder().preProcessor(str -> "<red>" + str + ", world!</red>").build();
this.assertParsedEquals(miniMessage, expected, input);
}

// GH-103
@Test
void testPlaceholderInHover() {
Expand Down