Skip to content

Commit 376fc8e

Browse files
authored
Merge pull request #272 from commonmark/issue-271-strikethrough-two-tildes
Add `requireTwoTildes` for `StrikethroughExtension` (fixes #271)
2 parents ba673a9 + de1c59e commit 376fc8e

File tree

6 files changed

+122
-11
lines changed

6 files changed

+122
-11
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
66
This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html),
77
with the exception that 0.x versions can break between minor versions.
88

9+
## [Unreleased]
10+
### Added
11+
- GitHub strikethrough: With the previous version we adjusted the
12+
extension to also accept the single tilde syntax. But if you use
13+
another extension that uses the single tilde syntax, you will get a
14+
conflict. To avoid that, `StrikethroughExtension` can now be
15+
configured to require two tildes like before, see Javadoc.
16+
917
## [0.20.0] - 2022-10-20
1018
### Fixed
1119
- GitHub tables: A single pipe (optional whitespace) now ends a table
@@ -371,7 +379,7 @@ API breaking changes (caused by changes in spec):
371379
Initial release of commonmark-java, a port of commonmark.js with extensions
372380
for autolinking URLs, GitHub flavored strikethrough and tables.
373381
374-
382+
[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.20.0...HEAD
375383
[0.20.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.19.0...commonmark-parent-0.20.0
376384
[0.19.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.2...commonmark-parent-0.19.0
377385
[0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2

commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughExtension.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,59 @@
1414
import org.commonmark.renderer.NodeRenderer;
1515

1616
/**
17-
* Extension for GFM strikethrough using ~~ (GitHub Flavored Markdown).
17+
* Extension for GFM strikethrough using {@code ~} or {@code ~~} (GitHub Flavored Markdown).
18+
* <p>Example input:</p>
19+
* <pre>{@code ~foo~ or ~~bar~~}</pre>
20+
* <p>Example output (HTML):</p>
21+
* <pre>{@code <del>foo</del> or <del>bar</del>}</pre>
1822
* <p>
19-
* Create it with {@link #create()} and then configure it on the builders
23+
* Create the extension with {@link #create()} and then add it to the parser and renderer builders
2024
* ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)},
2125
* {@link HtmlRenderer.Builder#extensions(Iterable)}).
2226
* </p>
2327
* <p>
2428
* The parsed strikethrough text regions are turned into {@link Strikethrough} nodes.
2529
* </p>
30+
* <p>
31+
* If you have another extension that only uses a single tilde ({@code ~}) syntax, you will have to configure this
32+
* {@link StrikethroughExtension} to only accept the double tilde syntax, like this:
33+
* </p>
34+
* <pre>
35+
* {@code
36+
* StrikethroughExtension.builder().requireTwoTildes(true).build();
37+
* }
38+
* </pre>
39+
* <p>
40+
* If you don't do that, there's a conflict between the two extensions and you will get an
41+
* {@link IllegalArgumentException} when constructing the parser.
42+
* </p>
2643
*/
2744
public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
2845
TextContentRenderer.TextContentRendererExtension {
2946

30-
private StrikethroughExtension() {
47+
private final boolean requireTwoTildes;
48+
49+
private StrikethroughExtension(Builder builder) {
50+
this.requireTwoTildes = builder.requireTwoTildes;
3151
}
3252

53+
/**
54+
* @return the extension with default options
55+
*/
3356
public static Extension create() {
34-
return new StrikethroughExtension();
57+
return builder().build();
58+
}
59+
60+
/**
61+
* @return a builder to configure the behavior of the extension
62+
*/
63+
public static Builder builder() {
64+
return new Builder();
3565
}
3666

3767
@Override
3868
public void extend(Parser.Builder parserBuilder) {
39-
parserBuilder.customDelimiterProcessor(new StrikethroughDelimiterProcessor());
69+
parserBuilder.customDelimiterProcessor(new StrikethroughDelimiterProcessor(requireTwoTildes));
4070
}
4171

4272
@Override
@@ -58,4 +88,26 @@ public NodeRenderer create(TextContentNodeRendererContext context) {
5888
}
5989
});
6090
}
91+
92+
public static class Builder {
93+
94+
private boolean requireTwoTildes = false;
95+
96+
/**
97+
* @param requireTwoTildes Whether two tilde characters ({@code ~~}) are required for strikethrough or whether
98+
* one is also enough. Default is {@code false}; both a single tilde and two tildes can be used for strikethrough.
99+
* @return {@code this}
100+
*/
101+
public Builder requireTwoTildes(boolean requireTwoTildes) {
102+
this.requireTwoTildes = requireTwoTildes;
103+
return this;
104+
}
105+
106+
/**
107+
* @return a configured extension
108+
*/
109+
public Extension build() {
110+
return new StrikethroughExtension(this);
111+
}
112+
}
61113
}

commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010

1111
public class StrikethroughDelimiterProcessor implements DelimiterProcessor {
1212

13+
private final boolean requireTwoTildes;
14+
15+
public StrikethroughDelimiterProcessor() {
16+
this(false);
17+
}
18+
19+
public StrikethroughDelimiterProcessor(boolean requireTwoTildes) {
20+
this.requireTwoTildes = requireTwoTildes;
21+
}
22+
1323
@Override
1424
public char getOpeningCharacter() {
1525
return '~';
@@ -22,7 +32,7 @@ public char getClosingCharacter() {
2232

2333
@Override
2434
public int getMinLength() {
25-
return 1;
35+
return requireTwoTildes ? 2 : 1;
2636
}
2737

2838
@Override

commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,25 @@
44
import org.commonmark.node.Node;
55
import org.commonmark.node.Paragraph;
66
import org.commonmark.node.SourceSpan;
7+
import org.commonmark.node.Text;
78
import org.commonmark.parser.IncludeSourceSpans;
89
import org.commonmark.parser.Parser;
10+
import org.commonmark.parser.delimiter.DelimiterProcessor;
11+
import org.commonmark.parser.delimiter.DelimiterRun;
912
import org.commonmark.renderer.html.HtmlRenderer;
1013
import org.commonmark.renderer.text.TextContentRenderer;
1114
import org.commonmark.testutil.RenderingTestCase;
1215
import org.junit.Test;
1316

1417
import java.util.Arrays;
15-
import java.util.Collections;
1618
import java.util.Set;
1719

20+
import static java.util.Collections.singleton;
1821
import static org.junit.Assert.assertEquals;
1922

2023
public class StrikethroughTest extends RenderingTestCase {
2124

22-
private static final Set<Extension> EXTENSIONS = Collections.singleton(StrikethroughExtension.create());
25+
private static final Set<Extension> EXTENSIONS = singleton(StrikethroughExtension.create());
2326
private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build();
2427
private static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build();
2528
private static final TextContentRenderer CONTENT_RENDERER = TextContentRenderer.builder()
@@ -92,6 +95,19 @@ public void textContentRenderer() {
9295
assertEquals("/foo/", CONTENT_RENDERER.render(document));
9396
}
9497

98+
@Test
99+
public void requireTwoTildesOption() {
100+
Parser parser = Parser.builder()
101+
.extensions(singleton(StrikethroughExtension.builder()
102+
.requireTwoTildes(true)
103+
.build()))
104+
.customDelimiterProcessor(new SubscriptDelimiterProcessor())
105+
.build();
106+
107+
Node document = parser.parse("~foo~ ~~bar~~");
108+
assertEquals("(sub)foo(/sub) /bar/", CONTENT_RENDERER.render(document));
109+
}
110+
95111
@Test
96112
public void sourceSpans() {
97113
Parser parser = Parser.builder()
@@ -110,4 +126,29 @@ public void sourceSpans() {
110126
protected String render(String source) {
111127
return HTML_RENDERER.render(PARSER.parse(source));
112128
}
129+
130+
private static class SubscriptDelimiterProcessor implements DelimiterProcessor {
131+
132+
@Override
133+
public char getOpeningCharacter() {
134+
return '~';
135+
}
136+
137+
@Override
138+
public char getClosingCharacter() {
139+
return '~';
140+
}
141+
142+
@Override
143+
public int getMinLength() {
144+
return 1;
145+
}
146+
147+
@Override
148+
public int process(DelimiterRun openingRun, DelimiterRun closingRun) {
149+
openingRun.getOpener().insertAfter(new Text("(sub)"));
150+
closingRun.getCloser().insertBefore(new Text("(/sub)"));
151+
return 1;
152+
}
153+
}
113154
}

commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ void add(DelimiterProcessor dp) {
5151
added = true;
5252
break;
5353
} else if (len == pLen) {
54-
throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len);
54+
throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len + "; conflicting processors: " + p + ", " + dp);
5555
}
5656
}
5757
if (!added) {

commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void multipleDelimitersWithDifferentLengths() {
6060
}
6161

6262
@Test(expected = IllegalArgumentException.class)
63-
public void multipleDelimitersWithSameLength() {
63+
public void multipleDelimitersWithSameLengthConflict() {
6464
Parser.builder()
6565
.customDelimiterProcessor(new OneDelimiterProcessor())
6666
.customDelimiterProcessor(new OneDelimiterProcessor())

0 commit comments

Comments
 (0)