ThemesJsGenerator.java
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2021, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.theme;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Generates <code>src/main/resources/teavm/themes.js</code>, the companion
* script that makes the bundled themes available to the TeaVM (browser) build.
* <p>
* The browser build has no classpath, so
* {@link ThemeUtils#loadBundledOrLocalTheme} cannot reach
* <code>/themes/puml-theme-*.puml</code>. This generator writes those same
* files into a JS map on <code>PLANTUML_THEMES</code>, which the engine loads
* on demand through {@code TeaVmScriptLoader}, exactly like
* <code>emoji.js</code> and <code>openiconic.js</code>.
* <p>
* Run it after adding or editing a theme:
*
* <pre>
* java net.sourceforge.plantuml.theme.ThemesJsGenerator
* </pre>
*/
public final class ThemesJsGenerator {
private static final String THEMES_DIR = "src/main/resources/themes";
private static final String OUTPUT = "src/main/resources/teavm/themes.js";
private static final String THEME_FILE_PREFIX = "puml-theme-";
private static final String THEME_FILE_SUFFIX = ".puml";
public static void main(String[] args) throws IOException {
final File themesDir = new File(THEMES_DIR);
if (themesDir.isDirectory() == false)
throw new IOException("Themes directory does not exist: " + themesDir.getAbsolutePath());
final File output = new File(OUTPUT);
if (output.getParentFile().isDirectory() == false)
throw new IOException("Target directory does not exist: " + output.getParentFile());
final List<String> names = listThemeNames(themesDir);
// Written with explicit \n line endings so the output is byte-identical on
// every platform (PrintStream.println would use the platform separator).
final StringBuilder out = new StringBuilder();
out.append("// themes.js - Generated by ThemesJsGenerator.main()\n");
out.append("// Do not edit manually\n");
out.append("(function () {\n");
out.append("var g = (typeof globalThis !== 'undefined') ? globalThis"
+ " : ((typeof self !== 'undefined') ? self : this);\n");
out.append("g.PLANTUML_THEMES = g.PLANTUML_THEMES || {};\n");
out.append("\n");
for (String name : names) {
final File file = new File(themesDir, ThemeUtils.getFilename(name));
if (file.isFile() == false)
throw new IOException("Theme file not found: " + file.getAbsolutePath());
final String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)
.replace("\r\n", "\n");
out.append("g.PLANTUML_THEMES[").append(toJsLiteral(name)).append("]=").append(toJsLiteral(content))
.append(";\n");
}
out.append("})();\n");
Files.write(output.toPath(), out.toString().getBytes(StandardCharsets.UTF_8));
System.out.println("Generated " + output.getAbsolutePath() + " (" + names.size() + " themes)");
}
/**
* Lists the bundled theme names by scanning the themes directory, so that a
* newly added puml-theme-*.puml file is picked up without any other step.
*/
private static List<String> listThemeNames(File themesDir) throws IOException {
final String[] files = themesDir.list();
if (files == null)
throw new IOException("Cannot list " + themesDir.getAbsolutePath());
final List<String> result = new ArrayList<>();
for (String file : files)
if (file.startsWith(THEME_FILE_PREFIX) && file.endsWith(THEME_FILE_SUFFIX))
result.add(file.substring(THEME_FILE_PREFIX.length(),
file.length() - THEME_FILE_SUFFIX.length()));
Collections.sort(result);
return result;
}
/**
* Renders a string as a JS double-quoted literal. Everything outside printable
* ASCII is escaped as \\uXXXX so the generated file stays pure ASCII.
*/
private static String toJsLiteral(String value) {
final StringBuilder sb = new StringBuilder("\"");
for (int i = 0; i < value.length(); i++) {
final char c = value.charAt(i);
switch (c) {
case '"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
default:
if (c < 0x20 || c > 0x7E)
sb.append(String.format("\\u%04x", (int) c));
else
sb.append(c);
}
}
return sb.append('"').toString();
}
private ThemesJsGenerator() {
}
}