From 1e0c814845a53d6f8bc0da0770413a6b9daa35bf Mon Sep 17 00:00:00 2001 From: dags- Date: Tue, 1 May 2018 11:22:01 +0100 Subject: [PATCH] 7.1.0 --- build.gradle | 10 +- gradle/wrapper/gradle-wrapper.properties | 2 +- src/main/java/me/dags/textmu/MUBuilder.java | 143 +------ src/main/java/me/dags/textmu/MUParam.java | 70 ++-- src/main/java/me/dags/textmu/MUParser.java | 381 +++++++++++------- src/main/java/me/dags/textmu/MUTParser.java | 125 ++++++ src/main/java/me/dags/textmu/MarkupSpec.java | 12 +- .../java/me/dags/textmu/MarkupTemplate.java | 342 ++++++++-------- src/main/resources/mcmod.info | 4 +- 9 files changed, 593 insertions(+), 496 deletions(-) create mode 100644 src/main/java/me/dags/textmu/MUTParser.java diff --git a/build.gradle b/build.gradle index f066424..ad46947 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,10 @@ import org.apache.tools.ant.filters.ReplaceTokens +apply plugin: 'java' + group 'me.dags' -version '0.1' -def spongeAPI = '5.1.0' +version '1.0' +def spongeAPI = '7.1.0' def spongeChannel = 'SNAPSHOT' repositories { @@ -13,8 +15,8 @@ repositories { } dependencies { - apply plugin: 'java' - compileOnly "org.spongepowered:spongeapi:${spongeAPI}-$spongeChannel" + compile "org.spongepowered:spongeapi:${spongeAPI}-$spongeChannel" + runtime "org.spongepowered:spongeapi:${spongeAPI}-$spongeChannel" } processResources { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41fe60b..18dadc4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Thu Mar 16 09:16:52 GMT 2017 +#Tue Oct 03 08:21:06 BST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/me/dags/textmu/MUBuilder.java b/src/main/java/me/dags/textmu/MUBuilder.java index ff9c58f..40dc365 100644 --- a/src/main/java/me/dags/textmu/MUBuilder.java +++ b/src/main/java/me/dags/textmu/MUBuilder.java @@ -2,11 +2,6 @@ import org.spongepowered.api.text.LiteralText; import org.spongepowered.api.text.Text; -import org.spongepowered.api.text.TextRepresentable; - -import java.lang.reflect.Array; -import java.util.Iterator; -import java.util.Map; /** * @author dags @@ -15,24 +10,18 @@ class MUBuilder { private final LiteralText.Builder builder = (LiteralText.Builder) LiteralText.builder(); private final StringBuilder stringBuilder = new StringBuilder(128); - private final StringBuilder argBuilder = new StringBuilder(16); private final MarkupSpec markupSpec; - private final Map arguments; - private final boolean argsEnabled; - private boolean buildingArg = false; private boolean escaped = false; private boolean quoted = false; private boolean empty = true; - MUBuilder(MarkupSpec markupSpec, Map arguments) { - this.arguments = arguments; + MUBuilder(MarkupSpec markupSpec) { this.markupSpec = markupSpec; - this.argsEnabled = arguments != MUParser.EMPTY; } boolean isEmpty() { - return empty && stringBuilder.length() == 0 && argBuilder.length() == 0; + return empty && stringBuilder.length() == 0; } boolean isRaw() { @@ -59,17 +48,6 @@ MUBuilder append(char c) { if (escaped || quoted) { stringBuilder.append(c); escaped = false; - } else if (buildingArg) { - if (c == '}') { - String argument = argBuilder.toString(); - argBuilder.setLength(0); - buildingArg = false; - appendArg(argument); - } else { - argBuilder.append(c); - } - } else if (argsEnabled && c == '{') { - buildingArg = true; } else { stringBuilder.append(c); escaped = false; @@ -79,11 +57,6 @@ MUBuilder append(char c) { } MUBuilder append(Text other) { - if (buildingArg) { - stringBuilder.append(argBuilder); - argBuilder.setLength(0); - buildingArg = false; - } appendString(); builder.append(other); empty = false; @@ -103,118 +76,6 @@ private MUBuilder appendString() { return this; } - private void appendArg(String name) { - int splitPoint = name.indexOf(":"); - int joinPoint = name.indexOf(":", 1 + splitPoint); - - if (splitPoint < 0) { - Object value = arguments.get(name); - appendObject(value); - } else if (splitPoint == 0) { - String templateKey = name.substring(splitPoint + 1); - appendTemplate(templateKey); - } else if (joinPoint > 0) { - String valueKey = name.substring(0, splitPoint); - String templateKey = name.substring(splitPoint + 1, joinPoint); - String separator = name.substring(joinPoint + 1); - appendTemplate(templateKey, valueKey, separator); - } else { - String valueKey = name.substring(0, splitPoint); - String templateKey = name.substring(splitPoint + 1); - appendTemplate(templateKey, valueKey, ""); - } - } - - private void appendTemplate(String templateKey) { - Object template = arguments.get(templateKey); - if (template != null) { - if (MarkupTemplate.class.isInstance(template)) { - Text text = MarkupTemplate.class.cast(template).renderTemplate(arguments); - append(text); - } else if (String.class.isInstance(template)) { - Text text = markupSpec.template((String) template).renderTemplate(arguments); - append(text); - } - } - } - - private void appendTemplate(String templateKey, String valueKey, String separator) { - Object templ = arguments.get(templateKey); - Object value = arguments.get(valueKey); - - if (value != null && templ != null) { - MarkupTemplate template = null; - if (MarkupTemplate.class.isInstance(templ)) { - template = MarkupTemplate.class.cast(templ); - } else if (String.class.isInstance(templ)) { - template = markupSpec.template((String) templ); - } - - if (template != null) { - if (Map.class.isInstance(value)) { - appendMap(template, value, separator); - } else if (Iterable.class.isInstance(value)) { - appendIterable(template, value, separator); - } else if (value.getClass().isArray()) { - appendArray(template, value, separator); - } else { - Text text = template.applier().withUnchecked(arguments).with(value).render(); - append(text); - } - } - } - } - - private void appendObject(Object value) { - if (value != null) { - if (MarkupTemplate.class.isInstance(value)) { - Text text = MarkupTemplate.class.cast(value).renderTemplate(arguments); - append(text); - } else if (TextRepresentable.class.isInstance(value)) { - TextRepresentable text = TextRepresentable.class.cast(value); - append(text.toText()); - } else { - stringBuilder.append(value); - } - } - } - - private void appendIterable(MarkupTemplate template, Object value, String separator) { - MarkupTemplate.Applier applier = template.applier().withUnchecked(arguments); - Iterator iterator = Iterable.class.cast(value).iterator(); - while (iterator.hasNext()) { - Object child = iterator.next(); - Text one = applier.with(child).render(); - Text text = !separator.isEmpty() && iterator.hasNext() ? Text.of(one, separator) : one; - append(text); - } - } - - private void appendArray(MarkupTemplate template, Object value, String separator) { - MarkupTemplate.Applier applier = template.applier().withUnchecked(arguments); - int length = Array.getLength(value); - for (int i = 0; i < length; i++) { - Object child = Array.get(value, i); - if (child != null) { - Text one = applier.with(child).render(); - Text text = !separator.isEmpty() && i + 1 < length ? Text.of(one, separator) : one; - append(text); - } - } - } - - private void appendMap(MarkupTemplate template, Object value, String separator) { - MarkupTemplate.Applier applier = template.applier().withUnchecked(arguments); - Map map = Map.class.cast(value); - Iterator> iterator = map.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - Text one = applier.with(entry).render(); - Text text = !separator.isEmpty() && iterator.hasNext() ? Text.of(one, separator) : one; - append(text); - } - } - String string() { return stringBuilder.toString(); } diff --git a/src/main/java/me/dags/textmu/MUParam.java b/src/main/java/me/dags/textmu/MUParam.java index d9ba149..4b4a505 100644 --- a/src/main/java/me/dags/textmu/MUParam.java +++ b/src/main/java/me/dags/textmu/MUParam.java @@ -24,7 +24,7 @@ abstract class MUParam { abstract void apply(Text.Builder builder); - private static final String URL = "((ht|f)tp(s?):\\/\\/|www\\.)(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)"; + private static final String URL = "^((ht|f)tp(s?)://|www\\.)?([\\da-z-]+)(\\.([\\da-z-]+))*((\\.[a-z]{2,6})+|:[0-9]+)(/[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)*?$"; private static final Pattern URL_PATTERN = Pattern.compile(URL, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); static final MUParam EMPTY = new MUParam() { @Override @@ -42,27 +42,31 @@ void apply(Text.Builder builder) {} static Map colors() { if (MUParam.colors.isEmpty()) { Map colors = new HashMap<>(); - Sponge.getRegistry().getAllOf(TextColor.class).stream() - .filter(color -> color != TextColors.RESET && color != TextColors.NONE) + try { + Sponge.getRegistry().getAllOf(TextColor.class).stream() + .filter(color -> color != TextColors.RESET && color != TextColors.NONE) .forEach(color -> colors.put(color.getId().toLowerCase(), color)); - - colors.put("a", TextColors.GREEN); - colors.put("b", TextColors.AQUA); - colors.put("c", TextColors.AQUA); - colors.put("d", TextColors.RED); - colors.put("e", TextColors.LIGHT_PURPLE); - colors.put("f", TextColors.YELLOW); - colors.put("0", TextColors.BLACK); - colors.put("1", TextColors.DARK_BLUE); - colors.put("2", TextColors.DARK_GREEN); - colors.put("3", TextColors.DARK_AQUA); - colors.put("4", TextColors.DARK_RED); - colors.put("5", TextColors.DARK_PURPLE); - colors.put("6", TextColors.GOLD); - colors.put("7", TextColors.GRAY); - colors.put("8", TextColors.DARK_GRAY); - colors.put("9", TextColors.BLUE); - MUParam.colors = colors; + } catch (IllegalStateException e) { + //e.printStackTrace(); + } finally { + colors.put("a", TextColors.GREEN); + colors.put("b", TextColors.AQUA); + colors.put("c", TextColors.AQUA); + colors.put("d", TextColors.RED); + colors.put("e", TextColors.LIGHT_PURPLE); + colors.put("f", TextColors.YELLOW); + colors.put("0", TextColors.BLACK); + colors.put("1", TextColors.DARK_BLUE); + colors.put("2", TextColors.DARK_GREEN); + colors.put("3", TextColors.DARK_AQUA); + colors.put("4", TextColors.DARK_RED); + colors.put("5", TextColors.DARK_PURPLE); + colors.put("6", TextColors.GOLD); + colors.put("7", TextColors.GRAY); + colors.put("8", TextColors.DARK_GRAY); + colors.put("9", TextColors.BLUE); + MUParam.colors = colors; + } } return MUParam.colors; } @@ -70,16 +74,20 @@ static Map colors() { static Map styles() { if (MUParam.styles.isEmpty()) { Map styles = new HashMap<>(); - Sponge.getRegistry().getAllOf(TextStyle.Base.class).stream() - .filter(style -> style != TextStyles.NONE && style != TextStyles.RESET) - .forEach(style -> styles.put(style.getId().toLowerCase(), style)); - - styles.put("k", TextStyles.OBFUSCATED); - styles.put("l", TextStyles.BOLD); - styles.put("m", TextStyles.STRIKETHROUGH); - styles.put("n", TextStyles.UNDERLINE); - styles.put("o", TextStyles.ITALIC); - MUParam.styles = styles; + try { + Sponge.getRegistry().getAllOf(TextStyle.Base.class).stream() + .filter(style -> style != TextStyles.NONE && style != TextStyles.RESET) + .forEach(style -> styles.put(style.getId().toLowerCase(), style)); + } catch (IllegalStateException e) { + //e.printStackTrace(); + } finally { + styles.put("k", TextStyles.OBFUSCATED); + styles.put("l", TextStyles.BOLD); + styles.put("m", TextStyles.STRIKETHROUGH); + styles.put("n", TextStyles.UNDERLINE); + styles.put("o", TextStyles.ITALIC); + MUParam.styles = styles; + } } return MUParam.styles; } diff --git a/src/main/java/me/dags/textmu/MUParser.java b/src/main/java/me/dags/textmu/MUParser.java index 188c6b3..14d854b 100644 --- a/src/main/java/me/dags/textmu/MUParser.java +++ b/src/main/java/me/dags/textmu/MUParser.java @@ -1,201 +1,300 @@ package me.dags.textmu; +import com.sun.istack.internal.Nullable; import org.spongepowered.api.text.Text; -import org.spongepowered.api.text.serializer.TextParseException; -import java.util.ArrayList; +import javax.annotation.Nonnull; +import java.util.Arrays; import java.util.Collections; +import java.util.LinkedList; import java.util.List; -import java.util.Map; /** * @author dags */ -class MUParser { +final class MUParser { - static final Map EMPTY = Collections.EMPTY_MAP; - - private final String input; private final MarkupSpec spec; - private final Map arguments; + private final Buffer buffer; + private final String in; + + private boolean blockEscaped = false; + private boolean charEscaped = false; + private char next = (char) -1; private int pos = -1; - MUParser(MarkupSpec spec, String input) { + MUParser(MarkupSpec spec, String in) { + this.buffer = new Buffer(in.length()); this.spec = spec; - this.input = input; - this.arguments = EMPTY; + this.in = in; } - MUParser(MarkupSpec spec, String input, Map arguments) { - this.spec = spec; - this.input = input; - this.arguments = arguments.isEmpty() ? EMPTY : arguments; + Text parse() { + return nextText(true).build(); } - private boolean hasNext() { - return pos + 1 < input.length(); - } + /** + * Increment the character position until it is not on whitespace + */ + private void skipSpace() { + while (next == ' ' && next()) { - private char next() { - if (hasNext()) { - return input.charAt(++pos); } - throw new TextParseException("Unexpected end of String: " + input); } - private char peek() { - return hasNext() ? input.charAt(pos + 1) : input.charAt(pos); - } - - private boolean skip(int count) { - if (pos + count < input.length()) { - pos += count; + /** + * Checks and increments the character position in the input string + * Reads escaped blocks '`...`' and escaped chars '\\.' in one go (appending the block/char to the buffer) + */ + private boolean next() { + if (pos + 1 < in.length()) { + next = in.charAt(++pos); + if (blockEscaped) { + blockEscaped = next != '`'; + if (blockEscaped) { + buffer.append(next); + } + return next(); + } + if (charEscaped) { + charEscaped = false; + buffer.append(next); + return next(); + } + if (next == '`') { + blockEscaped = true; + return next(); + } + if (next == '\\') { + charEscaped = true; + return next(); + } return true; } - pos = input.length() - 1; return false; } - Text parse() { - MUBuilder builder = new MUBuilder(spec, arguments); - boolean quoted = false; - boolean escaped = false; - - while (hasNext()) { - char c = next(); - if (!escaped) { - if (c == '`') { - builder.setQuoted(true); - quoted = !quoted; + /** + * Reads in the next text section, splitting off an parsing any markup syntax before appending it + * If is called as the root the input string will be read to the end, otherwise the method terminates + * at the end of a markup statement on the close brace ')' + */ + @Nonnull + private Text.Builder nextText(boolean root) { + buffer.reset(); + Text.Builder builder = null; + + while (next()) { + // '[' indicates start of markup syntax + if (next == '[') { + // append any plain-text currently in the buffer + builder = appendPlainText(builder); + int start = pos; + + // parse markup syntax `[..](...)`, returns null if the syntax is invalid/incomplete + Text.Builder next = nextStatement(); + if (next != null) { + // valid syntax so append + builder = append(builder, next); continue; } - if (!quoted) { - if (c == '[') { - Text text = parseStatement(); - // avoid unnecessary nesting of texts - if (!hasNext() && builder.isEmpty()) { - return text; - } - builder.append(text); - continue; - } - if (escaped = c == '\\') { - builder.setEscaped(true); - continue; - } - } + // invalid syntax, reset position to read as plain text starting with the '[' char + pos = start; + buffer.reset(); + buffer.append('['); + continue; } - builder.append(c); - escaped = false; + + // indicates end of a syntax + if (!root && next == ')') { + break; + } + + // append char as plain-text + buffer.append(next); } - return builder.build(); + + // append any trailing plain-text + builder = appendPlainText(builder); + + // builder may still be null at this point + return builder == null ? Text.builder() : builder; } - private Text parseStatement() { - int start = pos; - int end = input.length(); - List params = parseParams(); - if (hasNext()) { - if (next() == '(') { - Text.Builder content = nextContent(); - for (MUParam param : params) { - if (param.test(spec)) { - param.apply(content); - } - } - return content.build(); + /** + * Parses the markup statement `[..](...)` + * Starts at pos 1 in the syntax. ie the char immediately after the open bracket '[' + * Ends on the close brace ')' + * returns null if syntax is incorrect/incomplete + */ + @Nullable + private Text.Builder nextStatement() { + // parse params `[..]` + List params = nextParams(); + if (!next()) { + // unexpected end of string + return null; + } + + if (next != '(') { + // invalid syntax + return null; + } + + // parse content `(...)` + Text.Builder builder = nextText(false); + if (next != ')') { + // invalid syntax + return null; + } + + // apply params to valid content + for (MUParam param : params) { + if (spec == null || param.test(spec)) { + param.apply(builder); } - end = pos; } - return Text.of(input.substring(start, end)); + + return builder; } - private List parseParams() { - List params = new ArrayList<>(); - while (hasNext()) { + /** + * Parses the list of parameters between the brackets `[..]` in the statement + */ + private List nextParams() { + List params = Collections.emptyList(); + + while (next()) { + // end of params + if (next == ']') { + break; + } + + buffer.append(next); MUParam param = nextParam(); - params.add(param); - if (peek() == ']') { - next(); + if (param != MUParam.EMPTY) { + if (params.isEmpty()) { + // lazy initialize the params list + params = new LinkedList<>(); + } + params.add(param); + } + + // end of params + if (next == ']') { break; } } + return params; } + /** + * Parse a single parameter + */ private MUParam nextParam() { - MUBuilder builder = new MUBuilder(spec, arguments); - - while (hasNext()) { - char peek = peek(); - if (!builder.isEscaped()) { - if (peek == '`') { - next(); - builder.setQuoted(!builder.isQuoted()); - continue; - } + // ignore leading whitespace + skipSpace(); - if (!builder.isQuoted()) { - if (peek == ']') { - break; - } - if (peek == ',') { - next(); - break; - } - if (peek == '[' && skip(1)) { - return MUParam.of(parseStatement()); - } - if (peek == '\\') { - builder.setEscaped(true); - next(); - continue; - } - } + // special case where param is hover text + if (next == '[') { + Text.Builder hover = nextStatement(); + if (hover == null) { + return MUParam.EMPTY; } - builder.append(next()); + return MUParam.of(hover.build()); } - if (builder.isRaw()) { - return MUParam.of(builder.string()); + // read param chars into the buffer + while (next()) { + if (next == ',' || next == ']') { + break; + } + buffer.append(next); } - // parameter is complex (may be from a template) so serialize and parse as a param - return new MUParser(spec, builder.serialized(), arguments).nextParam(); + // parse param from the buffer + return MUParam.of(buffer.drain()); } - private Text.Builder nextContent() { - MUBuilder builder = new MUBuilder(spec, arguments); - while (hasNext()) { - if (!builder.isEscaped()) { - char peek = peek(); - if (peek == '`') { - next(); - builder.setQuoted(!builder.isQuoted()); - continue; - } - - if (!builder.isQuoted()) { - if (peek == ')') { - next(); - return builder.toBuilder(); - } - if (peek == '[') { - next(); - builder.append(parseStatement()); - continue; - } - if (peek == '\\') { - builder.setEscaped(true); - next(); - continue; - } - } + /** + * Adds any plain-text stored in the buffer to the current Text.Builder + */ + private Text.Builder appendPlainText(Text.Builder builder) { + if (buffer.length() > 0) { + String plain = buffer.drain(); + if (builder == null) { + builder = Text.builder(plain); + } else { + builder.append(Text.of(plain)); } - builder.append(next()); } + return builder; + } - return builder.plain(); + /** + * Adds the 'other' Builder to the main one, lazily creating the main Builder if null + */ + private Text.Builder append(@Nullable Text.Builder builder, @Nonnull Text.Builder other) { + if (builder == null) { + // lazy create builder + builder = Text.builder(); + } + return builder.append(other.build()); + } + + private static class Buffer { + + private char[] buffer; + private int carrot = 0; + + private Buffer(int size) { + buffer = new char[size + 2]; + } + + /** + * The length of the current buffered content + */ + private int length() { + return carrot; + } + + /** + * Resets the carrot position + * Contents are overwritten as subsequent appends are called rather than creating new char array + */ + private void reset() { + carrot = 0; + } + + /** + * Append a char to buffer + */ + private Buffer append(char c) { + ensureCapacity(carrot + 1); + buffer[carrot++] = c; + return this; + } + + /** + * Retrieve the string stored in the buffer and reset the carrot to the start + */ + private String drain() { + int end = carrot; + carrot = 0; + return new String(buffer, 0, end); + } + + // expand the buffer array as necessary + + /** + * Expand the buffer array as necessary + */ + private void ensureCapacity(int size) { + if (size > buffer.length) { + buffer = Arrays.copyOf(buffer, size * 2 + 2); + } + } } } diff --git a/src/main/java/me/dags/textmu/MUTParser.java b/src/main/java/me/dags/textmu/MUTParser.java new file mode 100644 index 0000000..40e004a --- /dev/null +++ b/src/main/java/me/dags/textmu/MUTParser.java @@ -0,0 +1,125 @@ +package me.dags.textmu; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author dags + */ +class MUTParser { + + private final MarkupSpec spec; + private final String in; + + private boolean blockEscaped = false; + private boolean charEscaped = false; + private char next = (char) -1; + private int pos = -1; + + MUTParser(MarkupSpec spec, String in) { + this.spec = spec; + this.in = in; + } + + MarkupTemplate parse() { + return new MarkupTemplate(spec, in, parse(false)); + } + + private boolean next() { + if (pos + 1 < in.length()) { + next = in.charAt(++pos); + if (blockEscaped) { + blockEscaped = next != '`'; + return next(); + } + if (charEscaped) { + charEscaped = false; + return next(); + } + if (next == '`') { + blockEscaped = true; + return next(); + } + if (next == '\\') { + charEscaped = true; + return next(); + } + return true; + } + return false; + } + + private List parse(boolean inArg) { + List list = new LinkedList<>(); + + int start = pos + 1; + while (next()) { + if (next == '{') { + addPlain(list, start, pos); + addArg(list); + start = pos + 1; + continue; + } + + if (inArg && (next == '}' || next == ':')) { + addPlain(list, start, pos); + return list; + } + } + + addPlain(list, start, in.length()); + + return list; + } + + private void addArg(List list) { + int start = pos + 1; + + String key = null; + String separator = ""; + List template = null; + + while (next()) { + if (next == ':') { + key = in.substring(start, pos); + template = parse(true); + if (next == '}') { + break; + } + start = pos + 1; + } + + if (next == '}') { + if (template == null) { + key = in.substring(start, pos); + } else { + separator = in.substring(start, pos); + } + break; + } + } + + if (key == null) { + list.add(plain(start, pos)); + return; + } + + if (template == null) { + list.add(new MarkupTemplate.Arg(key)); + return; + } + + list.add(new MarkupTemplate.Templ(key, separator, template)); + } + + private void addPlain(List list, int start, int end) { + if (end > start) { + list.add(plain(start, end)); + } + } + + private MarkupTemplate.Component plain(int start, int end) { + String plain = in.substring(start, end); + return new MarkupTemplate.Plain(plain); + } +} diff --git a/src/main/java/me/dags/textmu/MarkupSpec.java b/src/main/java/me/dags/textmu/MarkupSpec.java index a8d4fe3..12f3948 100644 --- a/src/main/java/me/dags/textmu/MarkupSpec.java +++ b/src/main/java/me/dags/textmu/MarkupSpec.java @@ -53,7 +53,7 @@ private MarkupSpec(Builder builder) { } public MarkupTemplate template(String input) { - return new MarkupTemplate(this, input); + return new MUTParser(this, input).parse(); } /** @@ -198,6 +198,16 @@ public static MarkupSpec.Builder builder() { return new Builder(); } + @Override + public String getId() { + return "textmu"; + } + + @Override + public String getName() { + return "TextMU"; + } + public static class Builder { private final Set colors = new HashSet<>(); diff --git a/src/main/java/me/dags/textmu/MarkupTemplate.java b/src/main/java/me/dags/textmu/MarkupTemplate.java index c498770..4d32783 100644 --- a/src/main/java/me/dags/textmu/MarkupTemplate.java +++ b/src/main/java/me/dags/textmu/MarkupTemplate.java @@ -1,16 +1,11 @@ package me.dags.textmu; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.objectmapping.ObjectMappingException; import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers; -import org.spongepowered.api.event.cause.Cause; -import org.spongepowered.api.service.context.Context; -import org.spongepowered.api.service.permission.Subject; -import org.spongepowered.api.service.permission.SubjectData; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.TextElement; import org.spongepowered.api.text.TextRepresentable; @@ -18,9 +13,11 @@ import org.spongepowered.api.text.transform.SimpleTextFormatter; import org.spongepowered.api.text.transform.SimpleTextTemplateApplier; +import java.lang.reflect.Array; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; -import java.util.Set; /** * @author dags @@ -28,161 +25,75 @@ public final class MarkupTemplate { public static final TypeToken TYPE_TOKEN = TypeToken.of(MarkupTemplate.class); - public static final TypeSerializer TYPE_SERIALIZER = new Serializer(); + public static final TypeSerializer TYPE_SERIALIZER = new MarkupTemplate.Serializer(); static { TypeSerializers.getDefaultSerializers().registerType(TYPE_TOKEN, TYPE_SERIALIZER); } + private final String raw; private final MarkupSpec spec; - private final String template; + private final List list; - MarkupTemplate(MarkupSpec spec, String template) { + public MarkupTemplate(MarkupSpec spec, String raw, List list) { + this.raw = raw; this.spec = spec; - this.template = template; + this.list = list; } - /** - * Create a new TemplateApplier for this MarkupTemplate - * - * @return The newly created Applier - */ - public Applier applier() { - return new Applier(this); - } - - /** - * Create a new TemplateApplier with the value reference '{.}' - * - * @param value The value assigned to '{.}' - * @return The newly created Applier - */ - public Applier with(Object value) { - return applier().with(value); - } - - /** - * Create a new TemplateApplier with the key/value pair referenced '{.key}' & '{.value}' - * - * @param entry The key/value pair assigned to '{.key}' & '{.value}' - * @return The newly created Applier - */ - public Applier with(Map.Entry entry) { - return applier().with(entry); - } - - /** - * Create a new TemplateApplier with the given key/value pair - * - * @param key The named variable used in the template - * @param value The value that this variable should be assigned - * @return The newly created Applier - */ public Applier with(String key, Object value) { return applier().with(key, value); } - /** - * Create a new TemplateApplier with the given map of key/value pairs - * - * @param map The named variable used in the template - * @return The newly created Applier - */ - public Applier with(Map map) { + public Applier with(Map map) { return applier().with(map); } - /** - * Create a new TemplateApplier with the Permission Options from the given Subject and Contents - * - * @param subject The Subject whose permission options will be used - * @param contexts The Contexts used to determine which Options are applicable - * @return The new created Applier - */ - public Applier withOptions(Subject subject, Set contexts) { - return applier().withOptions(subject, contexts); - } - - /** - * Create a new TemplateApplier with the NamedCauses from the given Cause object - * - * @param cause The Cause containing the NamedCauses to be used - * @return The current Applied - */ - public Applier withCause(Cause cause) { - return applier().withCause(cause); - } - - @Override - public String toString() { - return template; - } - - Text renderTemplate(Map args) { - return new MUParser(spec, template, args).parse(); + public Applier with(SimpleTextFormatter partition) { + return applier().with(partition); } - private static void validKey(String key) { - Preconditions.checkNotNull(key); - - if (key.isEmpty() || !key.matches("^[a-zA-Z0-9_]*$")) { - String error = String.format("Key: '%s' is a reserved key name!", key); - throw new UnsupportedOperationException(error); - } + public Applier applier() { + return new Applier(); } public class Applier extends SimpleTextTemplateApplier { - private final MarkupTemplate template; private final Map arguments = new HashMap<>(); + private final StringBuilder sb = new StringBuilder(); + + Applier() { - Applier(MarkupTemplate template) { - this.template = template; } - /** - * @return Any TextElement objects stored in this Applier. Note that this Applier may hold other object types - * that will not be returned in the resulting map - */ @Override public ImmutableMap getParameters() { - ImmutableMap.Builder map = ImmutableMap.builder(); + ImmutableMap.Builder builder = ImmutableMap.builder(); arguments.entrySet().stream() .filter(entry -> TextElement.class.isInstance(entry.getValue())) - .forEach(entry -> map.put(entry.getKey(), TextElement.class.cast(entry.getValue()))); - return map.build(); + .forEach(entry -> builder.put(entry.getKey(), TextElement.class.cast(entry.getValue()))); + return builder.build(); } - /** - * As base method but applies Markup rendering to any TextRepresentables - * @param key The argument name - * @param value The value for the argument - */ @Override public void setParameter(String key, TextElement value) { - validKey(key); - if (value instanceof TextRepresentable) { String md = spec.writeUnescaped((TextRepresentable) value); value = spec.render(md); } - this.arguments.put(key, value); + arguments.put(key, value); } - /** - * @return Always returns an empty template - */ @Override public TextTemplate getTemplate() { return TextTemplate.EMPTY; } - /** - * @param template Does not store a TextTemplate, value is always ignored - */ @Override - public void setTemplate(TextTemplate template) {} + public void setTemplate(TextTemplate template) { + + } @Override public Text toText() { @@ -190,89 +101,170 @@ public Text toText() { } public Text render() { - return template.renderTemplate(arguments); + return spec.render(apply()); } - public Applier with(Object object) { - arguments.put(".", object); - return this; + public String apply() { + for (Component c : list) { + c.apply(this); + } + return sb.toString(); } - public Applier with(Map.Entry entry) { - arguments.put(".key", entry.getKey()); - arguments.put(".value", entry.getValue()); + public Applier with(String key, Object value) { + arguments.put(key, value); return this; } - /** - * Provide a key/value pair to be used by the template - * - * @param key The named variable used in the template - * @param value The value that this variable should be assigned - * @return The current Applier - */ - public Applier with(String key, Object value) { - validKey(key); - this.arguments.put(key, value); + public Applier with(Map map) { + for (Object o : map.entrySet()) { + Map.Entry entry = (Map.Entry) o; + with(entry.getKey().toString(), entry.getValue()); + } return this; } - /** - * Provide a map of key/value pairs to be used by the template - * - * @param map A map of key/values - * @return The current Applier - */ - public Applier with(Map map) { - for (Map.Entry entry : map.entrySet()) { - validKey(entry.getKey().toString()); - arguments.put(entry.getKey().toString(), entry.getValue()); + public Applier with(SimpleTextFormatter partition) { + for (SimpleTextTemplateApplier applier : partition.getAll()) { + for (Map.Entry e : applier.getParameters().entrySet()) { + setParameter(e.getKey(), e.getValue()); + } } return this; } - /** - * Provide a Subject whose Permission Options should be used by the template - * - * @param subject The Subject whose Permission Options will be used - * @param contexts The Contexts used to determine which Options are applicable - * @return The current Applier - */ - public Applier withOptions(Subject subject, Set contexts) { - SubjectData data = subject.getSubjectData(); - return with(data.getOptions(contexts)); + private void drop(String key) { + arguments.remove(key); } - /** - * Provide a Cause whose NamedCauses should be used by the template - * - * @param cause The Cause containing the NamedCauses to be used - * @return The current Applied - */ - public Applier withCause(Cause cause) { - return with(cause.getNamedCauses()); + private void render(Object o) { + sb.append(o); } - /** - * Inherit arguments from a TextFormatter partition - * - * @param partition The partition to inherit arguments from - * @return The current Applier - */ - public Applier inherit(SimpleTextFormatter partition) { - for (SimpleTextTemplateApplier applier : partition.getAll()) { - for (Map.Entry e : applier.getParameters().entrySet()) { - setParameter(e.getKey(), e.getValue()); + private Object get(String key) { + return arguments.get(key); + } + } + + interface Component { + + void apply(Applier applier); + } + + static class Plain implements Component { + + private final String text; + + Plain(String text) {this.text = text;} + + @Override + public void apply(Applier applier) { + applier.render(text); + } + } + + static class Arg implements Component { + + private final String key; + + Arg(String key) {this.key = key;} + + @Override + public void apply(Applier applier) { + Object value = applier.get(key); + if (value != null) { + applier.render(value); + } + } + } + + static class Templ implements Component { + + private final String key; + private final String separator; + private final List components; + + Templ(String key, String separator, List components) { + this.key = key; + this.separator = separator; + this.components = components; + } + + @Override + public void apply(Applier applier) { + if (key.isEmpty()) { + for (Component comp : components) { + comp.apply(applier); + } + } else { + Object o = applier.get(key); + if (o == null) { + return; + } + + if (o.getClass() == MarkupTemplate.class) { + applyTemplate(applier, o); + } else if (o instanceof Iterable) { + applyIterable(applier, o); + } else if (o instanceof Map) { + applyMap(applier, o); + } else if (o.getClass().isArray()) { + applyArray(applier, o); + } else { + apply(applier, components); } } - return this; } - Applier withUnchecked(Map map) { - for (Map.Entry entry : map.entrySet()) { - arguments.put(entry.getKey().toString(), entry.getValue()); + private void applyTemplate(Applier applier, Object o) { + MarkupTemplate template = (MarkupTemplate) o; + String text = template.with(applier.arguments).apply(); + applier.render(text); + } + + private void applyIterable(Applier applier, Object o) { + Iterator iterator = ((Iterable) o).iterator(); + while (iterator.hasNext()) { + applier.with(".", iterator.next()); + apply(applier, components); + if (iterator.hasNext()) { + applier.render(separator); + } + } + applier.drop("."); + } + + private void applyArray(Applier applier, Object o) { + for (int i = 0, len = Array.getLength(o); i < len; i++) { + Object e = Array.get(o, i); + applier.with(".", e); + apply(applier, components); + if (i + 1 < len) { + applier.render(separator); + } + } + applier.drop("."); + } + + private void applyMap(Applier applier, Object o) { + Iterator iterator = ((Map) o).entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + applier.with(".key", entry.getKey()); + applier.with(".value", entry.getValue()); + apply(applier, components); + if (iterator.hasNext()) { + applier.render(separator); + } + } + applier.drop(".key"); + applier.drop(".value"); + } + + private void apply(Applier applier, List list) { + for (Component comp : list) { + comp.apply(applier); } - return this; } } @@ -281,14 +273,14 @@ private static class Serializer implements TypeSerializer { @Override public MarkupTemplate deserialize(TypeToken type, ConfigurationNode node) throws ObjectMappingException { MarkupSpec spec = node.getValue(MarkupSpec.TYPE_TOKEN); - String template = node.getNode("template").getString(""); + String template = node.getNode("components").getString(""); return spec.template(template); } @Override public void serialize(TypeToken type, MarkupTemplate template, ConfigurationNode node) throws ObjectMappingException { node.setValue(MarkupSpec.TYPE_TOKEN, template.spec); - node.getNode("template").setValue(template.template); + node.getNode("components").setValue(template.raw); } } } diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index afcd7b9..5714c51 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -3,9 +3,9 @@ "modid": "textmu", "name": "TextMU", "version": "@version@", - "description": "A markup language for SpongeAPI", + "description": "A markup syntax for SpongeAPI", "requiredMods": [ - "spongeapi@5.1.0-SNAPSHOT" + "spongeapi@5.2.0-SNAPSHOT" ] } ]