15
15
*/
16
16
package com .diffplug .freshmark ;
17
17
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 ;
23
18
import java .util .function .Function ;
24
19
import java .util .regex .Matcher ;
25
20
import java .util .regex .Pattern ;
29
24
import com .diffplug .jscriptbox .ScriptBox ;
30
25
import com .diffplug .jscriptbox .TypedScriptEngine ;
31
26
32
- public class FreshMark implements Compiler {
33
- static final Parser parser = new Parser ("freshmark" );
34
-
35
- private final Map <String , ?> properties ;
36
- private final Function <String , String > template ;
37
-
38
- public FreshMark (Map <String , ?> properties , Consumer <String > warningStream ) {
39
- this .properties = properties ;
40
- this .template = key -> {
41
- Object value = properties .get (key );
42
- if (value != null ) {
43
- return Objects .toString (value );
44
- } else {
45
- warningStream .accept ("Unknown key '" + key + "'" );
46
- return key + "=UNKNOWN" ;
47
- }
48
- };
49
- }
50
-
51
- /** Compiles the given document. */
52
- public String compile (String input ) {
53
- return parser .compile (input , this );
54
- }
55
-
56
- @ Override
57
- public String compileSection (String section , String program , String input ) {
58
- return Errors .rethrow ().get (() -> {
59
- TypedScriptEngine engine = ScriptBox .create ()
60
- .setAll (properties )
61
- .set ("link" ).toFunc2 (FreshMark ::link )
62
- .set ("image" ).toFunc2 (FreshMark ::image )
63
- .set ("shield" ).toFunc4 (FreshMark ::shield )
64
- .set ("prefixDelimiterReplace" ).toFunc4 (FreshMark ::prefixDelimiterReplace )
65
- .buildTyped (Language .nashorn ());
66
-
67
- // apply the templating engine to the program
68
- engine .getRaw ().put ("input" , input );
69
- engine .eval (template (program , template ));
70
- String compiled = engine .get ("output" , String .class );
71
- if (compiled .length () == 0 ) {
72
- compiled = "\n " ;
73
- } else {
74
- if (compiled .charAt (0 ) != '\n' ) {
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" , "-->" );
38
+
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 ());
47
+
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 " )) {
75
58
compiled = "\n " + compiled ;
76
59
}
77
- if (compiled .charAt ( compiled . length () - 1 ) != '\n' ) {
60
+ if (! compiled .endsWith ( " \n " ) ) {
78
61
compiled = compiled + "\n " ;
79
62
}
80
- }
81
- return parser .prefix + " " + section + "\n " +
82
- program +
83
- parser .postfix +
84
- compiled +
85
- parser .prefix + " /" + section + " " + parser .postfix ;
86
- });
87
- }
88
-
89
- /** Generates a markdown link. */
90
- static String link (String text , String url ) {
91
- return "[" + text + "](" + url + ")" ;
92
- }
93
-
94
- /** Generates a markdown image. */
95
- static String image (String altText , String url ) {
96
- return "!" + link (altText , url );
97
- }
63
+ return parser .prefix + " " + section + "\n " +
64
+ program +
65
+ parser .postfix +
66
+ compiled +
67
+ parser .prefix + " /" + section + " " + parser .postfix ;
68
+ });
69
+ }
70
+ };
98
71
99
- /** Generates shields using <a href="http://shields.io/">shields.io</a> . */
100
- static String shield (String altText , String subject , String status , String color ) {
101
- return image ( altText , "https://img.shields.io/badge/" + shieldEscape ( subject ) + "-" + shieldEscape ( status ) + "-" + shieldEscape ( color ) + ".svg" );
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 );
102
75
}
103
76
104
- private static String shieldEscape (String raw ) {
105
- return Errors .rethrow ().get (() -> URLEncoder .encode (
106
- raw .replace ("_" , "__" ).replace ("-" , "--" ).replace (" " , "_" ),
107
- StandardCharsets .UTF_8 .name ()));
108
- }
77
+ /** For the given section, return the proper templated value for the given key. */
78
+ protected abstract String keyToValue (String section , String key );
109
79
110
- /** Replaces everything between the */
111
- static String prefixDelimiterReplace (String input , String prefix , String delimiter , String replacement ) {
112
- StringBuilder builder = new StringBuilder (input .length () * 3 / 2 );
113
-
114
- int lastElement = 0 ;
115
- Pattern pattern = Pattern .compile ("(.*?" + Pattern .quote (prefix ) + ")(.*?)(" + Pattern .quote (delimiter ) + ")" , Pattern .DOTALL );
116
- Matcher matcher = pattern .matcher (input );
117
- while (matcher .find ()) {
118
- builder .append (matcher .group (1 ));
119
- builder .append (replacement );
120
- builder .append (matcher .group (3 ));
121
- lastElement = matcher .end ();
122
- }
123
- builder .append (input .substring (lastElement ));
124
- return builder .toString ();
125
- }
126
-
127
- private static final Pattern TEMPLATE = Pattern .compile ("(.*?)\\ {\\ {(.*?)\\ }\\ }" , Pattern .DOTALL );
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 );
128
82
129
83
/** Replaces whatever is inside of {@code {{key}}} tags using the {@code keyToValue} function. */
130
84
static String template (String input , Function <String , String > keyToValue ) {
@@ -140,4 +94,6 @@ static String template(String input, Function<String, String> keyToValue) {
140
94
result .append (input .substring (lastElement ));
141
95
return result .toString ();
142
96
}
97
+
98
+ private static final Pattern TEMPLATE = Pattern .compile ("(.*?)\\ {\\ {(.*?)\\ }\\ }" , Pattern .DOTALL );
143
99
}
0 commit comments