import java.io.*; import java.nio.charset.*; import java.time.*; import java.util.*; import java.util.zip.*; // Web application asset packager public class Bundle { // Read a file from disk into a byte buffer static byte[] fileRead(File file) { try (var stream = new FileInputStream(file)) { return stream.readAllBytes(); } catch (Exception e) { return null; } } // Retrieve the canonical form of a file static File getFile(File file) { try { return file.getCanonicalFile(); } catch (Exception e) { return null; } } // Retrieve the canonical file for a relative filename static File getFile(String filename) { return getFile(new File(filename)); } // List all canonical files within a directory static File[] listFiles(File dir) { var ret = dir.listFiles(); for (int x = 0; x < ret.length; x++) ret[x] = getFile(ret[x]); return ret; } // List all asset files to be bundled static Asset[] listAssets(File root, File main) { var assets = new ArrayList(); var dirs = new ArrayList(); // Initial directories dirs.add(new File(root, "shrooms-vb-core")); dirs.add(new File(root, "shrooms-vb-web" )); // Process all directories while (dirs.size() != 0) { var dir = dirs.remove(0); // Process all child files and directories for (var file : listFiles(dir)) { // Exclude this file or directory if (file.equals(main) || file.getName().startsWith(".git")) continue; // Include this directory if (file.isDirectory()) dirs.add(file); // Include this file else assets.add(new Asset(root, file)); } } Collections.sort(assets); return assets.toArray(new Asset[assets.size()]); } // Determine the relative path of a file from the root directory static String relativePath(File root, File file) { // Work backwards to identify the full path var path = new ArrayList(); while (!root.equals(file)) { path.add(0, file.getName()); file = file.getParentFile(); } // Join the path parts with forward slashes var ret = new StringBuilder(); for (String part : path) ret.append("/" + part); return ret.toString().substring(1); } // Express a byte array as a Base64 string static String toBase64(byte[] data) { return Base64.getMimeEncoder(0, new byte[0]).encodeToString(data); } // Encode a byte array as a zlib buffer static byte[] toZlib(byte[] data) { try { var comp = new Deflater(Deflater.BEST_COMPRESSION, false); comp.setInput(data); comp.finish(); var ret = new byte[data.length]; ret = Arrays.copyOf(ret, comp.deflate(ret)); comp.end(); return ret; } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } // Program entry point public static void main(String[] args) { // Select all assets var root = getFile("../"); var main = getFile("main.js"); var assets = listAssets(root, main); // Resolve the current date code var today = ZonedDateTime.now(Clock.systemUTC()); String dateCode = String.format("%04d%02d%02d", today.getYear(), today.getMonthValue(), today.getDayOfMonth()); // Process the manifest var manifest = new StringBuilder(); manifest.append("["); for (var asset : assets) { manifest.append(String.format("%s\"%s\",%d", asset == assets[0] ? "" : ",", asset.name, asset.length)); } manifest.append(",\"" + dateCode + "\"]"); // Encode the bundle var bundle = new ByteArrayOutputStream(); try { bundle.write(fileRead(main)); bundle.write(0); bundle.write(manifest.toString().getBytes(StandardCharsets.UTF_8)); bundle.write(0); for (var asset : assets) bundle.write(fileRead(asset.file)); } catch (Exception e) {} // Read the HTML template var template = new String( fileRead(new File("template.html")), StandardCharsets.UTF_8) .split("\\\"\\\""); // Generate the output HTML file String filename = "../acid-shroom_" + dateCode + ".html"; try (var stream = new FileOutputStream(filename)) { stream.write(template[0].getBytes(StandardCharsets.UTF_8)); stream.write('"'); stream.write(toBase64(toZlib(bundle.toByteArray())) .getBytes(StandardCharsets.UTF_8)); stream.write('"'); stream.write(template[1].getBytes(StandardCharsets.UTF_8)); } catch (Exception e) {} } ///////////////////////////////// Classes ///////////////////////////////// // Packaged asset file static class Asset implements Comparable { File file; // File on disk int length; // Size in bytes String name; // Filename without path Asset(File root, File file) { this.file = file; length = (int) file.length(); name = relativePath(root, file); } public int compareTo(Asset o) { return name.compareTo(o.name); } public boolean equals(Object o) { return o instanceof Asset && compareTo((Asset) o) == 0; } public int hashCode() { return name.hashCode(); } } }