Skip to content

Commit 18582f6

Browse files
committed
Convert flexmark to compile-only sourceset
This converts the flexmark step to use the compile-only flexmark glue code, which makes using the library specific code much easier by avoiding the java reflection api.
1 parent 05ab009 commit 18582f6

File tree

3 files changed

+100
-91
lines changed

3 files changed

+100
-91
lines changed

lib/build.gradle

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ apply from: rootProject.file('gradle/java-publish.gradle')
88

99
def NEEDS_GLUE = [
1010
'sortPom',
11-
'ktlint'
11+
'ktlint',
12+
'flexmark'
1213
]
1314
for (glue in NEEDS_GLUE) {
1415
sourceSets.register(glue) {
@@ -32,6 +33,9 @@ dependencies {
3233
ktlintCompileOnly "com.pinterest:ktlint:$VER_KTLINT"
3334
ktlintCompileOnly "com.pinterest.ktlint:ktlint-core:$VER_KTLINT"
3435
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-standard:$VER_KTLINT"
36+
37+
// used for markdown formatting
38+
flexmarkCompileOnly 'com.vladsch.flexmark:flexmark-all:0.62.2'
3539
}
3640

3741
// we'll hold the core lib to a high standard
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2021 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.glue.markdown;
17+
18+
import com.vladsch.flexmark.formatter.Formatter;
19+
import com.vladsch.flexmark.parser.Parser;
20+
import com.vladsch.flexmark.parser.ParserEmulationProfile;
21+
import com.vladsch.flexmark.parser.PegdownExtensions;
22+
import com.vladsch.flexmark.profile.pegdown.PegdownOptionsAdapter;
23+
import com.vladsch.flexmark.util.ast.Document;
24+
import com.vladsch.flexmark.util.data.MutableDataHolder;
25+
import com.vladsch.flexmark.util.data.MutableDataSet;
26+
27+
import com.diffplug.spotless.FormatterFunc;
28+
29+
/**
30+
* The formatter function for <a href="https://github.com/vsch/flexmark-java">flexmark-java</a>.
31+
*/
32+
public class FlexmarkFormatterFunc implements FormatterFunc {
33+
34+
/**
35+
* The emulation profile is used by both the parser and the formatter and generally determines the markdown flavor.
36+
* COMMONMARK is the default defined by flexmark-java.
37+
*/
38+
private static final String DEFAULT_EMULATION_PROFILE = "COMMONMARK";
39+
40+
private final Parser parser;
41+
private final Formatter formatter;
42+
43+
public FlexmarkFormatterFunc() {
44+
// flexmark-java has a separate parser and renderer (formatter)
45+
// this is build from the example in https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter
46+
47+
// The emulation profile generally determines the markdown flavor. We use the same one for both the parser and
48+
// the formatter, to make sure this formatter func is idempotent.
49+
final ParserEmulationProfile emulationProfile = ParserEmulationProfile.valueOf(DEFAULT_EMULATION_PROFILE);
50+
51+
final MutableDataHolder parserOptions = createParserOptions(emulationProfile);
52+
final MutableDataHolder formatterOptions = createFormatterOptions(parserOptions, emulationProfile);
53+
54+
parser = Parser.builder(parserOptions).build();
55+
formatter = Formatter.builder(formatterOptions).build();
56+
}
57+
58+
/**
59+
* Creates the parser options.
60+
* See: https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options
61+
*
62+
* @param emulationProfile the emulation profile (or flavor of markdown) the parser should use
63+
* @return the created parser options
64+
*/
65+
private static MutableDataHolder createParserOptions(ParserEmulationProfile emulationProfile) {
66+
final MutableDataHolder parserOptions = PegdownOptionsAdapter.flexmarkOptions(PegdownExtensions.ALL).toMutable();
67+
parserOptions.set(Parser.PARSER_EMULATION_PROFILE, emulationProfile);
68+
return parserOptions;
69+
}
70+
71+
/**
72+
* Creates the formatter options, copies the parser extensions and changes defaults that make sense for a formatter.
73+
* See: https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options
74+
*
75+
* @param parserOptions the options used for the parser
76+
* @param emulationProfile the emulation profile (or flavor of markdown) the formatter should use
77+
* @return the created formatter options
78+
*/
79+
private static MutableDataHolder createFormatterOptions(MutableDataHolder parserOptions, ParserEmulationProfile emulationProfile) {
80+
final MutableDataHolder formatterOptions = new MutableDataSet();
81+
formatterOptions.set(Parser.EXTENSIONS, Parser.EXTENSIONS.get(parserOptions));
82+
formatterOptions.set(Formatter.FORMATTER_EMULATION_PROFILE, emulationProfile);
83+
return formatterOptions;
84+
}
85+
86+
@Override
87+
public String apply(String input) throws Exception {
88+
final Document parsedMarkdown = parser.parse(input);
89+
return formatter.render(parsedMarkdown);
90+
}
91+
}

lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java

+4-90
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
package com.diffplug.spotless.markdown;
1717

1818
import java.io.Serializable;
19-
import java.lang.reflect.Array;
20-
import java.lang.reflect.Method;
19+
import java.lang.reflect.Constructor;
2120
import java.util.Objects;
2221

2322
import com.diffplug.spotless.FormatterFunc;
@@ -34,13 +33,6 @@ private FlexmarkStep() {}
3433
private static final String NAME = "flexmark-java";
3534
private static final String MAVEN_COORDINATE = "com.vladsch.flexmark:flexmark-all:";
3635

37-
/**
38-
* The emulation profile is used by both the parser and the formatter and generally determines the markdown flavor.
39-
* COMMONMARK is the default defined by flexmark-java. It's defined here so it can be used in both the parser and
40-
* the formatter, to keep the step idempotent.
41-
*/
42-
private static final String DEFAULT_EMULATION_PROFILE = "COMMONMARK";
43-
4436
/** Creates a formatter step for the default version. */
4537
public static FormatterStep create(Provisioner provisioner) {
4638
return create(defaultVersion(), provisioner);
@@ -70,87 +62,9 @@ private static class State implements Serializable {
7062

7163
FormatterFunc createFormat() throws Exception {
7264
final ClassLoader classLoader = jarState.getClassLoader();
73-
74-
// flexmark-java has a separate parser and renderer (formatter)
75-
// this is build from the example in https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter
76-
77-
// first we need to create the parser and find the parse method
78-
final Class<?> parserClazz = classLoader.loadClass("com.vladsch.flexmark.parser.Parser");
79-
final Class<?> parserBuilderClazz = classLoader.loadClass("com.vladsch.flexmark.parser.Parser$Builder");
80-
final Class<?> parserEmulationProfileClazz = classLoader.loadClass("com.vladsch.flexmark.parser.ParserEmulationProfile");
81-
final Class<?> dataHolderClazz = classLoader.loadClass("com.vladsch.flexmark.util.data.DataHolder");
82-
final Class<?> dataKeyClazz = classLoader.loadClass("com.vladsch.flexmark.util.data.DataKey");
83-
final Object parserEmulationProfile = parserEmulationProfileClazz.getField(DEFAULT_EMULATION_PROFILE).get(null);
84-
final Object parserOptions = buildParserOptions(classLoader, parserClazz, dataHolderClazz, dataKeyClazz, parserEmulationProfile);
85-
final Object parserBuilder = parserClazz.getMethod("builder", dataHolderClazz).invoke(null, parserOptions);
86-
final Object parser = parserBuilderClazz.getMethod("build").invoke(parserBuilder);
87-
final Method parseMethod = parserClazz.getMethod("parse", String.class);
88-
89-
// now we can create the formatter and find the render method
90-
final Class<?> formatterClazz = classLoader.loadClass("com.vladsch.flexmark.formatter.Formatter");
91-
final Class<?> nodeClazz = classLoader.loadClass("com.vladsch.flexmark.util.ast.Node");
92-
final Class<?> formatterBuilderClazz = classLoader.loadClass("com.vladsch.flexmark.formatter.Formatter$Builder");
93-
final Object formatterOptions = buildFormatterOptions(
94-
classLoader, parserClazz, formatterClazz, dataKeyClazz, dataHolderClazz, parserOptions, parserEmulationProfile);
95-
final Object formatterBuilder = formatterClazz.getMethod("builder", dataHolderClazz).invoke(null, formatterOptions);
96-
final Object formatter = formatterBuilderClazz.getMethod("build").invoke(formatterBuilder);
97-
final Method renderMethod = formatterClazz.getMethod("render", nodeClazz);
98-
99-
// the input must be parsed by the parser and then rendered by the formatter
100-
return input -> (String) renderMethod.invoke(formatter, parseMethod.invoke(parser, input));
101-
}
102-
103-
private Object buildParserOptions(
104-
ClassLoader classLoader,
105-
Class<?> parserClazz,
106-
Class<?> dataHolderClazz,
107-
Class<?> dataKeyClazz,
108-
Object parserEmulationProfile) throws Exception {
109-
final Class<?> pegdownOptionsAdapterClazz = classLoader.loadClass("com.vladsch.flexmark.profile.pegdown.PegdownOptionsAdapter");
110-
final Class<?> pegdownExtensionsClazz = classLoader.loadClass("com.vladsch.flexmark.parser.PegdownExtensions");
111-
final Class<?> extensionClazz = classLoader.loadClass("com.vladsch.flexmark.util.misc.Extension");
112-
final Class<?> mutableDataHolderClazz = classLoader.loadClass("com.vladsch.flexmark.util.data.MutableDataHolder");
113-
114-
final int pegDownExtensionsConstantAll = pegdownExtensionsClazz.getField("ALL").getInt(null);
115-
final Object extensions = Array.newInstance(extensionClazz, 0);
116-
final Class<?> extensionArrayClazz = extensions.getClass();
117-
118-
final Object parserOptions = pegdownOptionsAdapterClazz
119-
.getMethod("flexmarkOptions", Integer.TYPE, extensionArrayClazz)
120-
.invoke(null, pegDownExtensionsConstantAll, extensions);
121-
final Object mutableParserOptions = dataHolderClazz.getMethod("toMutable").invoke(parserOptions);
122-
final Object parserEmulationProfileKey = parserClazz.getField("PARSER_EMULATION_PROFILE").get(null);
123-
final Method mutableDataHolderSetMethod = mutableDataHolderClazz.getMethod("set", dataKeyClazz, Object.class);
124-
mutableDataHolderSetMethod.invoke(mutableParserOptions, parserEmulationProfileKey, parserEmulationProfile);
125-
return mutableParserOptions;
126-
}
127-
128-
/**
129-
* Creates the formatter options, copies the parser extensions and changes defaults that make sense for a formatter.
130-
* See: https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options
131-
*/
132-
private Object buildFormatterOptions(
133-
ClassLoader classLoader,
134-
Class<?> parserClazz,
135-
Class<?> formatterClazz,
136-
Class<?> dataKeyClazz,
137-
Class<?> dataHolderClazz,
138-
Object parserOptions,
139-
Object parserEmulationProfile) throws Exception {
140-
final Class<?> mutableDataSetClazz = classLoader.loadClass("com.vladsch.flexmark.util.data.MutableDataSet");
141-
final Object formatterOptions = mutableDataSetClazz.getConstructor().newInstance();
142-
final Method mutableDataSetMethodSet = mutableDataSetClazz.getMethod("set", dataKeyClazz, Object.class);
143-
144-
// copy the parser extensions like the example in https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter
145-
final Object parserExtensions = parserClazz.getField("EXTENSIONS").get(null);
146-
final Object copiedExtensions = dataKeyClazz.getMethod("get", dataHolderClazz).invoke(parserExtensions, parserOptions);
147-
mutableDataSetMethodSet.invoke(formatterOptions, parserExtensions, copiedExtensions);
148-
149-
// use the same emulation profile for the parser and the formatted, to make sure the step is idempotent
150-
final Object formatterEmulationProfile = formatterClazz.getField("FORMATTER_EMULATION_PROFILE").get(null);
151-
mutableDataSetMethodSet.invoke(formatterOptions, formatterEmulationProfile, parserEmulationProfile);
152-
153-
return formatterOptions;
65+
final Class<?> formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.markdown.FlexmarkFormatterFunc");
66+
final Constructor<?> constructor = formatterFunc.getConstructor();
67+
return (FormatterFunc) constructor.newInstance();
15468
}
15569

15670
}

0 commit comments

Comments
 (0)