Skip to content

Commit deb550e

Browse files
committed
fix: minor Markup fixes and refactorings
1 parent 398e1bd commit deb550e

8 files changed

Lines changed: 134 additions & 85 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ static Fluent of(Appendable appendable, Style startingStyle) {
5858
* Sets the Markup parser to use for parsing markup patterns in text. This is useful for
5959
* allowing users to define their own custom markup syntax.
6060
*
61-
* @param markup the Markup parser to use
61+
* @param markupParser the Markup parser to use
6262
* @return this Fluent instance for chaining
6363
*/
64-
Fluent markupParser(Markup markup);
64+
Fluent markupParser(MarkupParser markupParser);
6565

6666
/**
6767
* Returns the current style combining the styles that have been applied since the creation of

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

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.codejive.twinkle.fluent;
2+
3+
/** An interface for possible markup parser implementations that can be used for the `Fluent` */
4+
public interface MarkupParser {
5+
6+
void parse(Fluent fluent, String textWithMarkup);
7+
}

twinkle-text/src/main/java/org/codejive/twinkle/fluent/commands/TextualCommands.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ default Fluent text(@NonNull Printable prt) {
2727
*/
2828
Fluent text(@NonNull Object obj, Object... args);
2929

30+
/**
31+
* Writes the given string
32+
*
33+
* @param text the text to output
34+
* @return this Fluent instance for chaining
35+
*/
36+
Fluent plain(@NonNull String text);
37+
3038
Fluent markup(@NonNull Object obj, Object... args);
3139

3240
/**

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

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import java.util.regex.Pattern;
77
import org.codejive.twinkle.ansi.Color;
88
import org.codejive.twinkle.ansi.Hyperlink;
9-
import org.codejive.twinkle.fluent.Markup;
9+
import org.codejive.twinkle.fluent.Fluent;
10+
import org.codejive.twinkle.fluent.MarkupParser;
1011
import org.codejive.twinkle.fluent.commands.ColorCommands;
1112
import org.codejive.twinkle.fluent.commands.NegatableCommands;
1213

@@ -16,46 +17,40 @@
1617
* like `{red}` applying a red color. While others can have parameters, like `{~10,5}` moving the
1718
* cursor to column 10 and row 5.
1819
*/
19-
public class DefaultMarkup implements Markup {
20-
protected final FluentImpl fluent;
21-
20+
public class DefaultMarkupParser implements MarkupParser {
2221
private static Map<String, Color.BasicColor> colors;
2322

2423
private static final Pattern markupPattern = Pattern.compile("(?<!\\{)\\{([^{}]*)}");
2524

26-
public DefaultMarkup(FluentImpl fluent) {
27-
this.fluent = fluent;
28-
}
29-
3025
@Override
31-
public void parse(Appendable appendable, String textWithMarkup) {
26+
public void parse(Fluent fluent, String textWithMarkup) {
3227
// Call handleMarkup() for each markup pattern found in the text
3328
int lastIndex = 0;
3429
Matcher matcher = markupPattern.matcher(textWithMarkup);
3530
while (matcher.find()) {
3631
// Append text before the markup
3732
if (matcher.start() > lastIndex) {
38-
append(appendable, textWithMarkup.substring(lastIndex, matcher.start()));
33+
append(fluent, textWithMarkup.substring(lastIndex, matcher.start()));
3934
}
4035
// Handle the markup content
4136
String markupContent = matcher.group(1);
42-
handleMarkup(appendable, markupContent);
37+
handleMarkup(fluent, markupContent);
4338
lastIndex = matcher.end();
4439
}
4540
// Append any remaining text after the last markup
4641
if (lastIndex < textWithMarkup.length()) {
47-
append(appendable, textWithMarkup.substring(lastIndex, textWithMarkup.length()));
42+
append(fluent, textWithMarkup.substring(lastIndex, textWithMarkup.length()));
4843
}
4944
}
5045

51-
protected void handleMarkup(Appendable appendable, String markup) {
52-
if (tryStyles(markup, fluent)) {
46+
protected void handleMarkup(Fluent fluent, String markup) {
47+
if (tryStyles(fluent, markup)) {
5348
return;
5449
}
5550

5651
if (markup.startsWith("/") && markup.length() > 1) {
5752
String closeMarkup = markup.substring(1);
58-
if (tryStyles(closeMarkup, fluent.not())) {
53+
if (tryStyles(fluent.not(), closeMarkup)) {
5954
return;
6055
}
6156
}
@@ -81,26 +76,20 @@ protected void handleMarkup(Appendable appendable, String markup) {
8176
return;
8277
}
8378

84-
if (tryPosition(markup)) {
79+
if (tryPosition(fluent, markup)) {
8580
return;
8681
}
8782

88-
Hyperlink link = tryHyperlink(markup);
89-
if (link != null) {
90-
if (link == Hyperlink.END) {
91-
fluent.lru();
92-
} else {
93-
fluent.url(link.url, link.id);
94-
}
83+
if (tryHyperlink(fluent, markup)) {
9584
return;
9685
}
9786

98-
if (tryColors(markup)) {
87+
if (tryColors(fluent, markup)) {
9988
return;
10089
}
10190
}
10291

103-
private boolean tryStyles(String markup, NegatableCommands fluentNeg) {
92+
private boolean tryStyles(NegatableCommands fluentNeg, String markup) {
10493
switch (markup.toLowerCase()) {
10594
case "bold":
10695
case "b":
@@ -135,7 +124,7 @@ private boolean tryStyles(String markup, NegatableCommands fluentNeg) {
135124
return false;
136125
}
137126

138-
private boolean tryPosition(String markup) {
127+
private boolean tryPosition(Fluent fluent, String markup) {
139128
switch (markup.toLowerCase()) {
140129
case "mark":
141130
case "@":
@@ -217,24 +206,27 @@ private int parsePos(String pos, int max) throws NumberFormatException {
217206
return Integer.parseInt(pos.trim());
218207
}
219208

220-
private Hyperlink tryHyperlink(String markup) {
209+
private boolean tryHyperlink(Fluent fluent, String markup) {
221210
if (markup.equals("/")) {
222-
return Hyperlink.END;
211+
fluent.lru();
212+
return true;
223213
} else if (markup.startsWith("http://") || markup.startsWith("https://")) {
224-
return Hyperlink.of(markup);
214+
Hyperlink link = Hyperlink.of(markup);
215+
fluent.url(link.url, link.id);
216+
return true;
225217
}
226-
return null;
218+
return false;
227219
}
228220

229-
private boolean tryColors(String markup) {
221+
private boolean tryColors(Fluent fluent, String markup) {
230222
if (markup.startsWith("bg:")) {
231-
return tryColors(markup.substring(3), fluent.bg());
223+
return tryColorsFgBg(fluent.bg(), markup.substring(3));
232224
} else {
233-
return tryColors(markup, fluent);
225+
return tryColorsFgBg(fluent, markup);
234226
}
235227
}
236228

237-
private boolean tryColors(String markup, ColorCommands fluentColors) {
229+
private boolean tryColorsFgBg(ColorCommands fluentColors, String markup) {
238230
// Try #RRGGBB format first
239231
Color color = tryRgbColor(markup);
240232
if (color == null) {
@@ -289,9 +281,9 @@ private Color tryColorByName(String markup) {
289281
return colors.get(lmarkup);
290282
}
291283

292-
protected void append(Appendable appendable, String text) {
284+
protected void append(Fluent fluent, String text) {
293285
try {
294-
appendable.append(text);
286+
fluent.plain(text);
295287
} catch (Exception e) {
296288
// We simply ignore errors
297289
}

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import org.codejive.twinkle.ansi.util.AnsiTricks;
1414
import org.codejive.twinkle.ansi.util.Printable;
1515
import org.codejive.twinkle.fluent.Fluent;
16-
import org.codejive.twinkle.fluent.Markup;
16+
import org.codejive.twinkle.fluent.MarkupParser;
1717
import org.codejive.twinkle.fluent.commands.ColorCommands;
1818
import org.codejive.twinkle.fluent.commands.LineCommands;
1919
import org.codejive.twinkle.fluent.commands.NegatableCommands;
@@ -33,7 +33,7 @@ public class FluentImpl implements Fluent {
3333
protected final Style startingStyle;
3434
protected Style currentStyle;
3535
protected Deque<Style> styleStack;
36-
protected Markup markup;
36+
protected MarkupParser markupParser;
3737

3838
public static FluentImpl of(Appendable appendable, Style startingStyle) {
3939
return new FluentImpl(appendable, startingStyle);
@@ -48,12 +48,12 @@ protected FluentImpl(Appendable appendable, Style startingStyle) {
4848
this.startingStyle = startingStyle;
4949
this.currentStyle = startingStyle;
5050
this.styleStack = new ArrayDeque<>();
51-
this.markup = new DefaultMarkup(this);
51+
this.markupParser = new DefaultMarkupParser();
5252
}
5353

5454
@Override
55-
public Fluent markupParser(Markup markup) {
56-
this.markup = markup;
55+
public Fluent markupParser(MarkupParser markupParser) {
56+
this.markupParser = markupParser;
5757
return this;
5858
}
5959

@@ -100,10 +100,19 @@ public FluentImpl text(@NonNull Object obj, Object... args) {
100100
return append(String.format(String.valueOf(obj), args));
101101
}
102102

103+
@Override
104+
public FluentImpl plain(@NonNull String text) {
105+
return append(text);
106+
}
107+
103108
@Override
104109
public Fluent markup(@NonNull Object obj, Object... args) {
110+
StringBuilder sb = new StringBuilder();
111+
FluentImpl f = new FluentImpl(sb, currentStyle);
112+
f.markupParser.parse(f, obj.toString());
113+
105114
Formatter fmt = new Formatter(appendable);
106-
fmt.format(markup.parse(obj.toString()), args);
115+
fmt.format(sb.toString(), args);
107116
return this;
108117
}
109118

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

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
import org.codejive.twinkle.ansi.Ansi;
66
import org.codejive.twinkle.ansi.Color;
77
import org.codejive.twinkle.ansi.Style;
8-
import org.codejive.twinkle.fluent.impl.DefaultMarkup;
8+
import org.codejive.twinkle.fluent.impl.DefaultMarkupParser;
99
import org.codejive.twinkle.fluent.impl.FluentImpl;
1010
import org.junit.jupiter.api.Test;
1111

1212
/**
13-
* Tests for {@link DefaultMarkup}.
13+
* Tests for {@link DefaultMarkupParser}.
1414
*
1515
* <p>Each test creates a {@link FluentImpl} backed by a {@link StringBuilder} and passes it to a
16-
* {@link DefaultMarkup} instance. The same {@link StringBuilder} is used as the target appendable
17-
* for {@code parse()}, so plain text and ANSI escape sequences land in the same buffer in the
18-
* correct order.
16+
* {@link DefaultMarkupParser} instance. The same {@link StringBuilder} is used as the target
17+
* appendable for {@code parse()}, so plain text and ANSI escape sequences land in the same buffer
18+
* in the correct order.
1919
*/
20-
public class TestDefaultMarkup {
20+
public class TestDefaultMarkupParser {
2121

2222
/**
2323
* Creates a fresh FluentImpl + DefaultMarkup pair that both write to the returned
@@ -26,10 +26,10 @@ public class TestDefaultMarkup {
2626
private static class Setup {
2727
final StringBuilder sb = new StringBuilder();
2828
final FluentImpl fluent = FluentImpl.of(sb, Style.DEFAULT);
29-
final DefaultMarkup markup = new DefaultMarkup(fluent);
29+
final DefaultMarkupParser markup = new DefaultMarkupParser();
3030

3131
void parse(String text) {
32-
markup.parse(sb, text);
32+
markup.parse(fluent, text);
3333
}
3434

3535
String result() {
@@ -193,10 +193,6 @@ public void testItalicOffMarkup() {
193193
assertThat(s.result()).isEqualTo(Ansi.italic() + "on" + Ansi.italicOff() + "off");
194194
}
195195

196-
// -------------------------------------------------------------------------
197-
// Color markup
198-
// -------------------------------------------------------------------------
199-
200196
@Test
201197
public void testRedColorMarkup() {
202198
Setup s = new Setup();
@@ -273,6 +269,21 @@ public void testMultipleMarkupInOneString() {
273269

274270
@Test
275271
public void testMultipleMarkupInOneString2() {
272+
Setup s = new Setup();
273+
s.parse("A{brightmagenta}B{white}C{brightmagenta}D");
274+
assertThat(s.result())
275+
.isEqualTo(
276+
"A"
277+
+ Color.BasicColor.MAGENTA.bright().toAnsiFg()
278+
+ "B"
279+
+ Color.BasicColor.WHITE.toAnsiFg()
280+
+ "C"
281+
+ Color.BasicColor.MAGENTA.bright().toAnsiFg()
282+
+ "D");
283+
}
284+
285+
@Test
286+
public void testMarkupPushPopInOneString() {
276287
Setup s = new Setup();
277288
s.parse("A{brightmagenta}{+}B{white}C{-}D");
278289
assertThat(s.result())
@@ -313,10 +324,6 @@ public void testOverlappingCloseTags() {
313324
+ Ansi.italicOff());
314325
}
315326

316-
// -------------------------------------------------------------------------
317-
// Cursor position markup
318-
// -------------------------------------------------------------------------
319-
320327
@Test
321328
public void testPositionMarkup() {
322329
Setup s = new Setup();
@@ -373,10 +380,6 @@ public void testJumpCursorCaretShorthand() {
373380
assertThat(s.result()).isEqualTo(Ansi.cursorRestore());
374381
}
375382

376-
// -------------------------------------------------------------------------
377-
// Hyperlink markup
378-
// -------------------------------------------------------------------------
379-
380383
@Test
381384
public void testHyperlinkMarkup() {
382385
Setup s = new Setup();
@@ -392,10 +395,6 @@ public void testHyperlinkEndOnlyMarkup() {
392395
assertThat(s.result()).isEqualTo(Ansi.linkEnd());
393396
}
394397

395-
// -------------------------------------------------------------------------
396-
// Push / Pop markup
397-
// -------------------------------------------------------------------------
398-
399398
@Test
400399
public void testPushPlusShorthand() {
401400
Setup s = new Setup();
@@ -415,10 +414,6 @@ public void testPushPopKeywords() {
415414
assertThat(s.fluent.style()).isEqualTo(styleBeforePush);
416415
}
417416

418-
// -------------------------------------------------------------------------
419-
// Unknown / invalid markup is silently ignored
420-
// -------------------------------------------------------------------------
421-
422417
@Test
423418
public void testUnknownMarkupIsIgnored() {
424419
Setup s = new Setup();
@@ -439,4 +434,11 @@ public void testInvalidPositionIsIgnored() {
439434
s.parse("{~abc,def}text");
440435
assertThat(s.result()).isEqualTo("text");
441436
}
437+
438+
@Test
439+
public void testTextWithFormatting() {
440+
Setup s = new Setup();
441+
s.parse("{i}%s{/i}");
442+
assertThat(s.result()).isEqualTo(Ansi.italic() + "%s" + Ansi.italicOff());
443+
}
442444
}

0 commit comments

Comments
 (0)