|
1 | 1 | package org.codejive.twinkle.fluent.impl; |
2 | 2 |
|
| 3 | +import java.io.IOException; |
3 | 4 | import java.util.HashMap; |
4 | 5 | import java.util.Map; |
| 6 | +import java.util.function.Consumer; |
5 | 7 | import java.util.regex.Matcher; |
6 | 8 | import java.util.regex.Pattern; |
7 | 9 | import org.codejive.twinkle.ansi.Color; |
|
20 | 22 | public class DefaultMarkupParser implements MarkupParser { |
21 | 23 | private static Map<String, Color.BasicColor> colors; |
22 | 24 |
|
23 | | - private static final Pattern markupPattern = Pattern.compile("(?<!\\{)\\{([^{}]*)}"); |
| 25 | + private static final Pattern markupPattern = Pattern.compile("(?<!\\{)\\{([^{}]+)}"); |
| 26 | + private static final Pattern varPattern = Pattern.compile("(?<!\\$)\\$(/?\\d+)"); |
24 | 27 |
|
25 | 28 | @Override |
26 | | - public void parse(Fluent fluent, String textWithMarkup) { |
| 29 | + public void parse(Fluent fluent, String textWithMarkup, Object... args) { |
27 | 30 | // Call handleMarkup() for each markup pattern found in the text |
28 | | - int lastIndex = 0; |
29 | | - Matcher matcher = markupPattern.matcher(textWithMarkup); |
30 | | - while (matcher.find()) { |
31 | | - // Append text before the markup |
32 | | - if (matcher.start() > lastIndex) { |
33 | | - append(fluent, textWithMarkup.substring(lastIndex, matcher.start())); |
34 | | - } |
35 | | - // Handle the markup content |
36 | | - String markupContent = matcher.group(1); |
37 | | - handleMarkup(fluent, markupContent); |
38 | | - lastIndex = matcher.end(); |
39 | | - } |
40 | | - // Append any remaining text after the last markup |
41 | | - if (lastIndex < textWithMarkup.length()) { |
42 | | - append(fluent, textWithMarkup.substring(lastIndex, textWithMarkup.length())); |
43 | | - } |
| 31 | + applyPattern( |
| 32 | + fluent::plain, |
| 33 | + markupPattern, |
| 34 | + "{", |
| 35 | + textWithMarkup, |
| 36 | + markup -> handleMarkup(fluent, markup, args)); |
44 | 37 | } |
45 | 38 |
|
46 | | - protected void handleMarkup(Fluent fluent, String markup) { |
| 39 | + protected void handleMarkup(Fluent fluent, String markup, Object... args) { |
| 40 | + markup = applySubstitutions(markup, args); |
| 41 | + |
47 | 42 | if (tryStyles(fluent, markup)) { |
48 | 43 | return; |
49 | 44 | } |
@@ -89,6 +84,48 @@ protected void handleMarkup(Fluent fluent, String markup) { |
89 | 84 | } |
90 | 85 | } |
91 | 86 |
|
| 87 | + private String applySubstitutions(String markup, Object... args) { |
| 88 | + if (markup.contains("$")) { |
| 89 | + StringBuilder sb = new StringBuilder(); |
| 90 | + applyPattern(sb::append, varPattern, "$", markup, vn -> applyVar(sb, vn, args)); |
| 91 | + return sb.toString(); |
| 92 | + } |
| 93 | + return markup; |
| 94 | + } |
| 95 | + |
| 96 | + protected void applyVar(Appendable appendable, String varName, Object... args) { |
| 97 | + try { |
| 98 | + if (varName.startsWith("/")) { |
| 99 | + appendable.append("/").append(getVar(varName.substring(1), args)); |
| 100 | + } else { |
| 101 | + appendable.append(getVar(varName, args)); |
| 102 | + } |
| 103 | + } catch (IOException e) { |
| 104 | + // Ignore |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + protected String getVar(String varName, Object... args) { |
| 109 | + try { |
| 110 | + int num = Integer.parseInt(varName) - 1; |
| 111 | + if (num >= args.length) { |
| 112 | + throw new IndexOutOfBoundsException( |
| 113 | + "Variable substitution index out of bounds: " |
| 114 | + + num |
| 115 | + + " is larger than the number of provided arguments: " |
| 116 | + + args.length); |
| 117 | + } |
| 118 | + if (num < 0) { |
| 119 | + throw new IndexOutOfBoundsException( |
| 120 | + "Variable substitution index must be 1 or higher"); |
| 121 | + } |
| 122 | + return String.valueOf(args[num]); |
| 123 | + } catch (NumberFormatException e) { |
| 124 | + // Ignore |
| 125 | + } |
| 126 | + return ""; |
| 127 | + } |
| 128 | + |
92 | 129 | private boolean tryStyles(NegatableCommands fluentNeg, String markup) { |
93 | 130 | switch (markup.toLowerCase()) { |
94 | 131 | case "bold": |
@@ -281,11 +318,31 @@ private Color tryColorByName(String markup) { |
281 | 318 | return colors.get(lmarkup); |
282 | 319 | } |
283 | 320 |
|
284 | | - protected void append(Fluent fluent, String text) { |
285 | | - try { |
286 | | - fluent.plain(text); |
287 | | - } catch (Exception e) { |
288 | | - // We simply ignore errors |
| 321 | + protected void applyPattern( |
| 322 | + Consumer<String> appendable, |
| 323 | + Pattern pattern, |
| 324 | + String token, |
| 325 | + String textWithMarkup, |
| 326 | + Consumer<String> handler) { |
| 327 | + int lastIndex = 0; |
| 328 | + Matcher matcher = pattern.matcher(textWithMarkup); |
| 329 | + while (matcher.find()) { |
| 330 | + // Append text before the markup |
| 331 | + if (matcher.start() > lastIndex) { |
| 332 | + String txt = textWithMarkup.substring(lastIndex, matcher.start()); |
| 333 | + txt = txt.replace(token + token, token); |
| 334 | + appendable.accept(txt); |
| 335 | + } |
| 336 | + // Handle the markup content |
| 337 | + String markupContent = matcher.group(1); |
| 338 | + handler.accept(markupContent); |
| 339 | + lastIndex = matcher.end(); |
| 340 | + } |
| 341 | + // Append any remaining text after the last markup |
| 342 | + if (lastIndex < textWithMarkup.length()) { |
| 343 | + String txt = textWithMarkup.substring(lastIndex); |
| 344 | + txt = txt.replace(token + token, token); |
| 345 | + appendable.accept(txt); |
289 | 346 | } |
290 | 347 | } |
291 | 348 | } |
0 commit comments