Skip to content

Commit e686bbf

Browse files
authored
Add more configuration options for flexmark (#2713)
2 parents b48fed7 + 20e61e2 commit e686bbf

File tree

14 files changed

+285
-34
lines changed

14 files changed

+285
-34
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1919
### Fixed
2020
* palantirJavaFormat is no longer arbitrarily set to outdated versions on Java 17, latest available version is always used ([#2686](https://github.com/diffplug/spotless/pull/2686) fixes [#2685](https://github.com/diffplug/spotless/issues/2685))
2121
* Use Provider API for Gradle properties. ([#2718](https://github.com/diffplug/spotless/pull/2718)
22+
### Added
23+
* new options to customize Flexmark, e.g. to allow YAML front matter ([#2616](https://github.com/diffplug/spotless/issues/2616))
2224
### Removed
2325
* **BREAKING** Drop support for older Ktlint versions. ([#2711](https://github.com/diffplug/spotless/pull/2711))
2426

lib/src/flexmark/java/com/diffplug/spotless/glue/markdown/FlexmarkFormatterFunc.java

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 DiffPlug
2+
* Copyright 2021-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,12 @@
1515
*/
1616
package com.diffplug.spotless.glue.markdown;
1717

18+
import java.lang.reflect.Field;
19+
import java.lang.reflect.Method;
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
1824
import com.vladsch.flexmark.formatter.Formatter;
1925
import com.vladsch.flexmark.parser.Parser;
2026
import com.vladsch.flexmark.parser.ParserEmulationProfile;
@@ -23,32 +29,50 @@
2329
import com.vladsch.flexmark.util.ast.Document;
2430
import com.vladsch.flexmark.util.data.MutableDataHolder;
2531
import com.vladsch.flexmark.util.data.MutableDataSet;
32+
import com.vladsch.flexmark.util.misc.Extension;
2633

2734
import com.diffplug.spotless.FormatterFunc;
35+
import com.diffplug.spotless.markdown.FlexmarkConfig;
2836

2937
/**
3038
* The formatter function for <a href="https://github.com/vsch/flexmark-java">flexmark-java</a>.
3139
*/
3240
public class FlexmarkFormatterFunc implements FormatterFunc {
3341

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";
42+
private static final Map<String, String> KNOWN_EXTENSIONS = new HashMap<>();
43+
static {
44+
// using strings to maximize compatibility with older flexmark versions
45+
KNOWN_EXTENSIONS.put("Abbreviation", "com.vladsch.flexmark.ext.abbreviation.AbbreviationExtension");
46+
KNOWN_EXTENSIONS.put("Admonition", "com.vladsch.flexmark.ext.admonition.AdmonitionExtension");
47+
KNOWN_EXTENSIONS.put("Aside", "com.vladsch.flexmark.ext.aside.AsideExtension");
48+
KNOWN_EXTENSIONS.put("Attributes", "com.vladsch.flexmark.ext.attributes.AttributesExtension");
49+
KNOWN_EXTENSIONS.put("Definition", "com.vladsch.flexmark.ext.definition.DefinitionExtension");
50+
KNOWN_EXTENSIONS.put("Emoji", "com.vladsch.flexmark.ext.emoji.EmojiExtension");
51+
KNOWN_EXTENSIONS.put("EnumeratedReference", "com.vladsch.flexmark.ext.enumerated.reference.EnumeratedReferenceExtension");
52+
KNOWN_EXTENSIONS.put("Footnote", "com.vladsch.flexmark.ext.footnotes.FootnoteExtension");
53+
KNOWN_EXTENSIONS.put("GitLab", "com.vladsch.flexmark.ext.gitlab.GitLabExtension");
54+
KNOWN_EXTENSIONS.put("JekyllFrontMatter", "com.vladsch.flexmark.ext.jekyll.front.matter.JekyllFrontMatterExtension");
55+
KNOWN_EXTENSIONS.put("JekyllTag", "com.vladsch.flexmark.ext.jekyll.tag.JekyllTagExtension");
56+
KNOWN_EXTENSIONS.put("Macros", "com.vladsch.flexmark.ext.macros.MacrosExtension");
57+
KNOWN_EXTENSIONS.put("SimToc", "com.vladsch.flexmark.ext.toc.SimTocExtension");
58+
KNOWN_EXTENSIONS.put("Tables", "com.vladsch.flexmark.ext.tables.TablesExtension");
59+
KNOWN_EXTENSIONS.put("TaskList", "com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension");
60+
KNOWN_EXTENSIONS.put("WikiLink", "com.vladsch.flexmark.ext.wikilink.WikiLinkExtension");
61+
KNOWN_EXTENSIONS.put("YamlFrontMatter", "com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension");
62+
}
3963

4064
private final Parser parser;
4165
private final Formatter formatter;
4266

43-
public FlexmarkFormatterFunc() {
67+
public FlexmarkFormatterFunc(FlexmarkConfig config) {
4468
// flexmark-java has a separate parser and renderer (formatter)
4569
// this is build from the example in https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter
4670

4771
// The emulation profile generally determines the markdown flavor. We use the same one for both the parser and
4872
// the formatter, to make sure this formatter func is idempotent.
49-
final ParserEmulationProfile emulationProfile = ParserEmulationProfile.valueOf(DEFAULT_EMULATION_PROFILE);
73+
final ParserEmulationProfile emulationProfile = ParserEmulationProfile.valueOf(config.getEmulationProfile());
5074

51-
final MutableDataHolder parserOptions = createParserOptions(emulationProfile);
75+
final MutableDataHolder parserOptions = createParserOptions(emulationProfile, config);
5276
final MutableDataHolder formatterOptions = createFormatterOptions(parserOptions, emulationProfile);
5377

5478
parser = Parser.builder(parserOptions).build();
@@ -62,12 +86,63 @@ public FlexmarkFormatterFunc() {
6286
* @param emulationProfile the emulation profile (or flavor of markdown) the parser should use
6387
* @return the created parser options
6488
*/
65-
private static MutableDataHolder createParserOptions(ParserEmulationProfile emulationProfile) {
66-
final MutableDataHolder parserOptions = PegdownOptionsAdapter.flexmarkOptions(PegdownExtensions.ALL).toMutable();
89+
private static MutableDataHolder createParserOptions(ParserEmulationProfile emulationProfile, FlexmarkConfig config) {
90+
int pegdownExtensions = buildPegdownExtensions(config.getPegdownExtensions());
91+
Extension[] extensions = buildExtensions(config.getExtensions());
92+
final MutableDataHolder parserOptions = PegdownOptionsAdapter.flexmarkOptions(
93+
pegdownExtensions, extensions).toMutable();
6794
parserOptions.set(Parser.PARSER_EMULATION_PROFILE, emulationProfile);
6895
return parserOptions;
6996
}
7097

98+
/**
99+
* Loads all listed pegdown extensions by using the constants defined in {@link PegdownExtensions}.
100+
* Additionally, pure digit strings are directly converted to allow highly customized configurations.
101+
*
102+
* @param config the string-array configuration for pegdown
103+
* @return bit-wise or'd extensions
104+
*/
105+
private static int buildPegdownExtensions(List<String> config) {
106+
int extensions = PegdownExtensions.NONE;
107+
for (String str : config) {
108+
if (str.matches("\\d+")) {
109+
extensions |= Integer.parseInt(str);
110+
} else if (str.matches("(0x|0X)?[a-fA-F0-9]+")) {
111+
extensions |= Integer.decode(str);
112+
} else {
113+
try {
114+
Field field = PegdownExtensions.class.getField(str);
115+
extensions |= field.getInt(null);
116+
} catch (ReflectiveOperationException e) {
117+
throw new IllegalArgumentException("Unknown PegdownExtension '" + str + "'");
118+
}
119+
}
120+
}
121+
return extensions;
122+
}
123+
124+
/**
125+
* Loads all listed extensions by looking up the implementation class (optionally resolving shortcuts
126+
* for known extensions) and calling the conventional create-method.
127+
*
128+
* @param config the string-list of the configured extensions
129+
* @return the array of Extension instances
130+
*/
131+
private static Extension[] buildExtensions(List<String> config) {
132+
Extension[] extensions = new Extension[config.size()];
133+
for (int i = 0; i < extensions.length; i++) {
134+
String className = KNOWN_EXTENSIONS.getOrDefault(config.get(i), config.get(i));
135+
try {
136+
Class<?> c = Extension.class.getClassLoader().loadClass(className);
137+
Method create = c.getMethod("create");
138+
extensions[i] = Extension.class.cast(create.invoke(null));
139+
} catch (ReflectiveOperationException e) {
140+
throw new IllegalArgumentException("Unknown flexmark extension '" + config.get(i) + "'");
141+
}
142+
}
143+
return extensions;
144+
}
145+
71146
/**
72147
* Creates the formatter options, copies the parser extensions and changes defaults that make sense for a formatter.
73148
* See: https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options
@@ -76,7 +151,8 @@ private static MutableDataHolder createParserOptions(ParserEmulationProfile emul
76151
* @param emulationProfile the emulation profile (or flavor of markdown) the formatter should use
77152
* @return the created formatter options
78153
*/
79-
private static MutableDataHolder createFormatterOptions(MutableDataHolder parserOptions, ParserEmulationProfile emulationProfile) {
154+
private static MutableDataHolder createFormatterOptions(MutableDataHolder parserOptions,
155+
ParserEmulationProfile emulationProfile) {
80156
final MutableDataHolder formatterOptions = new MutableDataSet();
81157
formatterOptions.set(Parser.EXTENSIONS, Parser.EXTENSIONS.get(parserOptions));
82158
formatterOptions.set(Formatter.FORMATTER_EMULATION_PROFILE, emulationProfile);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2025 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.markdown;
17+
18+
import java.io.Serializable;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
public class FlexmarkConfig implements Serializable {
23+
private static final long serialVersionUID = 1L;
24+
25+
/**
26+
* The emulation profile is used by both the parser and the formatter and generally determines the markdown flavor.
27+
* COMMONMARK is the default defined by flexmark-java.
28+
*/
29+
private String emulationProfile = "COMMONMARK";
30+
private List<String> pegdownExtensions = List.of("ALL");
31+
private List<String> extensions = new ArrayList<>();
32+
33+
public String getEmulationProfile() {
34+
return emulationProfile;
35+
}
36+
37+
public void setEmulationProfile(String emulationProfile) {
38+
this.emulationProfile = emulationProfile;
39+
}
40+
41+
public List<String> getPegdownExtensions() {
42+
return pegdownExtensions;
43+
}
44+
45+
public void setPegdownExtensions(List<String> pegdownExtensions) {
46+
this.pegdownExtensions = pegdownExtensions;
47+
}
48+
49+
public List<String> getExtensions() {
50+
return extensions;
51+
}
52+
53+
public void setExtensions(List<String> extensions) {
54+
this.extensions = extensions;
55+
}
56+
57+
}

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,24 @@ public final class FlexmarkStep implements Serializable {
3434
public static final String NAME = "flexmark-java";
3535

3636
private final JarState.Promised jarState;
37+
private final FlexmarkConfig config;
3738

38-
private FlexmarkStep(JarState.Promised jarState) {
39+
private FlexmarkStep(JarState.Promised jarState, FlexmarkConfig config) {
3940
this.jarState = jarState;
41+
this.config = config;
4042
}
4143

4244
/** Creates a formatter step for the default version. */
43-
public static FormatterStep create(Provisioner provisioner) {
44-
return create(defaultVersion(), provisioner);
45+
public static FormatterStep create(Provisioner provisioner, FlexmarkConfig config) {
46+
return create(defaultVersion(), provisioner, config);
4547
}
4648

4749
/** Creates a formatter step for the given version. */
48-
public static FormatterStep create(String version, Provisioner provisioner) {
50+
public static FormatterStep create(String version, Provisioner provisioner, FlexmarkConfig config) {
4951
Objects.requireNonNull(version, "version");
5052
Objects.requireNonNull(provisioner, "provisioner");
5153
return FormatterStep.create(NAME,
52-
new FlexmarkStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner))),
54+
new FlexmarkStep(JarState.promise(() -> JarState.from(MAVEN_COORDINATE + version, provisioner)), config),
5355
FlexmarkStep::equalityState,
5456
State::createFormat);
5557
}
@@ -59,24 +61,26 @@ public static String defaultVersion() {
5961
}
6062

6163
private State equalityState() {
62-
return new State(jarState.get());
64+
return new State(jarState.get(), config);
6365
}
6466

6567
private static class State implements Serializable {
6668
@Serial
6769
private static final long serialVersionUID = 1L;
6870

6971
private final JarState jarState;
72+
private final FlexmarkConfig config;
7073

71-
State(JarState jarState) {
74+
State(JarState jarState, FlexmarkConfig config) {
7275
this.jarState = jarState;
76+
this.config = config;
7377
}
7478

7579
FormatterFunc createFormat() throws Exception {
7680
final ClassLoader classLoader = jarState.getClassLoader();
7781
final Class<?> formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.markdown.FlexmarkFormatterFunc");
78-
final Constructor<?> constructor = formatterFunc.getConstructor();
79-
return (FormatterFunc) constructor.newInstance();
82+
final Constructor<?> constructor = formatterFunc.getConstructor(FlexmarkConfig.class);
83+
return (FormatterFunc) constructor.newInstance(config);
8084
}
8185
}
8286
}

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1010
* Bump default `cleanthat` version to latest `2.23` -> `2.24`. ([#2620](https://github.com/diffplug/spotless/pull/2620))
1111
### Fixed
1212
- palantirJavaFormat is no longer arbitrarily set to outdated versions on Java 17, latest available version is always used ([#2686](https://github.com/diffplug/spotless/pull/2686) fixes [#2685](https://github.com/diffplug/spotless/issues/2685))
13+
### Added
14+
- new options to customize Flexmark, e.g. to allow YAML front matter ([#2616](https://github.com/diffplug/spotless/issues/2616))
1315
### Removed
1416
* **BREAKING** Drop support for older Ktlint versions. ([#2711](https://github.com/diffplug/spotless/pull/2711))
1517

plugin-gradle/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,10 @@ spotless {
736736
737737
[homepage](https://github.com/vsch/flexmark-java). Flexmark is a flexible Commonmark/Markdown parser that can be used to format Markdown files. It supports different [flavors of Markdown](https://github.com/vsch/flexmark-java#markdown-processor-emulation) and [many formatting options](https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options).
738738
739-
Currently, none of the available options can be configured yet. It uses only the default options together with `COMMONMARK` as `FORMATTER_EMULATION_PROFILE`.
739+
The default configuration uses a pegdown compatible parser with `COMMONMARK` as `FORMATTER_EMULATION_PROFILE`.
740+
You can change the `emulationProfile` to one of the other [supported profiles](https://github.com/vsch/flexmark-java/blob/master/flexmark/src/main/java/com/vladsch/flexmark/parser/ParserEmulationProfile.java).
741+
The `pegdownExtensions` can be configured as a list of [constants](https://github.com/vsch/flexmark-java/blob/master/flexmark/src/main/java/com/vladsch/flexmark/parser/PegdownExtensions.java) or as a custom bitset as an integer.
742+
Any other `extension` can be configured using either the simple name as shown in the example or using a full-qualified class name.
740743
741744
To apply flexmark to all of the `.md` files in your project, use this snippet:
742745
@@ -745,6 +748,9 @@ spotless {
745748
flexmark {
746749
target '**/*.md' // you have to set the target manually
747750
flexmark() // or flexmark('0.64.8') // version is optional
751+
.emulationProfile('COMMONMARK') // optional
752+
.pegdownExtensions('ALL') // optional
753+
.extensions('YamlFrontMatter') // optional
748754
}
749755
}
750756
```

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FlexmarkExtension.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 DiffPlug
2+
* Copyright 2023-2025 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,11 +15,13 @@
1515
*/
1616
package com.diffplug.gradle.spotless;
1717

18+
import java.util.List;
1819
import java.util.Objects;
1920

2021
import javax.inject.Inject;
2122

2223
import com.diffplug.spotless.FormatterStep;
24+
import com.diffplug.spotless.markdown.FlexmarkConfig;
2325
import com.diffplug.spotless.markdown.FlexmarkStep;
2426

2527
public class FlexmarkExtension extends FormatExtension {
@@ -50,14 +52,35 @@ protected void setupTask(SpotlessTask task) {
5052
public class FlexmarkFormatterConfig {
5153

5254
private final String version;
55+
private final FlexmarkConfig config = new FlexmarkConfig();
5356

5457
FlexmarkFormatterConfig(String version) {
5558
this.version = Objects.requireNonNull(version);
5659
addStep(createStep());
5760
}
5861

62+
public FlexmarkFormatterConfig emulationProfile(String emulationProfile) {
63+
this.config.setEmulationProfile(emulationProfile);
64+
return this;
65+
}
66+
67+
public FlexmarkFormatterConfig pegdownExtensions(int pegdownExtensions) {
68+
this.config.setPegdownExtensions(List.of(Integer.toString(pegdownExtensions)));
69+
return this;
70+
}
71+
72+
public FlexmarkFormatterConfig pegdownExtensions(String... pegdownExtensions) {
73+
this.config.setPegdownExtensions(List.of(pegdownExtensions));
74+
return this;
75+
}
76+
77+
public FlexmarkFormatterConfig extensions(String... extensions) {
78+
this.config.setExtensions(List.of(extensions));
79+
return this;
80+
}
81+
5982
private FormatterStep createStep() {
60-
return FlexmarkStep.create(this.version, provisioner());
83+
return FlexmarkStep.create(this.version, provisioner(), config);
6184
}
6285
}
6386

0 commit comments

Comments
 (0)