Skip to content

Commit 97b429d

Browse files
committed
fix: some more minor markup fixes and refactorings
1 parent 98bd50b commit 97b429d

6 files changed

Lines changed: 110 additions & 29 deletions

File tree

examples/src/main/java/org/codejive/twinkle/demos/BouncingTwinkleDemo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static void main(String[] args) throws Exception {
103103
Fluent f = writer.fluent();
104104
f.at(2, 0).markup("{green}[ {white}%s{green} ]", size);
105105
f.at(size.width() / 2 - 3, 0)
106-
.markup("{green}[ {blue}{ul}{%s}Twinkle{/}{/ul}{green} ]", URL);
106+
.markup("{green}[ {blue}{ul}{$1}Twinkle{/}{/ul}{green} ]", URL);
107107
f.at(size.width() - 12, 0)
108108
.markup("{green}[ {+}{white}fps %s{-} ]", Math.round(fps.average()));
109109
f.at(textX, textY).color(textColor).text(text).done();

twinkle-text/src/main/java/org/codejive/twinkle/fluent/MarkupParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
/** An interface for possible markup parser implementations that can be used for the `Fluent` */
44
public interface MarkupParser {
55

6-
void parse(Fluent fluent, String textWithMarkup);
6+
void parse(Fluent fluent, String textWithMarkup, Object... args);
77
}

twinkle-text/src/main/java/org/codejive/twinkle/fluent/impl/DefaultMarkupParser.java

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.codejive.twinkle.fluent.impl;
22

3+
import java.io.IOException;
34
import java.util.HashMap;
45
import java.util.Map;
6+
import java.util.function.Consumer;
57
import java.util.regex.Matcher;
68
import java.util.regex.Pattern;
79
import org.codejive.twinkle.ansi.Color;
@@ -20,30 +22,23 @@
2022
public class DefaultMarkupParser implements MarkupParser {
2123
private static Map<String, Color.BasicColor> colors;
2224

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+)");
2427

2528
@Override
26-
public void parse(Fluent fluent, String textWithMarkup) {
29+
public void parse(Fluent fluent, String textWithMarkup, Object... args) {
2730
// 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));
4437
}
4538

46-
protected void handleMarkup(Fluent fluent, String markup) {
39+
protected void handleMarkup(Fluent fluent, String markup, Object... args) {
40+
markup = applySubstitutions(markup, args);
41+
4742
if (tryStyles(fluent, markup)) {
4843
return;
4944
}
@@ -89,6 +84,48 @@ protected void handleMarkup(Fluent fluent, String markup) {
8984
}
9085
}
9186

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+
92129
private boolean tryStyles(NegatableCommands fluentNeg, String markup) {
93130
switch (markup.toLowerCase()) {
94131
case "bold":
@@ -281,11 +318,31 @@ private Color tryColorByName(String markup) {
281318
return colors.get(lmarkup);
282319
}
283320

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);
289346
}
290347
}
291348
}

twinkle-text/src/main/java/org/codejive/twinkle/fluent/impl/FluentImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public FluentImpl plain(@NonNull String text) {
109109
public Fluent markup(@NonNull Object obj, Object... args) {
110110
StringBuilder sb = new StringBuilder();
111111
FluentImpl f = new FluentImpl(sb, currentStyle);
112-
f.markupParser.parse(f, obj.toString());
112+
f.markupParser.parse(f, obj.toString(), args);
113113

114114
Formatter fmt = new Formatter(appendable);
115115
fmt.format(sb.toString(), args);

twinkle-text/src/test/java/org/codejive/twinkle/fluent/TestDefaultMarkupParser.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ private static class Setup {
2828
final FluentImpl fluent = FluentImpl.of(sb, Style.DEFAULT);
2929
final DefaultMarkupParser markup = new DefaultMarkupParser();
3030

31-
void parse(String text) {
32-
markup.parse(fluent, text);
31+
void parse(String text, Object... args) {
32+
markup.parse(fluent, text, args);
3333
}
3434

3535
String result() {
@@ -441,4 +441,11 @@ public void testTextWithFormatting() {
441441
s.parse("{i}%s{/i}");
442442
assertThat(s.result()).isEqualTo(Ansi.italic() + "%s" + Ansi.italicOff());
443443
}
444+
445+
@Test
446+
public void testTextWithArgumentInMarkup() {
447+
Setup s = new Setup();
448+
s.parse("{i}{$1}{/i}", "bold");
449+
assertThat(s.result()).isEqualTo(Ansi.italic() + Ansi.bold() + Ansi.italicOff());
450+
}
444451
}

twinkle-text/src/test/java/org/codejive/twinkle/fluent/TestFluent.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,23 @@ public void testFormatMarkupOrdering() {
570570
assertThat(stringFluent.toString()).isEqualTo("");
571571
}
572572

573+
@Test
574+
public void testTextWithFormattingInMarkup() {
575+
Fluent stringFluent = Fluent.string();
576+
// Markup is invalid so will be ignored (removed)
577+
stringFluent.markup("{i}{%s}{/i}", "bold");
578+
assertThat(stringFluent.toString()).isEqualTo(Ansi.italic() + Ansi.italicOff());
579+
}
580+
581+
@Test
582+
public void testTextWithArgumentInMarkup() {
583+
Fluent stringFluent = Fluent.string();
584+
// Markup is invalid so will be ignored (removed)
585+
stringFluent.markup("{i}{$1}{/i}", "bold");
586+
assertThat(stringFluent.toString())
587+
.isEqualTo(Ansi.italic() + Ansi.bold() + Ansi.italicOff());
588+
}
589+
573590
@Test
574591
public void testMarkupAppendable() {
575592
RecordingAppendable recapp = new RecordingAppendable();

0 commit comments

Comments
 (0)