Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .run/ZnaiCliApp.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ZnaiCliApp" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="org.testingisdocumenting.znai.cli.ZnaiCliApp" />
<module name="znai-cli" />
<option name="PROGRAM_PARAMETERS" value="preview" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/znai-docs/znai" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.testingisdocumenting.znai.cli.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
8 changes: 8 additions & 0 deletions znai-cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@
<artifactId>webtau-core-groovy</artifactId>
<scope>test</scope>
</dependency>
<!-- this dependency allows to preview znai docs -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<classifier>sources</classifier>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import java.util.List;
import java.util.Map;

public class MermaidFencePlugin implements FencePlugin {
public class MermaidFencePlugin extends MermaidPluginBase implements FencePlugin {
private String content;

@Override
Expand All @@ -51,6 +51,7 @@ public PluginParamsDefinition parameters() {
public PluginResult process(ComponentsRegistry componentsRegistry, Path markupPath, PluginParams pluginParams, String content) {
this.content = content;
Map<String, Object> props = new LinkedHashMap<>(pluginParams.getOpts().toMap());
processIconPacks(componentsRegistry, props);
props.put("mermaid", content);

return PluginResult.docElement("Mermaid", props);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import java.util.Map;
import java.util.stream.Stream;

public class MermaidIncludePlugin implements IncludePlugin {
public class MermaidIncludePlugin extends MermaidPluginBase implements IncludePlugin {
private Path mermaidPath;
private String content;

Expand All @@ -58,7 +58,7 @@ public PluginResult process(ComponentsRegistry componentsRegistry, ParserHandler

Map<String, Object> props = new LinkedHashMap<>(pluginParams.getOpts().toMap());
props.put("mermaid", content);

processIconPacks(componentsRegistry, props);
return PluginResult.docElement("Mermaid", props);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2026 znai maintainers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.testingisdocumenting.znai.diagrams.mermaid;

import org.testingisdocumenting.znai.core.AuxiliaryFile;
import org.testingisdocumenting.znai.core.ComponentsRegistry;
import org.testingisdocumenting.znai.extensions.Plugin;
import org.testingisdocumenting.znai.resources.ResourcesResolver;
import org.testingisdocumenting.znai.structure.DocStructure;
import org.testingisdocumenting.znai.utils.UrlUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

abstract class MermaidPluginBase implements Plugin {
protected Map<AuxiliaryFile, Boolean> additionalAuxiliaryFiles = new HashMap<>();

public Stream<AuxiliaryFile> auxiliaryFiles(ComponentsRegistry componentsRegistry) {
return additionalAuxiliaryFiles.entrySet().stream().filter(e -> e.getValue() == Boolean.FALSE).map(Map.Entry::getKey);
}

protected void processIconPacks(ComponentsRegistry componentsRegistry, Map<String, Object> props) {
ResourcesResolver resourcesResolver = componentsRegistry.resourceResolver();
DocStructure docStructure = componentsRegistry.docStructure();
if (!props.containsKey("iconpacks")) {
return;
}
if ((props.get("iconpacks") instanceof List<?>)) {
List<?> iconPacks = (List<?>) props.get("iconpacks");
iconPacks.forEach(iconPack -> {
tweakIconpackUrl(resourcesResolver, docStructure, iconPack);
}
);
}
}

private void tweakIconpackUrl(ResourcesResolver resourcesResolver, DocStructure docStructure, Object iconPack) {
if (!(iconPack instanceof Map<?, ?>)) {
return;
}
@SuppressWarnings("unchecked") Map<String, String> iconPackMap = (Map<String, String>) iconPack;
if (!iconPackMap.containsKey("name")) {
throw new IllegalArgumentException("iconpack name is missing");
}
if (iconPackMap.containsKey("url")) {
String url = iconPackMap.get("url");
if (!UrlUtils.isExternal(url)) {
AuxiliaryFile auxiliaryFile = resourcesResolver.runtimeAuxiliaryFile(url);
additionalAuxiliaryFiles.put(auxiliaryFile, false);
url = docStructure.fullUrl(auxiliaryFile.getDeployRelativePath().toString());
}
iconPackMap.put("url", url);
} else {
throw new IllegalArgumentException("iconpack url is missing");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

class MermaidPluginParams {
static final PluginParamsDefinition definition = new PluginParamsDefinition()
.add("wide", PluginParamType.BOOLEAN, "use all the horizontal space for the diagram", "true");

.add("wide", PluginParamType.BOOLEAN, "use all the horizontal space for the diagram",
"true")
.add("iconpacks", PluginParamType.LIST_OR_OBJECT, "icon packs to use for the diagram",
"[{name : \"logos\", url : \"https://unpkg.com/@iconify-json/logos@1/icons.json\" }]");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2026 znai maintainers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.testingisdocumenting.znai.diagrams.mermaid

import org.testingisdocumenting.znai.extensions.include.PluginsTestUtils
import org.testingisdocumenting.znai.extensions.PluginParamsFactory
import org.junit.Test

import static org.testingisdocumenting.znai.parser.TestComponentsRegistry.TEST_COMPONENTS_REGISTRY
import static org.testingisdocumenting.webtau.Matchers.code
import static org.testingisdocumenting.webtau.Matchers.throwException
class MermaidFencePluginTest {
static PluginParamsFactory pluginParamsFactory = TEST_COMPONENTS_REGISTRY.pluginParamsFactory()
@Test
void "should process mermaid diagram without iconpacks parameter"() {
def mermaidContent = '''graph TD
A[Start] --> B[Process]
B --> C[End]'''

def elements = process(mermaidContent, [wide: true])

elements.should == [
mermaid: mermaidContent,
wide: true,
]
}

@Test
void "should process mermaid diagram with iconpacks parameter"() {
def mermaidContent = '''graph TD
A[Start] --> B[Process]
B --> C[End]'''

def elements = process(mermaidContent, [iconpacks: [[name: "test", url: "/test-icons.json"]]]
)

elements.should == [
mermaid: mermaidContent,
iconpacks: [[name: "test", url: "/test-doc/test-icons.json"]]]
}

@Test
void "should process simple mermaid diagram without any parameters"() {
def mermaidContent = '''graph TD
A --> B'''

def elements = process(mermaidContent, Collections.emptyMap())

elements.should == [
mermaid: mermaidContent
]
}


@Test
void "should throw exception when iconpack missing name attribute"() {
def mermaidContent = '''graph TD
A[Start] --> B[Process]
B --> C[End]'''

code {
process(mermaidContent, [iconpacks: [[url: "/test-icons.json"]]])
} should throwException(IllegalArgumentException.class, "iconpack name is missing")
}

@Test
void "should throw exception when iconpack missing url attribute"() {
def mermaidContent = '''graph TD
A[Start] --> B[Process]
B --> C[End]'''

code {
process(mermaidContent, [iconpacks: [[name: "test"]]])
} should throwException(IllegalArgumentException.class, "iconpack url is missing")
}

private static def process(String content, Map<String, ?> params) {
def result = PluginsTestUtils.processFenceAndGetProps(pluginParamsFactory.create("mermaid", "", params), content)
return result
}
}
34 changes: 34 additions & 0 deletions znai-docs/znai/visuals/mermaid-diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,38 @@ sequenceDiagram
end
end
```
# Registering icon packs

Mermaid `architecture-beta` offers the possibility of displaying custom icons.

Use
```
mermaid {iconpacks : [{ name : "logos", url : "https://unpkg.com/@iconify-json/logos@1/icons.json" }]}
```
to register the `@iconify-json/logos@1` icon pack with the name `logos`.


Use
```
mermaid {iconpacks : [{ name : "logos", url : "mermaid/demo_icons.json" }]}
```
to register the `icons.json` icon pack with the name `logos`.

This assumes that

- the `icons.json` file is in a subdirectory `mermaid` of the directory containing the current page.


```mermaid {iconpacks : [{ name : "logos", url : "mermaid/demo_icons.json" }]}
architecture-beta
group api(logos:aws-lambda)[API]

service db(logos:aws-aurora)[Database] in api
service disk1(logos:aws-glacier)[Storage] in api
service disk2(logos:aws-s3)[Storage] in api
service server(logos:aws-ec2)[Server] in api

db:L <-[hosts]- R:server
disk1:T <-[mounts]- B:server
disk2:T <-[mounts]- B:db
```
22 changes: 22 additions & 0 deletions znai-docs/znai/visuals/mermaid/demo_icons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"prefix": "logos",
"icons": {
"aws-aurora": {
"body": "<defs><linearGradient id=\"SVGWTObRdcx\" x1=\"0%\" x2=\"100%\" y1=\"100%\" y2=\"0%\"><stop offset=\"0%\" stop-color=\"#2E27AD\"/><stop offset=\"100%\" stop-color=\"#527FFF\"/></linearGradient></defs><path fill=\"url(#SVGWTObRdcx)\" d=\"M0 0h256v256H0z\"/><path fill=\"#FFF\" d=\"M144.292 57.744h-9.626v-6.457h9.626V41.6h6.417v9.687h9.625v6.457h-9.625v9.687h-6.417zm38.502 35.518h-9.625v-6.458h9.625v-9.686h6.418v9.686h9.625v6.458h-9.625v9.687h-6.418zm-16.71 102.046c-6.128-15.57-20.272-29.803-35.75-35.973c15.478-6.167 29.622-20.4 35.75-35.973c6.129 15.573 20.272 29.806 35.747 35.973c-15.475 6.17-29.618 20.403-35.747 35.973m51.507-39.202c-22.569 0-48.298-25.893-48.298-48.605a3.22 3.22 0 0 0-3.209-3.228a3.22 3.22 0 0 0-3.208 3.228c0 22.712-25.733 48.605-48.302 48.605c-1.771 0-3.209 1.446-3.209 3.229a3.22 3.22 0 0 0 3.209 3.229c22.569 0 48.302 25.892 48.302 48.607a3.22 3.22 0 0 0 3.208 3.229a3.22 3.22 0 0 0 3.209-3.229c0-22.715 25.73-48.607 48.298-48.607a3.22 3.22 0 0 0 3.209-3.23a3.22 3.22 0 0 0-3.209-3.228M41.617 92.49c9.344 6.84 27.498 10.459 44.92 10.459s35.577-3.62 44.92-10.459v30.927c-4.623 6.186-21.626 12.282-44.278 12.282c-26.076 0-45.562-8.201-45.562-15.534zm44.92-25.059c27.831 0 44.92 8.463 44.92 14.53s-17.089 14.53-44.92 14.53s-44.92-8.463-44.92-14.53s17.089-14.53 44.92-14.53m44.92 121.183c0 7.443-19.216 15.77-44.936 15.77c-25.7 0-44.904-8.327-44.904-15.77v-20.632c9.459 7.22 27.976 11.042 45.745 11.042c12.356 0 24.305-1.756 33.632-4.946l-2.06-6.116c-8.682 2.968-19.896 4.605-31.572 4.605c-26.182 0-45.745-8.202-45.745-15.535v-25.898c9.433 7.206 27.87 11.023 45.562 11.023c18.956 0 35.214-3.93 44.278-10.152v9.69h6.417V81.961c0-13.632-26.448-20.988-51.337-20.988c-23.875 0-49.123 6.787-51.138 19.374H35.2v108.267c0 14.436 26.442 22.228 51.321 22.228c24.895 0 51.353-7.792 51.353-22.228v-11.401h-6.417z\"/>"
},
"aws-ec2": {
"body": "<defs><linearGradient id=\"SVGbGfcrvoV\" x1=\"0%\" x2=\"100%\" y1=\"100%\" y2=\"0%\"><stop offset=\"0%\" stop-color=\"#C8511B\"/><stop offset=\"100%\" stop-color=\"#F90\"/></linearGradient></defs><path fill=\"url(#SVGbGfcrvoV)\" d=\"M0 0h256v256H0z\"/><path fill=\"#FFF\" d=\"M86.4 169.6h80v-80h-80zm86.4-80h12.8V96h-12.8v12.8h12.8v6.4h-12.8v9.6h12.8v6.4h-12.8V144h12.8v6.4h-12.8v12.8h12.8v6.4h-12.8v.435a5.97 5.97 0 0 1-5.965 5.965h-.435v12.8H160V176h-12.8v12.8h-6.4V176h-9.6v12.8h-6.4V176H112v12.8h-6.4V176H92.8v12.8h-6.4V176h-.435A5.97 5.97 0 0 1 80 170.035v-.435h-9.6v-6.4H80v-12.8h-9.6V144H80v-12.8h-9.6v-6.4H80v-9.6h-9.6v-6.4H80V96h-9.6v-6.4H80v-.435a5.97 5.97 0 0 1 5.965-5.965h.435V70.4h6.4v12.8h12.8V70.4h6.4v12.8h12.8V70.4h6.4v12.8h9.6V70.4h6.4v12.8H160V70.4h6.4v12.8h.435a5.97 5.97 0 0 1 5.965 5.965zm-41.6 121.203a.4.4 0 0 1-.397.397H45.197a.4.4 0 0 1-.397-.397v-85.606a.4.4 0 0 1 .397-.397H64v-6.4H45.197a6.805 6.805 0 0 0-6.797 6.797v85.606a6.805 6.805 0 0 0 6.797 6.797h85.606a6.805 6.805 0 0 0 6.797-6.797V195.2h-6.4zm86.4-165.606v85.606a6.805 6.805 0 0 1-6.797 6.797H192v-6.4h18.803a.4.4 0 0 0 .397-.397V45.197a.4.4 0 0 0-.397-.397h-85.606a.4.4 0 0 0-.397.397V64h-6.4V45.197a6.805 6.805 0 0 1 6.797-6.797h85.606a6.805 6.805 0 0 1 6.797 6.797\"/>"
},
"aws-glacier": {
"body": "<defs><linearGradient id=\"SVGTHZ4wdZV\" x1=\"0%\" x2=\"100%\" y1=\"100%\" y2=\"0%\"><stop offset=\"0%\" stop-color=\"#1B660F\"/><stop offset=\"100%\" stop-color=\"#6CAE3E\"/></linearGradient></defs><path fill=\"url(#SVGTHZ4wdZV)\" d=\"M0 0h256v256H0z\"/><path fill=\"#FFF\" d=\"M48.212 66.919C50.91 76.375 82.075 86.4 123.202 86.4c41.055 0 72.185-9.99 74.972-19.433l.205-1.44C198.145 61.312 173.966 44.8 123.2 44.8c-50.774 0-74.947 16.516-75.18 20.73zm147.69 61.686l-1.226 8.65c10.752 2.646 12.682 1.03 12.701 1.014c.003-.077-.144-2.883-11.475-9.664m-49.731 81.18l.806 6.35c-7.67.973-15.677 1.465-23.795 1.465c-24.426 0-63.1-5.41-63.53-19.913L41.9 68.01q-.215-.936-.263-1.907l-.01-.067h.004c-.006-.148-.029-.288-.029-.436c0-11.09 31.786-27.2 81.6-27.2c49.811 0 81.6 16.11 81.6 27.2c0 .15-.029.298-.035.448h.003l-.006.032a10.7 10.7 0 0 1-.291 2.055l-7.604 53.638c10.71 6.112 15.965 10.887 16.794 15.315c.365 1.956-.118 3.79-1.392 5.31c-1.555 1.849-4.115 2.77-7.898 2.77c-2.819 0-6.332-.521-10.601-1.542l-.41 2.893l-6.336-.896l.522-3.687c-19.728-5.827-46.79-18.156-59.78-24.304c-1.334 1.108-3.02 1.799-4.883 1.799c-4.236 0-7.686-3.45-7.686-7.69s3.45-7.69 7.686-7.69c4.24 0 7.69 3.45 7.69 7.69c0 .045-.013.09-.013.135c11.04 5.212 38.314 17.728 57.895 23.65l8.156-57.532c-14.14 9.683-44.435 14.806-73.41 14.806c-29.028 0-59.37-5.139-73.48-14.85L66.02 197.007q.03.218.03.436c0 4.85 21.692 13.756 57.132 13.756c7.85 0 15.587-.476 22.989-1.414m58.521-24.604l7.588 7.584l-4.528 4.525l-8.49-8.49h-15.21l7.23 12.535l12.198 3.433l-1.735 6.157l-10.157-2.857l-2.774 10.361l-6.182-1.657l3.222-12.036l-7.533-13.065l-7.433 12.854l3.126 12.285l-6.198 1.578l-2.605-10.224l-10.359 2.774l-1.654-6.182l12.029-3.223l7.366-12.733h-15.005l-8.732 8.519l-4.468-4.583l7.556-7.37l-7.584-7.583l4.524-4.525l9.14 9.142h14.947l-7.629-13.193l-12.182-3.427l1.73-6.16l10.16 2.857l2.775-10.358l6.183 1.657l-3.23 12.055l7.543 13.043L185.745 166l-3.123-12.265l6.198-1.578l2.608 10.227l10.356-2.774l1.657 6.18l-12.05 3.228l-7.713 13.382h14.7l9.402-9.17l4.47 4.582z\"/>"
},
"aws-lambda": {
"body": "<defs><linearGradient id=\"SVGbGfcrvoV\" x1=\"0%\" x2=\"100%\" y1=\"100%\" y2=\"0%\"><stop offset=\"0%\" stop-color=\"#C8511B\"/><stop offset=\"100%\" stop-color=\"#F90\"/></linearGradient></defs><path fill=\"url(#SVGbGfcrvoV)\" d=\"M0 0h256v256H0z\"/><path fill=\"#FFF\" d=\"M89.624 211.2H49.89l43.945-91.853l19.912 40.992zm7.079-100.63a3.22 3.22 0 0 0-2.887-1.805h-.01a3.2 3.2 0 0 0-2.886 1.82L41.913 213.022a3.203 3.203 0 0 0 2.893 4.58l46.848-.001a3.21 3.21 0 0 0 2.9-1.83l25.65-54.08a3.18 3.18 0 0 0-.016-2.762zM207.985 211.2h-39.477L105.174 78.624a3.21 3.21 0 0 0-2.897-1.824h-25.83l.03-32h50.626l63.042 132.573a3.21 3.21 0 0 0 2.897 1.827h14.943zm3.208-38.4h-16.121L132.03 40.227a3.21 3.21 0 0 0-2.9-1.827H73.273a3.206 3.206 0 0 0-3.208 3.197l-.035 38.4c0 .851.333 1.664.94 2.265c.6.602 1.414.938 2.267.938h27.017l63.337 132.576a3.2 3.2 0 0 0 2.893 1.824h44.709a3.203 3.203 0 0 0 3.207-3.2V176c0-1.766-1.434-3.2-3.207-3.2\"/>"
},
"aws-s3": {
"body": "<defs><linearGradient id=\"SVGTHZ4wdZV\" x1=\"0%\" x2=\"100%\" y1=\"100%\" y2=\"0%\"><stop offset=\"0%\" stop-color=\"#1B660F\"/><stop offset=\"100%\" stop-color=\"#6CAE3E\"/></linearGradient></defs><path fill=\"url(#SVGTHZ4wdZV)\" d=\"M0 0h256v256H0z\"/><path fill=\"#FFF\" d=\"m194.675 137.256l1.229-8.652c11.33 6.787 11.478 9.59 11.475 9.667c-.02.016-1.952 1.629-12.704-1.015m-6.218-1.728c-19.584-5.926-46.857-18.438-57.894-23.654c0-.045.013-.086.013-.131c0-4.24-3.45-7.69-7.693-7.69c-4.237 0-7.687 3.45-7.687 7.69s3.45 7.69 7.687 7.69c1.862 0 3.552-.695 4.886-1.8c12.986 6.148 40.048 18.478 59.776 24.302l-7.801 55.059q-.033.225-.032.451c0 4.848-21.463 13.754-56.532 13.754c-35.44 0-57.13-8.906-57.13-13.754q0-.22-.028-.435l-16.3-119.062c14.108 9.712 44.454 14.85 73.478 14.85c28.979 0 59.273-5.12 73.41-14.802zM48 65.528c.23-4.21 24.428-20.73 75.2-20.73c50.764 0 74.966 16.516 75.2 20.73v1.437c-2.784 9.443-34.144 19.434-75.2 19.434c-41.127 0-72.503-10.023-75.2-19.479zm156.8.07c0-11.087-31.79-27.2-81.6-27.2c-49.812 0-81.6 16.113-81.6 27.2l.3 2.414l17.754 129.676c.426 14.503 39.1 19.91 63.526 19.91c30.31 0 62.512-6.969 62.928-19.9l7.668-54.07c4.265 1.02 7.776 1.542 10.595 1.542c3.785 0 6.345-.925 7.897-2.774c1.274-1.517 1.76-3.354 1.396-5.31c-.83-4.428-6.087-9.202-16.794-15.311l7.603-53.639z\"/>"
}
},
"width": 256,
"height": 256
}
Loading