From 1a371b8f01b841dd084a87f392d9ad554d4ab599 Mon Sep 17 00:00:00 2001 From: Kenneth Watson Date: Wed, 15 Jun 2022 09:38:11 +0800 Subject: [PATCH] config file with multiple sources, readying for multiple source set navigatoin --- .gitignore | 2 + .../shrimpworks/unreal/scriptbrowser/App.java | 76 +++++-- .../unreal/scriptbrowser/IniFile.java | 213 ++++++++++++++++++ .../unreal/scriptbrowser/USources.java | 10 +- .../unreal/scriptbrowser/www/Generator.java | 9 +- .../unreal/scriptbrowser/www/index.ftl | 4 +- .../unreal/scriptbrowser/www/script.ftl | 7 +- .../unreal/scriptbrowser/www/tree.ftl | 8 +- 8 files changed, 290 insertions(+), 39 deletions(-) create mode 100644 src/main/java/net/shrimpworks/unreal/scriptbrowser/IniFile.java diff --git a/.gitignore b/.gitignore index b18a4dd..4f524af 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build out .idea *.iml + +sources.ini diff --git a/src/main/java/net/shrimpworks/unreal/scriptbrowser/App.java b/src/main/java/net/shrimpworks/unreal/scriptbrowser/App.java index ce86e55..f47faa0 100644 --- a/src/main/java/net/shrimpworks/unreal/scriptbrowser/App.java +++ b/src/main/java/net/shrimpworks/unreal/scriptbrowser/App.java @@ -37,19 +37,63 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int } } - public static void main(String[] args) throws IOException { - final long startTime = System.currentTimeMillis(); + private static List loadProperties() throws IOException { + IniFile config = new IniFile(Paths.get("sources.ini")); + return config.sections().stream() + .map(s -> new USources( + s, + config.section(s).value("out").toString(), + config.section(s) + .asList("paths").values + .stream() + .map(v -> Paths.get(v.toString())) + .collect(Collectors.toList())) + ).collect(Collectors.toList()); + } + public static void main(String[] args) throws IOException { CLI cli = CLI.parse(Map.of(), args); - Path srcPath = Paths.get(cli.commands()[0]); - Path outPath = Paths.get(cli.commands()[1]); + Path outPath = Paths.get(cli.commands()[0]); + + List sources = loadProperties(); + for (USources source : sources) { + System.err.printf("Generating sources for %s%n", source.name); + loadSources(source); +// printTree(children(source, null), 0); + final long loadedTime = System.currentTimeMillis(); + + final Path htmlOut = outPath.resolve(source.outPath); + + System.err.println(" - Generating navigation tree"); + Generator.tree(children(source, null), htmlOut); + + System.err.println(" - Generating source pages"); + source.packages.values().forEach(pkg -> pkg.classes.values().parallelStream() + .filter(c -> c.kind == UClass.UClassKind.CLASS) + .forEach(e -> Generator.src(e, htmlOut))); + final long genTime = System.currentTimeMillis(); + System.err.printf(" - Generated HTML in %dms%n", genTime - loadedTime); + } + + System.err.println("Generating index page"); + Generator.offloadStatic("static.list", outPath); + Generator.index(outPath); + + System.err.println("Done"); + } - USources sources = new USources(); + private static void loadSources(USources sources) throws IOException { + for (Path srcPath : sources.paths) { + loadSources(sources, srcPath); + } + } + private static void loadSources(USources sources, Path srcPath) throws IOException { + final long startTime = System.currentTimeMillis(); final AtomicInteger classCounter = new AtomicInteger(0); try (Stream paths = Files.list(srcPath)) { - System.err.printf("Loading classes from %s%n", srcPath); + System.err.printf(" - Loading classes from %s%n", srcPath); paths.map(p -> { if (!Files.isDirectory(p)) return null; @@ -84,26 +128,8 @@ public static void main(String[] args) throws IOException { .forEach(sources::addPackage); } final long loadedTime = System.currentTimeMillis(); - System.err.printf("Loaded %d classes in %d packages in %dms%n", classCounter.get(), sources.packages.size(), + System.err.printf(" - Loaded %d classes in %d packages in %dms%n", classCounter.get(), sources.packages.size(), loadedTime - startTime); - - System.err.println("Generating index page"); - Generator.offloadStatic("static.list", outPath); - Generator.index(outPath); - - System.err.println("Generating navigation tree"); - Generator.tree(children(sources, null), outPath); - - System.err.println("Generating source pages"); - sources.packages.values().forEach(pkg -> pkg.classes.values().parallelStream() - .filter(c -> c.kind == UClass.UClassKind.CLASS) - .forEach(e -> Generator.src(e, outPath))); - final long genTime = System.currentTimeMillis(); - System.err.printf("Generated HTML in %dms%n", genTime - loadedTime); - -// printTree(children(sources, null), 0); - - System.err.println("Done"); } public static List children(USources sources, UClass parent) { diff --git a/src/main/java/net/shrimpworks/unreal/scriptbrowser/IniFile.java b/src/main/java/net/shrimpworks/unreal/scriptbrowser/IniFile.java new file mode 100644 index 0000000..ebf716d --- /dev/null +++ b/src/main/java/net/shrimpworks/unreal/scriptbrowser/IniFile.java @@ -0,0 +1,213 @@ +package net.shrimpworks.unreal.scriptbrowser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Provides a simple .ini file reader implementations, supporting + * some basic concepts like sections and repeat keys. + */ +public class IniFile { + + private static final Pattern SECTION = Pattern.compile("\\s*\\[([^]]*)\\]\\s*"); + private static final Pattern KEY_VALUE = Pattern.compile("\\s*([^=]*)=(.*)"); + private final List
sections; + + public IniFile(Path intFile) throws IOException { + this(FileChannel.open(intFile, StandardOpenOption.READ), false); + } + + /** + * Create a new IniFile from a file path. + * + * @param intFile path to int file to read + * @param syntheticRoot if true, will create a synthetic section named "root" + * to hold items which don't have a section header + * @throws IOException reading file failed + */ + public IniFile(Path intFile, boolean syntheticRoot) throws IOException { + this(FileChannel.open(intFile, StandardOpenOption.READ), syntheticRoot); + } + + public IniFile(SeekableByteChannel channel) throws IOException { + this(channel, false); + } + + /** + * Create a new IntFile from a byte channel. + * + * @param channel channel to read + * @param syntheticRoot if true, will create a synthetic section named "root" + * to hold items which don't have a section header + * @throws IOException reading channel failed + */ + public IniFile(SeekableByteChannel channel, boolean syntheticRoot) throws IOException { + this.sections = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(Channels.newReader(channel, "UTF-8"))) { + String r; + Section section = null; + if (syntheticRoot) { + section = new Section("root", new HashMap<>()); + sections.add(section); + } + while ((r = reader.readLine()) != null) { + Matcher m = SECTION.matcher(r); + if (m.matches()) { + section = new Section(m.group(1).trim(), new HashMap<>()); + sections.add(section); + } else if (section != null) { + m = KEY_VALUE.matcher(r); + if (m.matches()) { + String k = m.group(1).trim(); + String v = m.group(2).trim(); + + Value value = new StringValue(v); + + Value current = section.values.get(k); + + if (current instanceof ListValue) { + ((ListValue)current).values.add(value); + } else if (current != null) { + section.values.put(k, new ListValue(new ArrayList<>(Arrays.asList(current, value)))); + } else { + section.values.put(k, value); + } + } + } + } + } + } + + /** + * Get a section. + * + * @param section the section name + * @return the section, or null if not found + */ + public Section section(String section) { + return sections.stream().filter(s -> s.name.equalsIgnoreCase(section)).findFirst().orElse(null); + } + + /** + * Get a list of all sections within this .int file. + * + * @return section names + */ + public Collection sections() { + return sections.stream().map(s -> s.name).collect(Collectors.toList()); + } + + @Override + public String toString() { + return String.format("IntFile [sections=%s]", sections); + } + + /** + * An .int file section. + *

+ * In the file structure, these are identified by their [Heading]. + */ + public static class Section { + + public final String name; + private final Map values; + + public Section(String name, Map values) { + this.name = name; + this.values = values; + } + + /** + * Retrieve a value by its key. + * + * @param key key + * @return value, or null if the key does not exist + */ + public Value value(String key) { + return values.get(key); + } + + /** + * Get a list of all keys within this section. + * + * @return key names + */ + public Collection keys() { + return values.keySet(); + } + + /** + * Convenience to always retrieve a value as a list. + *

+ * This is useful if, based on reading the file a value appears to + * be a singleton, but code reading it perhaps expects a list to be + * present. + * + * @param key the key + * @return a list of values, or null if the key does not exist + */ + public ListValue asList(String key) { + Value val = value(key); + if (val instanceof ListValue) return (ListValue)val; + if (val != null) return new ListValue(Collections.singletonList(val)); + return new ListValue(Collections.emptyList()); + } + + @Override + public String toString() { + return String.format("Section [name=%s, values=%s]", name, values); + } + } + + public interface Value { + } + + public static class StringValue implements Value { + + public final String value; + + public StringValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + + public static class ListValue implements Value { + + public final List values; + + public ListValue(List values) { + this.values = values; + } + + public Value get(int index) { + return values.get(index); + } + + @Override + public String toString() { + return values.toString(); + } + } + +} diff --git a/src/main/java/net/shrimpworks/unreal/scriptbrowser/USources.java b/src/main/java/net/shrimpworks/unreal/scriptbrowser/USources.java index b5acb13..5a78ea8 100644 --- a/src/main/java/net/shrimpworks/unreal/scriptbrowser/USources.java +++ b/src/main/java/net/shrimpworks/unreal/scriptbrowser/USources.java @@ -1,14 +1,22 @@ package net.shrimpworks.unreal.scriptbrowser; +import java.nio.file.Path; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.TreeMap; public class USources { + public final String name; + public final String outPath; + public final List paths; public final Map packages; - public USources() { + public USources(String name, String outPath, List paths) { + this.name = name; + this.outPath = outPath; + this.paths = paths; this.packages = new TreeMap<>(); } diff --git a/src/main/java/net/shrimpworks/unreal/scriptbrowser/www/Generator.java b/src/main/java/net/shrimpworks/unreal/scriptbrowser/www/Generator.java index b8fdde6..2003163 100644 --- a/src/main/java/net/shrimpworks/unreal/scriptbrowser/www/Generator.java +++ b/src/main/java/net/shrimpworks/unreal/scriptbrowser/www/Generator.java @@ -83,6 +83,8 @@ public static void index(Path outPath) { public static void tree(Collection nodes, Path outPath) { try { + if (!Files.isDirectory(outPath)) Files.createDirectories(outPath); + Template tpl = TPL_CONFIG.getTemplate("tree.ftl"); try (Writer writer = Channels.newWriter( Files.newByteChannel( @@ -101,11 +103,8 @@ public static void tree(Collection nodes, Path outPath) { public static void src(UClass clazz, Path outPath) { try (InputStream is = Files.newInputStream(clazz.path, StandardOpenOption.READ)) { final Path htmlOut = outPath.resolve(clazz.pkg.name.toLowerCase()); - try { - if (!Files.isDirectory(htmlOut)) Files.createDirectories(htmlOut); - } catch (IOException e) { - // oops - } + + if (!Files.isDirectory(htmlOut)) Files.createDirectories(htmlOut); Template tpl = TPL_CONFIG.getTemplate("script.ftl"); diff --git a/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/index.ftl b/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/index.ftl index 8adfcd1..7601e49 100644 --- a/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/index.ftl +++ b/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/index.ftl @@ -9,7 +9,7 @@

- +

@@ -50,7 +50,7 @@ port1.onmessage = (m) => { switch (m.data.event) { case "loaded": - header.innerHTML = m.data.pkg + " / " + m.data.clazz + header.innerHTML = m.data.set + " / " + m.data.pkg + " / " + m.data.clazz break default: console.log("unknown message event ", m.data.type, m.data) diff --git a/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/script.ftl b/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/script.ftl index cdb98c5..b9b8a2d 100644 --- a/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/script.ftl +++ b/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/script.ftl @@ -1,9 +1,9 @@ - ${clazz.pkg.name}.${clazz.name} - - + ${clazz.pkg.sourceSet.name} / ${clazz.pkg.name}.${clazz.name} + + @@ -27,6 +27,7 @@ port2.postMessage({ "event": "loaded", + "set": "${clazz.pkg.sourceSet.name}", "pkg": "${clazz.pkg.name}", "clazz": "${clazz.name}" }); diff --git a/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/tree.ftl b/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/tree.ftl index 5d2e6c2..55727ca 100644 --- a/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/tree.ftl +++ b/src/main/resources/net/shrimpworks/unreal/scriptbrowser/www/tree.ftl @@ -1,5 +1,7 @@ <#macro jsonnode node depth> { + "path": "${node.clazz.pkg.sourceSet.outPath}", + "set": "${node.clazz.pkg.sourceSet.name}", "pkg": "${node.clazz.pkg.name}", "clazz": "${node.clazz.name}", "children": [ @@ -12,8 +14,8 @@ - - + + @@ -117,7 +119,7 @@ function openClassNode(node) { port2.postMessage({ "event": "nav", - "url": node.pkg.toLowerCase() + "/" + node.clazz.toLowerCase() + ".html" + "url": node.path.toLowerCase() + "/" + node.pkg.toLowerCase() + "/" + node.clazz.toLowerCase() + ".html" }); }