Skip to content

Commit

Permalink
Support for extension (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
thmarx authored Jan 5, 2024
1 parent 11b646e commit 98addb9
Show file tree
Hide file tree
Showing 35 changed files with 586 additions and 2 deletions.
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
<version>4.13.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
Expand Down Expand Up @@ -153,4 +159,4 @@
</snapshotRepository>
</distributionManagement>

</project>
</project>
15 changes: 15 additions & 0 deletions src/main/java/io/github/gitbucket/markedj/Lexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.*;

import static io.github.gitbucket.markedj.Utils.*;
import io.github.gitbucket.markedj.extension.Extension;

public class Lexer {

Expand Down Expand Up @@ -170,6 +171,20 @@ protected void token(String src, boolean top, boolean bq, LexerContext context){
}
}

{
Extension.LexResult result = null;
for (Extension extension : options.getExtensions()) {
result = extension.lex(src, context, this::token);
if (result.matches()) {
src = result.getSource();
break;
}
}
if (result != null && result.matches()) {
continue;
}
}

// list
{
List<String> cap = rules.get("list").exec(src);
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/io/github/gitbucket/markedj/Options.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.github.gitbucket.markedj;

import io.github.gitbucket.markedj.extension.Extension;
import java.util.ArrayList;
import java.util.List;
import org.jsoup.safety.Safelist;

public class Options {
Expand Down Expand Up @@ -33,6 +36,16 @@ public class Options {
.addAttributes("input", "type", "checked", "name", "value", "disabled")
.addAttributes(":all", "id", "class", "style");

private List<Extension> extensions = new ArrayList<>();

public void addExtension (Extension extension) {
extensions.add(extension);
}

public List<Extension> getExtensions () {
return extensions;
}

public void setGfm(boolean gfm) {
this.gfm = gfm;
}
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/io/github/gitbucket/markedj/Parser.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.github.gitbucket.markedj;

import io.github.gitbucket.markedj.extension.Extension;
import io.github.gitbucket.markedj.rule.Rule;
import io.github.gitbucket.markedj.token.*;

import java.util.Map;
import java.util.Optional;
import java.util.Stack;

public class Parser {
Expand All @@ -17,7 +19,7 @@ public Parser(Options options, Renderer renderer){
}

public String parse(Stack<Token> src, Map<String, Lexer.Link> links){
Map<String, Rule> rules;
Map<String, Rule> rules;
if(options.isGfm()){
if(options.isBreaks()){
rules = Grammer.INLINE_BREAKS_RULES;
Expand Down Expand Up @@ -149,6 +151,12 @@ protected String tok(ParserContext context){
return renderer.paragraph(parseText(context));
}
default: {
// try to find extension
String tokenType = context.currentToken().getType();
Optional<Extension> extension = options.getExtensions().stream().filter(ext -> ext.handlesToken(tokenType)).findFirst();
if (extension.isPresent()) {
return extension.get().parse(context, this::tok);
}
throw new RuntimeException("Unexpected token: " + context.currentToken());
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/io/github/gitbucket/markedj/extension/Extension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2023 GitBucket.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension;

import io.github.gitbucket.markedj.Lexer;
import io.github.gitbucket.markedj.Parser;
import java.util.function.Function;

/**
*
* @author t.marx
*/
public interface Extension {

public LexResult lex(String source, final Lexer.LexerContext context, final TokenConsumer consumer);

public boolean handlesToken (final String token);

public String parse (Parser.ParserContext context, Function<Parser.ParserContext, String> tok);

public static class LexResult {
private final String source;
private final boolean matches;

public LexResult(String source, boolean matches) {
this.source = source;
this.matches = matches;
}

public String getSource() {
return source;
}

public boolean matches() {
return matches;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2023 GitBucket.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension;

import io.github.gitbucket.markedj.Lexer;

/**
*
* @author t.marx
*/
@FunctionalInterface
public interface TokenConsumer {
public void token (String src, boolean top, boolean bq, Lexer.LexerContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2023 GitBucket.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension.notification;

import io.github.gitbucket.markedj.token.Token;

/**
*
* @author t.marx
*/
public class NotificationEndToken implements Token {
@Override
public String getType() {
return "NotificationEndToken";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2023 GitBucket.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension.notification;

import io.github.gitbucket.markedj.Lexer;
import io.github.gitbucket.markedj.Parser;
import io.github.gitbucket.markedj.extension.Extension;
import io.github.gitbucket.markedj.extension.TokenConsumer;
import io.github.gitbucket.markedj.rule.FindFirstRule;
import io.github.gitbucket.markedj.rule.Rule;
import io.github.gitbucket.markedj.token.Token;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
*
* @author t.marx
*/
public class NotificationExtension implements Extension {

public static String BLOCK_NOTIFICATION = "^((?:(!([xv!]?))[^\n]*(?!^!)\n?)+)";

private Rule notificationRule() {
return new FindFirstRule(BLOCK_NOTIFICATION);
}

@Override
public LexResult lex(String source, final Lexer.LexerContext context, final TokenConsumer consumer) {
List<String> cap = notificationRule().exec(source);
boolean tokenFound = false;
if (!cap.isEmpty()) {
// we have detected several contiguous lines of notifications
// ensure that all are of same kind
String allNotificationsLines = cap.get(0);

// if other kind of notifications lines are detected
// let's split them so that they are handled separately
String findOtherLinesPattern = "(?m)^(!" + Notifications.exceptGivenNotificationClass(cap.get(3)) + " .*)";
Matcher otherLinesMatcher = Pattern.compile(findOtherLinesPattern).matcher(cap.get(1));

if (otherLinesMatcher.find()) {
String otherLinesSeparated = otherLinesMatcher.replaceAll("\n$1\n");

// change the source to parse
// replace all the notifications lines with separated notifications lines
// and reparse the string
source = otherLinesSeparated + source.substring(allNotificationsLines.length());
} else {
source = source.substring(allNotificationsLines.length());
context.pushToken(new NotificationStartToken(cap.get(3)));
consumer.token(allNotificationsLines.replaceAll("(?m)^" + cap.get(2) + "[ ]?", ""), false, false, context);
context.pushToken(new NotificationEndToken());
}

tokenFound = true;
}
return new LexResult(source, tokenFound);
}

@Override
public boolean handlesToken(String token) {
return NotificationStartToken.TYPE.equals(token);
}

@Override
public String parse(Parser.ParserContext context, Function<Parser.ParserContext, String> tok) {
NotificationStartToken t = (NotificationStartToken) context.currentToken();
StringBuilder body = new StringBuilder();
while (true) {
Token n = context.nextToken();
if (n == null || n.getType().equals("NotificationEndToken")) {
break;
}
body.append(tok.apply(context));
}
return render(body.toString(), t.getNotification());
}

private String render(String info, Notifications.Notification notification) {
return String.format("<div class=\"notification_%s\">\n%s</div>\n", notification.name().toLowerCase(Locale.ENGLISH), info);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2023 GitBucket.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.gitbucket.markedj.extension.notification;

import io.github.gitbucket.markedj.token.Token;

/**
*
* @author t.marx
*/
public class NotificationStartToken implements Token {
protected static String TYPE = "NotificationStartToken";

private Notifications.Notification notification;

public NotificationStartToken(String type) {
this.notification = Notifications.Notification.fromString(type);
}

@Override
public String getType() {
return TYPE;
}

public Notifications.Notification getNotification() {
return notification;
}
}
Loading

0 comments on commit 98addb9

Please sign in to comment.