Skip to content

Commit

Permalink
Add requireTwoTildes for StrikethroughExtension (fixes #271)
Browse files Browse the repository at this point in the history
With the previous version we adjusted the extension to also accept the
single tilde syntax. But if you use another extension that uses the
single tilde syntax, you will get a conflict, see issue.

To avoid that, `StrikethroughExtension` can now be configured to require
two tildes like before, see Javadoc.
  • Loading branch information
robinst committed Oct 24, 2022
1 parent ba673a9 commit 6db2990
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 11 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Expand Up @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html),
with the exception that 0.x versions can break between minor versions.

## [Unreleased]
### Added
- GitHub strikethrough: With the previous version we adjusted the
extension to also accept the single tilde syntax. But if you use
another extension that uses the single tilde syntax, you will get a
conflict. To avoid that, `StrikethroughExtension` can now be
configured to require two tildes like before, see Javadoc.

## [0.20.0] - 2022-10-20
### Fixed
- GitHub tables: A single pipe (optional whitespace) now ends a table
Expand Down Expand Up @@ -371,7 +379,7 @@ API breaking changes (caused by changes in spec):
Initial release of commonmark-java, a port of commonmark.js with extensions
for autolinking URLs, GitHub flavored strikethrough and tables.


[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.20.0...HEAD
[0.20.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.19.0...commonmark-parent-0.20.0
[0.19.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.2...commonmark-parent-0.19.0
[0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2
Expand Down
Expand Up @@ -14,29 +14,57 @@
import org.commonmark.renderer.NodeRenderer;

/**
* Extension for GFM strikethrough using ~~ (GitHub Flavored Markdown).
* Extension for GFM strikethrough using {@code ~} or {@code ~~} (GitHub Flavored Markdown).
* <p>Example input:</p>
* <pre>{@code ~foo~ or ~~bar~~}</pre>
* <p>Example output (HTML):</p>
* <pre>{@code <del>foo</del> or <del>bar</del>}</pre>
* <p>
* Create it with {@link #create()} and then configure it on the builders
* Create the extension with {@link #create()} and then add it to the parser and renderer builders
* ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)},
* {@link HtmlRenderer.Builder#extensions(Iterable)}).
* </p>
* <p>
* The parsed strikethrough text regions are turned into {@link Strikethrough} nodes.
* </p>
* <p>
* If you have another extension that only uses a single tilde ({@code ~}) syntax, you will have to configure this
* {@link StrikethroughExtension} to only accept the double tilde syntax, like this:
* <pre>
* {@code
* StrikethroughExtension.builder().requireTwoTildes(true).build();
* }
* </pre>
* If you don't do that, there's a conflict between the two extensions and you will get an
* {@link IllegalArgumentException} when constructing the parser.
* </p>
*/
public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
TextContentRenderer.TextContentRendererExtension {

private StrikethroughExtension() {
private final boolean requireTwoTildes;

private StrikethroughExtension(Builder builder) {
this.requireTwoTildes = builder.requireTwoTildes;
}

/**
* @return the extension with default options
*/
public static Extension create() {
return new StrikethroughExtension();
return builder().build();
}

/**
* @return a builder to configure the behavior of the extension
*/
public static Builder builder() {
return new Builder();
}

@Override
public void extend(Parser.Builder parserBuilder) {
parserBuilder.customDelimiterProcessor(new StrikethroughDelimiterProcessor());
parserBuilder.customDelimiterProcessor(new StrikethroughDelimiterProcessor(requireTwoTildes));
}

@Override
Expand All @@ -58,4 +86,26 @@ public NodeRenderer create(TextContentNodeRendererContext context) {
}
});
}

public static class Builder {

private boolean requireTwoTildes = false;

/**
* @param requireTwoTildes Whether two tilde characters ({@code ~~}) are required for strikethrough or whether
* one is also enough. Default is {@code false}; both a single tilde and two tildes can be used for strikethrough.
* @return {@code this}
*/
public Builder requireTwoTildes(boolean requireTwoTildes) {
this.requireTwoTildes = requireTwoTildes;
return this;
}

/**
* @return a configured extension
*/
public Extension build() {
return new StrikethroughExtension(this);
}
}
}
Expand Up @@ -10,6 +10,16 @@

public class StrikethroughDelimiterProcessor implements DelimiterProcessor {

private final boolean requireTwoTildes;

public StrikethroughDelimiterProcessor() {
this(false);
}

public StrikethroughDelimiterProcessor(boolean requireTwoTildes) {
this.requireTwoTildes = requireTwoTildes;
}

@Override
public char getOpeningCharacter() {
return '~';
Expand All @@ -22,7 +32,7 @@ public char getClosingCharacter() {

@Override
public int getMinLength() {
return 1;
return requireTwoTildes ? 2 : 1;
}

@Override
Expand Down
Expand Up @@ -4,22 +4,25 @@
import org.commonmark.node.Node;
import org.commonmark.node.Paragraph;
import org.commonmark.node.SourceSpan;
import org.commonmark.node.Text;
import org.commonmark.parser.IncludeSourceSpans;
import org.commonmark.parser.Parser;
import org.commonmark.parser.delimiter.DelimiterProcessor;
import org.commonmark.parser.delimiter.DelimiterRun;
import org.commonmark.renderer.html.HtmlRenderer;
import org.commonmark.renderer.text.TextContentRenderer;
import org.commonmark.testutil.RenderingTestCase;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.Set;

import static java.util.Collections.singleton;
import static org.junit.Assert.assertEquals;

public class StrikethroughTest extends RenderingTestCase {

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

@Test
public void requireTwoTildesOption() {
Parser parser = Parser.builder()
.extensions(singleton(StrikethroughExtension.builder()
.requireTwoTildes(true)
.build()))
.customDelimiterProcessor(new SubscriptDelimiterProcessor())
.build();

Node document = parser.parse("~foo~ ~~bar~~");
assertEquals("(sub)foo(/sub) /bar/", CONTENT_RENDERER.render(document));
}

@Test
public void sourceSpans() {
Parser parser = Parser.builder()
Expand All @@ -110,4 +126,29 @@ public void sourceSpans() {
protected String render(String source) {
return HTML_RENDERER.render(PARSER.parse(source));
}

private static class SubscriptDelimiterProcessor implements DelimiterProcessor {

@Override
public char getOpeningCharacter() {
return '~';
}

@Override
public char getClosingCharacter() {
return '~';
}

@Override
public int getMinLength() {
return 1;
}

@Override
public int process(DelimiterRun openingRun, DelimiterRun closingRun) {
openingRun.getOpener().insertAfter(new Text("(sub)"));
closingRun.getCloser().insertBefore(new Text("(/sub)"));
return 1;
}
}
}
Expand Up @@ -51,7 +51,7 @@ void add(DelimiterProcessor dp) {
added = true;
break;
} else if (len == pLen) {
throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len);
throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len + "; conflicting processors: " + p + ", " + dp);
}
}
if (!added) {
Expand Down
Expand Up @@ -60,7 +60,7 @@ public void multipleDelimitersWithDifferentLengths() {
}

@Test(expected = IllegalArgumentException.class)
public void multipleDelimitersWithSameLength() {
public void multipleDelimitersWithSameLengthConflict() {
Parser.builder()
.customDelimiterProcessor(new OneDelimiterProcessor())
.customDelimiterProcessor(new OneDelimiterProcessor())
Expand Down

0 comments on commit 6db2990

Please sign in to comment.