Skip to content

Commit 3758c89

Browse files
plugins: user json based plugins (#1519)
1 parent d2de3d4 commit 3758c89

36 files changed

Lines changed: 1688 additions & 88 deletions

znai-core/src/main/java/org/testingisdocumenting/znai/extensions/Plugins.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@
2323
import org.testingisdocumenting.znai.utils.ServiceLoaderUtils;
2424

2525
import java.util.Collection;
26+
import java.util.LinkedHashMap;
2627
import java.util.Map;
2728
import java.util.Set;
2829

2930
import static java.util.stream.Collectors.joining;
30-
import static java.util.stream.Collectors.toMap;
3131

3232
public class Plugins {
3333
private static final Map<String, Plugin> includePluginsById = discoverIncludePlugins();
3434
private static final Map<String, Plugin> fencePluginsById = discoverFencePlugins();
3535
private static final Map<String, Plugin> inlineCodePluginsById = discoverInlinedCodePlugins();
3636

37+
private static final Set<String> builtInIncludeIds = Set.copyOf(includePluginsById.keySet());
38+
private static final Set<String> builtInFenceIds = Set.copyOf(fencePluginsById.keySet());
39+
3740
private static final PluginsTracker pluginsTracker = new PluginsTracker();
3841

3942
public static Map<String, ?> buildStatsMap() {
@@ -74,6 +77,35 @@ public static InlinedCodePlugin inlinedCodePluginById(String id) {
7477
pluginsTracker.inlineCodePlugins.createParamsTracker(id));
7578
}
7679

80+
public static void registerUserPlugin(Plugin plugin) {
81+
if (plugin instanceof IncludePlugin) {
82+
registerUserPlugin(includePluginsById, builtInIncludeIds, plugin);
83+
} else if (plugin instanceof FencePlugin) {
84+
registerUserPlugin(fencePluginsById, builtInFenceIds, plugin);
85+
} else {
86+
throw new IllegalArgumentException("unsupported plugin type: " + plugin.getClass().getCanonicalName());
87+
}
88+
}
89+
90+
public static void removeUserPlugin(String id) {
91+
if (builtInIncludeIds.contains(id) || builtInFenceIds.contains(id)) {
92+
throw new IllegalStateException("cannot remove built-in plugin <" + id + ">");
93+
}
94+
95+
includePluginsById.remove(id);
96+
fencePluginsById.remove(id);
97+
}
98+
99+
private static void registerUserPlugin(Map<String, Plugin> plugins, Set<String> builtInIds, Plugin plugin) {
100+
String id = plugin.id();
101+
if (builtInIds.contains(id)) {
102+
throw new IllegalStateException("plugin with id <" + id + "> is already registered: " +
103+
plugins.get(id).getClass().getCanonicalName());
104+
}
105+
106+
plugins.put(id, plugin);
107+
}
108+
77109
private static Plugin pluginById(Map<String, Plugin> plugins, String id) {
78110
final Plugin plugin = plugins.get(id);
79111
if (plugin == null) {
@@ -99,10 +131,13 @@ private static Map<String, Plugin> discoverInlinedCodePlugins() {
99131
private static <E extends Plugin> Map<String, Plugin> discoverPlugins(Class<E> pluginType) {
100132
final Set<E> list = ServiceLoaderUtils.load(pluginType);
101133

102-
final Map<String, Plugin> byId = list.stream().collect(toMap(Plugin::id, p -> p));
103-
if (byId.size() < list.size()) {
104-
throw new IllegalStateException("multiple plugins with the same id are detected. full list: \n" +
105-
renderListOfPlugins(list));
134+
final Map<String, Plugin> byId = new LinkedHashMap<>();
135+
for (E plugin : list) {
136+
Plugin prev = byId.put(plugin.id(), plugin);
137+
if (prev != null) {
138+
throw new IllegalStateException("multiple plugins with the same id are detected. full list: \n" +
139+
renderListOfPlugins(list));
140+
}
106141
}
107142

108143
return byId;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2026 znai maintainers
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+
17+
package org.testingisdocumenting.znai.resources;
18+
19+
import java.nio.file.Path;
20+
21+
public interface LocalResourcesResolver extends ResourcesResolver {
22+
void setCurrentFilePath(Path currentFilePath);
23+
24+
Path getCurrentFilePath();
25+
}

znai-core/src/main/java/org/testingisdocumenting/znai/resources/MultipleLocalLocationsResourceResolver.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import java.util.stream.Collectors;
2828
import java.util.stream.Stream;
2929

30-
public class MultipleLocalLocationsResourceResolver implements ResourcesResolver {
30+
public class MultipleLocalLocationsResourceResolver implements LocalResourcesResolver {
3131
private final TableOfContents toc;
3232
private final Path docRootPath;
3333
private final List<Path> lookupPaths;
@@ -98,7 +98,13 @@ private Stream<Path> allLocationsStream(String path) {
9898
return Stream.concat(relativeToCurrent, Stream.concat(absoluteLocation, lookedUpInLocations)).distinct();
9999
}
100100

101+
@Override
101102
public void setCurrentFilePath(Path currentFilePath) {
102103
this.currentFilePath.set(currentFilePath);
103104
}
105+
106+
@Override
107+
public Path getCurrentFilePath() {
108+
return currentFilePath.get();
109+
}
104110
}

znai-core/src/main/java/org/testingisdocumenting/znai/template/TextTemplate.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
import static freemarker.template.Configuration.VERSION_2_3_23;
2929

3030
public class TextTemplate {
31+
private static final Configuration SHARED_CONFIGURATION = new Configuration(VERSION_2_3_23);
32+
3133
private final Template template;
3234

3335
public TextTemplate(String templateName, String templateText) {
34-
Configuration cfg = new Configuration(VERSION_2_3_23);
3536
try {
36-
template = new Template(templateName, new StringReader(templateText), cfg);
37+
template = new Template(templateName, new StringReader(templateText), SHARED_CONFIGURATION);
3738
} catch (IOException e) {
3839
throw new RuntimeException(e);
3940
}

znai-core/src/test/groovy/org/testingisdocumenting/znai/parser/TestResourceResolver.groovy

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.testingisdocumenting.znai.parser
1919

20-
import org.testingisdocumenting.znai.resources.ResourcesResolver
20+
import org.testingisdocumenting.znai.resources.LocalResourcesResolver
2121
import org.testingisdocumenting.znai.utils.ResourceUtils
2222

2323
import javax.imageio.ImageIO
@@ -26,8 +26,9 @@ import java.nio.file.Path
2626
import java.nio.file.Paths
2727
import java.util.stream.Stream
2828

29-
class TestResourceResolver implements ResourcesResolver {
29+
class TestResourceResolver implements LocalResourcesResolver {
3030
private Path root
31+
private Path currentFilePath
3132

3233
TestResourceResolver(Path root) {
3334
this.root = root.toAbsolutePath()
@@ -88,4 +89,14 @@ class TestResourceResolver implements ResourcesResolver {
8889
boolean isLocalFile(String path) {
8990
return ResourceUtils.resourceStream(path) != null
9091
}
92+
93+
@Override
94+
void setCurrentFilePath(Path currentFilePath) {
95+
this.currentFilePath = currentFilePath
96+
}
97+
98+
@Override
99+
Path getCurrentFilePath() {
100+
return currentFilePath
101+
}
91102
}

znai-docs/znai/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"cssResources": ["custom.css", "plugins/javascript/theme-box.css"],
33
"jsResources": ["custom.js", "plugins/javascript/theme-box.js"],
44
"htmlResources": ["custom.html"],
5-
"htmlHeadResources": ["tracking.html"]
5+
"htmlHeadResources": ["tracking.html"],
6+
"plugins": ["plugins/themed-box-plugin.json", "plugins/custom-fence-block-plugin.json"]
67
}

0 commit comments

Comments
 (0)