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: Unify transformations and placeholders #672

Merged
merged 17 commits into from Feb 13, 2022

Conversation

zml2008
Copy link
Member

@zml2008 zml2008 commented Feb 7, 2022

This replaces both with a new Tag system, which exposes both
argumentless insertions and argument-handling transformations in one
registry, with one type hierarchy.

I hope to open the door to piggybacking on these tag resolvers to handle serialization as well at some future point.

Still to do:

  • examinability of built-in Tag implementations, to ease debugging
  • correct the message passed to exceptions created in Context
  • names?
  • API-ification of ElementNode (related to text-minimessage: add deserialization method returning parsed tree #658)
  • [ ] common impl for rainbow and gradient tags happening later, not an API-visible change
  • [ ] for rainbow and gradient length calculations, a way to distinguish between pre-calculated and lazily computed Inserting types (does this make sense to be restricted to just placeholders even?) delayed
  • ArgumentQueue/other helpers for parsing tag arguments (something providing API along the lines args.pop() that auto-throws an exception if the required argument isn't present, other helpers like Tag.Argument#asDouble(): OptionalDouble)

Overview

The changes in this PR aim to unify two very similar APIs to reduce the number of concepts developers have to deal with. In doing so, I've managed to reduce some layers of abstraction, and provide the full capabilities of each system for every use case. This has resulted in some shuffling around:

Placeholders Transformations New Tags
PlaceholderResolver TransformationRegistry TagResolver
Placeholder TransformationFactory TagResolver
Replacement Transformation Tag

As you can see, the concepts of a factory/placeholder and resolver/registry have been merged -- it is possible to consider a Placeholder or Factory as a simple case of a resolver, which only contains one element.

Types of Tags

Inserting

These tags are fairly straightforward: they represent a literal Component. The vast majority of Tag implementations will want to be Inserting tags. Inserting tags may also optionally be self-closing -- by default, this is only true for tags created by resolvers from the Placeholder class, so that placeholders are self-contained.

Modifying

Modiying tags have the ability to perform transformations on the entire component tree of their children after it has been created. This is used for the <rainbow> and <gradient> tags, but can be applied to any other more complex transformations.

PreProcess (or minimessage)

These tags have a value of a MiniMessage string. They are unique in that they're applied before any sort of parsing begins, so can be fragments rather than entire strings.

Examples

The implementations of the built-in tags for representing components provide a starting point for examples of how this system works. However, to address some more simple cases, and provide examples of a custom tag with richer functionality:

A simple case with placeholder-equivalent tags:

    final String input = "[<prefix><player><suffix>] >> <message>";
    final TagResolver placeholders = TagResolver.combining(
      Placeholder.parsed("prefix", "<red>Admin </red><blue>"),
      Placeholder.component("player", text("kashike")),
      Placeholder.parsed("suffix", "</blue>"),
      Placeholder.component("message", text("Hello!"))
    );
    
    final Component parsed = MiniMessage.miniMessage().deserialize(input, placeholders);

A fancier case, with custom logic

This creates a custom tag that performs all the styling needed to create a standard hyperlink using an HTML-like a tag:

  Component aTagExample() {
    final String input = "Hello, <a:https://kyori.net>click me!</a> but not me!";
    final MiniMessage extendedInstance = MiniMessage.builder()
      .tags(b -> b.resolver(TagResolver.resolver("a", MiniMessageTest::createA)))
      .build();

    return extendedInstance.deserialize(input);
  }

  static Tag createA(final ArgumentQueue args, final Context ctx) {
    final String link = args.popOr("The <a> tag requires exactly one argument, the link to open").value();

    return Tag.styling(
      NamedTextColor.BLUE,
      TextDecoration.UNDERLINED,
      ClickEvent.openUrl(link),
      HoverEvent.showText(Component.text("Open " + link))
    );
  }

This replaces both with a new Tag system, which exposes both
argumentless insertions and argument-handling transformations in one
registry, with one type hierarchy.
Correctly provide the post-preprocessing location information when
creating exceptions.
This allows us to mark the entire parser package as internal
@zml2008 zml2008 marked this pull request as ready for review February 7, 2022 05:34
@zml2008 zml2008 added this to the 4.10.0 milestone Feb 7, 2022
Copy link
Member

@rymiel rymiel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't review because github was broken but i noticed some stuff while testing it locally

@zml2008 zml2008 mentioned this pull request Feb 9, 2022
This simplifies error handling across arguments, and allows better state
tracking in error messages down the road.
@zml2008
Copy link
Member Author

zml2008 commented Feb 10, 2022

I think I've covered all the essential API elements in this PR now. I'd love to get this in by the end of the upcoming weekend, so please get your feedback in.

Copy link
Member

@MiniDigger MiniDigger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very minor stuff.
I was a bit confused by ClickTag not implementing anything and ColorTagResolver being a tag resolver. I also not sure if I like that basically everything is static in the tags classes. Wouldn't they be tag resolvers too, just really simple ones? that would clean up the buildintags class a bit where I got a bit confused too (I guess the comment there can be discared now that I looked at the tags, lol)
like, maybe SimpleTag that is a tag resolver itself?

but overall, outstanding work, I really like how it turned out so far!

Copy link
Member

@kezz kezz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incredibly solid bit of work here - I really don't have much to comment at all beyond saying good job! I've left a few minor nitpicky comments, mainly documentation changes - but these aren't particularly blocking as I'm sure we'll clean up the actual docs upon the release of 4.10.0 which should really help users get used to this change.

Well done!

This was referenced Feb 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Convert templates to “compound” tags
4 participants