|
15 | 15 | */
|
16 | 16 | package com.diffplug.freshmark;
|
17 | 17 |
|
18 |
| -import java.util.function.Function; |
| 18 | +import java.net.URLEncoder; |
| 19 | +import java.nio.charset.StandardCharsets; |
| 20 | +import java.util.Map; |
| 21 | +import java.util.Objects; |
| 22 | +import java.util.function.Consumer; |
19 | 23 | import java.util.regex.Matcher;
|
20 | 24 | import java.util.regex.Pattern;
|
21 | 25 |
|
| 26 | +import javax.script.ScriptEngine; |
| 27 | +import javax.script.ScriptException; |
| 28 | + |
22 | 29 | import com.diffplug.common.base.Errors;
|
23 | 30 | import com.diffplug.jscriptbox.Language;
|
24 | 31 | import com.diffplug.jscriptbox.ScriptBox;
|
25 |
| -import com.diffplug.jscriptbox.TypedScriptEngine; |
26 | 32 |
|
27 |
| -/** |
28 |
| - * The core implementation a FreshMark compiler. Provides two methods: |
29 |
| - * <ul> |
30 |
| - * <li>{@link #keyToValue} - defines how template keys are transformed into values</li> |
31 |
| - * <li>{@link #setupScriptEngine} - initializes any functions or variables which should be available to the program</li> |
32 |
| - * </ul> |
33 |
| - * See {@link FreshMarkDefault} for the default implementation. |
34 |
| - */ |
35 |
| -public abstract class FreshMark { |
36 |
| - /** Parser which splits up the raw document into structured tags which get passed to the compiler. */ |
37 |
| - static final Parser parser = new Parser("<!---freshmark", "-->"); |
| 33 | +/** The defaault implementation. */ |
| 34 | +public class FreshMark extends CommentScript { |
| 35 | + private static final String INTRON = "<!---freshmark"; |
| 36 | + private static final String EXON = "-->"; |
| 37 | + |
| 38 | + private final Map<String, ?> properties; |
| 39 | + private final Consumer<String> warningStream; |
38 | 40 |
|
39 |
| - /** Compiles a single section/program/input combo into the appropriate output. */ |
40 |
| - final Parser.Compiler compiler = new Parser.Compiler() { |
41 |
| - @Override |
42 |
| - public String compileSection(String section, String program, String input) { |
43 |
| - return Errors.rethrow().get(() -> { |
44 |
| - ScriptBox box = ScriptBox.create(); |
45 |
| - setupScriptEngine(section, box); |
46 |
| - TypedScriptEngine engine = box.buildTyped(Language.nashorn()); |
| 41 | + public FreshMark(Map<String, ?> properties, Consumer<String> warningStream) { |
| 42 | + super(INTRON, EXON, Pattern.quote(INTRON) + "(.*?)" + Pattern.quote(EXON)); |
| 43 | + this.properties = properties; |
| 44 | + this.warningStream = warningStream; |
| 45 | + } |
| 46 | + |
| 47 | + @Override |
| 48 | + protected ScriptEngine setupScriptEngine(String section) throws ScriptException { |
| 49 | + return ScriptBox.create() |
| 50 | + .setAll(properties) |
| 51 | + .set("link").toFunc2(FreshMark::link) |
| 52 | + .set("image").toFunc2(FreshMark::image) |
| 53 | + .set("shield").toFunc4(FreshMark::shield) |
| 54 | + .set("prefixDelimiterReplace").toFunc4(FreshMark::prefixDelimiterReplace) |
| 55 | + .build(Language.javascript()); |
| 56 | + } |
47 | 57 |
|
48 |
| - // apply the templating engine to the program |
49 |
| - String templatedProgram = template(program, key -> keyToValue(section, key)); |
50 |
| - // populate the input data |
51 |
| - engine.getRaw().put("input", input); |
52 |
| - // evaluate the program and get the result |
53 |
| - engine.eval(templatedProgram); |
54 |
| - String compiled = engine.get("output", String.class); |
55 |
| - // make sure that the compiled output starts and ends with a newline, |
56 |
| - // so that the tags stay separated separated nicely |
57 |
| - if (!compiled.startsWith("\n")) { |
58 |
| - compiled = "\n" + compiled; |
59 |
| - } |
60 |
| - if (!compiled.endsWith("\n")) { |
61 |
| - compiled = compiled + "\n"; |
62 |
| - } |
63 |
| - return parser.prefix + " " + section + "\n" + |
64 |
| - program + |
65 |
| - parser.postfix + |
66 |
| - compiled + |
67 |
| - parser.prefix + " /" + section + " " + parser.postfix; |
68 |
| - }); |
| 58 | + @Override |
| 59 | + protected String keyToValue(String section, String key) { |
| 60 | + Object value = properties.get(key); |
| 61 | + if (value != null) { |
| 62 | + return Objects.toString(value); |
| 63 | + } else { |
| 64 | + warningStream.accept("Unknown key '" + key + "'"); |
| 65 | + return key + "=UNKNOWN"; |
69 | 66 | }
|
70 |
| - }; |
| 67 | + } |
71 | 68 |
|
72 |
| - /** Compiles the given input string. Input must contain only unix newlines, output is guaranteed to be the same. */ |
73 |
| - public String compile(String input) { |
74 |
| - return parser.compile(input, compiler); |
| 69 | + //////////////////////// |
| 70 | + // built-in functions // |
| 71 | + //////////////////////// |
| 72 | + /** Generates a markdown link. */ |
| 73 | + static String link(String text, String url) { |
| 74 | + return "[" + text + "](" + url + ")"; |
75 | 75 | }
|
76 | 76 |
|
77 |
| - /** For the given section, return the proper templated value for the given key. */ |
78 |
| - protected abstract String keyToValue(String section, String key); |
| 77 | + /** Generates a markdown image. */ |
| 78 | + static String image(String altText, String url) { |
| 79 | + return "!" + link(altText, url); |
| 80 | + } |
79 | 81 |
|
80 |
| - /** For the given section, setup the JScriptBox appropriately. The `input` value will be set for you, but you need to do everything else. */ |
81 |
| - protected abstract void setupScriptEngine(String section, ScriptBox scriptBox); |
| 82 | + /** Generates shields using <a href="http://shields.io/">shields.io</a>. */ |
| 83 | + static String shield(String altText, String subject, String status, String color) { |
| 84 | + return image(altText, "https://img.shields.io/badge/" + shieldEscape(subject) + "-" + shieldEscape(status) + "-" + shieldEscape(color) + ".svg"); |
| 85 | + } |
82 | 86 |
|
83 |
| - /** Replaces whatever is inside of {@code {{key}}} tags using the {@code keyToValue} function. */ |
84 |
| - static String template(String input, Function<String, String> keyToValue) { |
85 |
| - Matcher matcher = TEMPLATE.matcher(input); |
86 |
| - StringBuilder result = new StringBuilder(input.length() * 3 / 2); |
| 87 | + private static String shieldEscape(String raw) { |
| 88 | + return Errors.rethrow().get(() -> URLEncoder.encode( |
| 89 | + raw.replace("_", "__").replace("-", "--").replace(" ", "_"), |
| 90 | + StandardCharsets.UTF_8.name())); |
| 91 | + } |
87 | 92 |
|
| 93 | + /** Replaces after prefix and before delimiter with replacement. */ |
| 94 | + static String prefixDelimiterReplace(String input, String prefix, String delimiter, String replacement) { |
| 95 | + StringBuilder builder = new StringBuilder(input.length() * 3 / 2); |
88 | 96 | int lastElement = 0;
|
| 97 | + Pattern pattern = Pattern.compile("(.*?" + Pattern.quote(prefix) + ")(.*?)(" + Pattern.quote(delimiter) + ")", Pattern.DOTALL); |
| 98 | + Matcher matcher = pattern.matcher(input); |
89 | 99 | while (matcher.find()) {
|
90 |
| - result.append(matcher.group(1)); |
91 |
| - result.append(keyToValue.apply(matcher.group(2))); |
| 100 | + builder.append(matcher.group(1)); |
| 101 | + builder.append(replacement); |
| 102 | + builder.append(matcher.group(3)); |
92 | 103 | lastElement = matcher.end();
|
93 | 104 | }
|
94 |
| - result.append(input.substring(lastElement)); |
95 |
| - return result.toString(); |
| 105 | + builder.append(input.substring(lastElement)); |
| 106 | + return builder.toString(); |
96 | 107 | }
|
97 |
| - |
98 |
| - private static final Pattern TEMPLATE = Pattern.compile("(.*?)\\{\\{(.*?)\\}\\}", Pattern.DOTALL); |
99 | 108 | }
|
0 commit comments