Skip to content

Commit 8213e2e

Browse files
Adds an initial implementation of a static HTML export.
1 parent f4eeaf8 commit 8213e2e

File tree

4 files changed

+161
-48
lines changed

4 files changed

+161
-48
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@ out
2828
*.puml
2929
*.wsd
3030
*.sh
31+
!ui.sh
3132

32-
*.json
33+
*.json
34+
35+
src/main/resources/static.zip

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ test {
4747
sourceSets.main.resources {
4848
srcDirs = ['src/main/resources']
4949
include 'build.properties'
50+
include 'static.zip'
5051
}
5152

5253
jar {

src/main/java/com/structurizr/cli/export/ExportCommand.java

Lines changed: 107 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.structurizr.cli.export;
22

33
import com.structurizr.Workspace;
4+
import com.structurizr.autolayout.graphviz.GraphvizAutomaticLayout;
45
import com.structurizr.cli.AbstractCommand;
5-
import com.structurizr.dsl.StructurizrDslParser;
6+
import com.structurizr.documentation.Documentable;
67
import com.structurizr.export.*;
78
import com.structurizr.export.dot.DOTExporter;
89
import com.structurizr.export.ilograph.IlographExporter;
@@ -17,15 +18,15 @@
1718
import org.apache.commons.logging.Log;
1819
import org.apache.commons.logging.LogFactory;
1920

20-
import java.io.BufferedWriter;
21-
import java.io.File;
22-
import java.net.URL;
23-
import java.net.URLClassLoader;
21+
import java.io.*;
2422
import java.nio.charset.StandardCharsets;
2523
import java.nio.file.Files;
24+
import java.util.Base64;
2625
import java.util.Collection;
2726
import java.util.HashMap;
2827
import java.util.Map;
28+
import java.util.zip.ZipEntry;
29+
import java.util.zip.ZipInputStream;
2930

3031
public class ExportCommand extends AbstractCommand {
3132

@@ -41,6 +42,7 @@ public class ExportCommand extends AbstractCommand {
4142
private static final String DOT_FORMAT = "dot";
4243
private static final String ILOGRAPH_FORMAT = "ilograph";
4344
private static final String D2_FORMAT = "d2";
45+
private static final String STATIC_FORMAT = "static";
4446
private static final String CUSTOM_FORMAT = "fqcn";
4547

4648
private static final Map<String,Exporter> EXPORTERS = new HashMap<>();
@@ -112,72 +114,130 @@ public void run(String... args) throws Exception {
112114

113115
workspaceId = workspace.getId();
114116

115-
if (!JSON_FORMAT.equalsIgnoreCase(format)) {
116-
// only inline the theme amd create default views if the user wants a diagram export
117-
ThemeUtils.loadThemes(workspace);
118-
addDefaultViewsAndStyles(workspace);
119-
}
120-
121117
if (outputPath == null) {
122118
outputPath = new File(workspacePath.getCanonicalPath()).getParent();
123119
}
124120

125121
File outputDir = new File(outputPath);
126122
outputDir.mkdirs();
127123

128-
Exporter exporter = findExporter(format, workspacePath);
129-
if (exporter == null) {
130-
log.info(" - unknown export format: " + format);
124+
if (STATIC_FORMAT.equals(format)) {
125+
log.info(" - writing static site to " + outputDir.getAbsolutePath());
126+
unzip(getClass().getResourceAsStream("/static.zip"), outputPath);
127+
128+
// add default views if no views exist
129+
addDefaultViewsAndStyles(workspace);
130+
131+
// clear all documentation - this isn't supported by the static site
132+
workspace.getDocumentation().clear();
133+
workspace.getModel().getElements().stream().filter(e -> e instanceof Documentable).map(e -> (Documentable)e).forEach(e -> e.getDocumentation().clear());
134+
135+
// apply Graphviz locally - the static site can't do this
136+
File tmpdir = Files.createTempDirectory("graphviz").toFile();
137+
tmpdir.deleteOnExit();
138+
log.debug("Graphviz working directory is " + tmpdir.getAbsolutePath());
139+
new GraphvizAutomaticLayout(tmpdir).apply(workspace);
140+
141+
String json = WorkspaceUtils.toJson(workspace, false);
142+
String base64 = Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
143+
144+
writeToFile(
145+
new File(outputDir, "workspace.js"),
146+
String.format("const jsonAsString = '%s';", base64)
147+
);
148+
131149
} else {
132-
log.info(" - exporting with " + exporter.getClass().getSimpleName());
150+
if (!JSON_FORMAT.equalsIgnoreCase(format)) {
151+
// only inline the theme amd create default views if the user wants a diagram export
152+
ThemeUtils.loadThemes(workspace);
153+
addDefaultViewsAndStyles(workspace);
154+
}
133155

134-
if (exporter instanceof DiagramExporter) {
135-
DiagramExporter diagramExporter = (DiagramExporter) exporter;
156+
Exporter exporter = findExporter(format, workspacePath);
157+
if (exporter == null) {
158+
log.info(" - unknown export format: " + format);
159+
} else {
160+
log.info(" - exporting with " + exporter.getClass().getSimpleName());
136161

137-
if (workspace.getViews().isEmpty()) {
138-
log.info(" - the workspace contains no views");
139-
} else {
140-
Collection<Diagram> diagrams = diagramExporter.export(workspace);
162+
if (exporter instanceof DiagramExporter) {
163+
DiagramExporter diagramExporter = (DiagramExporter) exporter;
141164

142-
for (Diagram diagram : diagrams) {
143-
File file = new File(outputPath, String.format("%s-%s.%s", prefix(workspaceId), diagram.getKey(), diagram.getFileExtension()));
144-
writeToFile(file, diagram.getDefinition());
165+
if (workspace.getViews().isEmpty()) {
166+
log.info(" - the workspace contains no views");
167+
} else {
168+
Collection<Diagram> diagrams = diagramExporter.export(workspace);
145169

146-
if (diagram.getLegend() != null) {
147-
file = new File(outputPath, String.format("%s-%s-key.%s", prefix(workspaceId), diagram.getKey(), diagram.getFileExtension()));
148-
writeToFile(file, diagram.getLegend().getDefinition());
149-
}
170+
for (Diagram diagram : diagrams) {
171+
File file = new File(outputPath, String.format("%s-%s.%s", prefix(workspaceId), diagram.getKey(), diagram.getFileExtension()));
172+
writeToFile(file, diagram.getDefinition());
173+
174+
if (diagram.getLegend() != null) {
175+
file = new File(outputPath, String.format("%s-%s-key.%s", prefix(workspaceId), diagram.getKey(), diagram.getFileExtension()));
176+
writeToFile(file, diagram.getLegend().getDefinition());
177+
}
150178

151-
if (!diagram.getFrames().isEmpty()) {
152-
int index = 1;
153-
for (Diagram frame : diagram.getFrames()) {
154-
file = new File(outputPath, String.format("%s-%s-%s.%s", prefix(workspaceId), diagram.getKey(), index, diagram.getFileExtension()));
155-
writeToFile(file, frame.getDefinition());
156-
index++;
179+
if (!diagram.getFrames().isEmpty()) {
180+
int index = 1;
181+
for (Diagram frame : diagram.getFrames()) {
182+
file = new File(outputPath, String.format("%s-%s-%s.%s", prefix(workspaceId), diagram.getKey(), index, diagram.getFileExtension()));
183+
writeToFile(file, frame.getDefinition());
184+
index++;
185+
}
157186
}
158187
}
159188
}
160-
}
161-
} else if (exporter instanceof WorkspaceExporter) {
162-
WorkspaceExporter workspaceExporter = (WorkspaceExporter) exporter;
163-
WorkspaceExport export = workspaceExporter.export(workspace);
189+
} else if (exporter instanceof WorkspaceExporter) {
190+
WorkspaceExporter workspaceExporter = (WorkspaceExporter) exporter;
191+
WorkspaceExport export = workspaceExporter.export(workspace);
164192

165-
String filename;
193+
String filename;
166194

167-
if (THEME_FORMAT.equalsIgnoreCase(format)) {
168-
filename = workspacePath.getName().substring(0, workspacePath.getName().lastIndexOf('.')) + "-theme";
169-
} else {
170-
filename = workspacePath.getName().substring(0, workspacePath.getName().lastIndexOf('.'));
171-
}
195+
if (THEME_FORMAT.equalsIgnoreCase(format)) {
196+
filename = workspacePath.getName().substring(0, workspacePath.getName().lastIndexOf('.')) + "-theme";
197+
} else {
198+
filename = workspacePath.getName().substring(0, workspacePath.getName().lastIndexOf('.'));
199+
}
172200

173-
File file = new File(outputPath, String.format("%s.%s", filename, export.getFileExtension()));
174-
writeToFile(file, export.getDefinition());
201+
File file = new File(outputPath, String.format("%s.%s", filename, export.getFileExtension()));
202+
writeToFile(file, export.getDefinition());
203+
}
175204
}
176205
}
177206

178207
log.info(" - finished");
179208
}
180209

210+
private void unzip(InputStream inputStream, String destinationDirectory) {
211+
byte[] buffer = new byte[1024];
212+
213+
try {
214+
ZipInputStream zis = new ZipInputStream(inputStream);
215+
ZipEntry ze = zis.getNextEntry();
216+
while (ze != null) {
217+
if (!ze.isDirectory()) {
218+
String fileName = ze.getName();
219+
File destinationFile = new File(destinationDirectory + File.separator + fileName);
220+
221+
new File(destinationFile.getParent()).mkdirs();
222+
FileOutputStream fos = new FileOutputStream(destinationFile);
223+
int len;
224+
while ((len = zis.read(buffer)) > 0) {
225+
fos.write(buffer, 0, len);
226+
}
227+
fos.close();
228+
}
229+
zis.closeEntry();
230+
ze = zis.getNextEntry();
231+
}
232+
233+
zis.closeEntry();
234+
zis.close();
235+
inputStream.close();
236+
} catch (IOException e) {
237+
e.printStackTrace();
238+
}
239+
}
240+
181241
private Exporter findExporter(String format, File workspacePath) {
182242
if (EXPORTERS.containsKey(format.toLowerCase())) {
183243
return EXPORTERS.get(format.toLowerCase());
@@ -214,4 +274,4 @@ private void writeToFile(File file, String content) throws Exception {
214274
writer.close();
215275
}
216276

217-
}
277+
}

ui.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/zsh
2+
3+
# - this script merges the contents of the structurizr/ui repository into this directory,
4+
# - this has only been tested on MacOS
5+
6+
export STRUCTURIZR_BUILD_NUMBER=$1
7+
export STRUCTURIZR_UI_DIR=../structurizr-ui
8+
export STRUCTURIZR_CLI_DIR=.
9+
10+
rm -rf $STRUCTURIZR_CLI_DIR/src/main/resources/static
11+
mkdir -p $STRUCTURIZR_CLI_DIR/src/main/resources/static
12+
13+
# JavaScript
14+
mkdir -p $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
15+
cp -a $STRUCTURIZR_UI_DIR/src/js/jquery-3.6.3.min.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
16+
cp -a $STRUCTURIZR_UI_DIR/src/js/bootstrap-3.3.7.min.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
17+
cp -a $STRUCTURIZR_UI_DIR/src/js/lodash-4.17.21.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
18+
cp -a $STRUCTURIZR_UI_DIR/src/js/backbone-1.4.1.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
19+
cp -a $STRUCTURIZR_UI_DIR/src/js/joint-3.6.5.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
20+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
21+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr-util.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
22+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr-ui.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
23+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr-workspace.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
24+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr-diagram.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
25+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr-quick-navigation.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
26+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr-navigation.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
27+
cp -a $STRUCTURIZR_UI_DIR/src/js/structurizr-tooltip.js $STRUCTURIZR_CLI_DIR/src/main/resources/static/js
28+
29+
# CSS
30+
mkdir -p $STRUCTURIZR_CLI_DIR/src/main/resources/static/css
31+
cp -a $STRUCTURIZR_UI_DIR/src/css/bootstrap-3.3.7.min.css $STRUCTURIZR_CLI_DIR/src/main/resources/static/css
32+
cp -a $STRUCTURIZR_UI_DIR/src/css/joint-3.6.5.css $STRUCTURIZR_CLI_DIR/src/main/resources/static/css
33+
cp -a $STRUCTURIZR_UI_DIR/src/css/structurizr-diagram.css $STRUCTURIZR_CLI_DIR/src/main/resources/static/css
34+
cp -a $STRUCTURIZR_UI_DIR/src/css/structurizr-static.css $STRUCTURIZR_CLI_DIR/src/main/resources/static/css
35+
cp -a $STRUCTURIZR_UI_DIR/src/css/structurizr-static-dark.css $STRUCTURIZR_CLI_DIR/src/main/resources/static/css
36+
37+
# Bootstrap icons
38+
mkdir -p $STRUCTURIZR_CLI_DIR/src/main/resources/static/bootstrap-icons
39+
cp -a $STRUCTURIZR_UI_DIR/src/bootstrap-icons/* $STRUCTURIZR_CLI_DIR/src/main/resources/static/bootstrap-icons
40+
41+
# HTML
42+
cp -a $STRUCTURIZR_UI_DIR/src/static.html $STRUCTURIZR_CLI_DIR/src/main/resources/static/index.html
43+
44+
cd src/main/resources/static
45+
rm ../static.zip
46+
zip -r ../static.zip .
47+
cd ../../../..
48+
rm -rf $STRUCTURIZR_CLI_DIR/src/main/resources/static
49+

0 commit comments

Comments
 (0)