Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for extension #45

Merged
merged 8 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
thmarx marked this conversation as resolved.
Show resolved Hide resolved

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;
thmarx marked this conversation as resolved.
Show resolved Hide resolved
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
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