pvbemu/app/Bundle.java

215 lines
6.7 KiB
Java

import java.awt.image.*;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import javax.imageio.*;
public class Bundle {
// File loaded from disk
static class File2 implements Comparable<File2> {
// Instance fields
byte[] data; // Contents
String filename; // Full path relative to root
// Comparator
public int compareTo(File2 o) {
if (filename.equals("app/_boot.js"))
return -1;
if (o.filename.equals("app/_boot.js"))
return 1;
return filename.compareTo(o.filename);
}
}
// Load all files in directory tree into memory
static HashMap<String, File2> readFiles(String bundleTitle) {
var dirs = new ArrayDeque<File>();
var root = new File(".");
var subs = new ArrayDeque<String>();
var files = new HashMap<String, File2>();
// Process all subdirectories
dirs.add(root);
while (!dirs.isEmpty()) {
var dir = dirs.remove();
// Add all subdirectories
for (var sub : dir.listFiles(f->f.isDirectory())) {
// Exclusions
if (dir == root && sub.getName().equals(".git"))
continue;
// Add the directory for bundling
dirs.add(sub);
}
// Add all files
for (var file : dir.listFiles(f->f.isFile())) {
var file2 = new File2();
// Read the file into memory
try {
var stream = new FileInputStream(file);
file2.data = stream.readAllBytes();
stream.close();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
// Determine the file's full pathname
subs.clear();
subs.addFirst(file.getName());
for (;;) {
file = file.getParentFile();
if (file.equals(root))
break;
subs.addFirst(file.getName());
}
file2.filename = String.join("/", subs);
// Exclusions
if (
file2.filename.startsWith(".git" ) ||
file2.filename.startsWith(bundleTitle + "_") &&
file2.filename.endsWith (".html" )
) continue;
// Add the file to the output
files.put(file2.filename, file2);
}
}
return files;
}
// Prepend manifest object to _boot.js
static void manifest(HashMap<String, File2> files, String bundleName) {
// Produce a sorted list of files
var values = files.values().toArray(new File2[files.size()]);
Arrays.sort(values);
// Build a file manifest
var manifest = new StringBuilder();
manifest.append("\"use strict\";\nlet manifest=[");
for (var file : values) {
manifest.append(
"[\"" + file.filename + "\"," + file.data.length + "]");
if (file != values[values.length - 1])
manifest.append(",");
}
manifest.append("],bundleName=\"" + bundleName + "\";");
// Prepend the manifest to _boot.js
var boot = files.get("app/_boot.js");
boot.data = (
manifest.toString() +
new String(boot.data, StandardCharsets.UTF_8) +
"\u0000"
).getBytes(StandardCharsets.UTF_8);
}
// Construct bundled blob
static byte[] blob(HashMap<String, File2> files) {
// Produce a sorted list of files
var values = files.values().toArray(new File2[files.size()]);
Arrays.sort(values);
// Build the blob
var blob = new ByteArrayOutputStream();
for (var file : values) try {
blob.write(file.data);
} catch (Exception e) { }
return blob.toByteArray();
}
// Encode bundled blob as a .png
static byte[] png(byte[] blob) {
// Calculate the dimensions of the image
int width = (int) Math.ceil(Math.sqrt(blob.length));
int height = (int) Math.ceil((double) blob.length / width);
// Prepare the pixel data
var pixels = new int[width * height];
for (int x = 0; x < blob.length; x++) {
int l = blob[x] & 0xFF;
pixels[x] = 0xFF000000 | l << 16 | l << 8 | l;
}
// Produce a BufferedImage containing the pixels
var img = new BufferedImage(width, height,
BufferedImage.TYPE_BYTE_GRAY);
img.getRaster().setPixels(0, 0, width, height, pixels);
// Encode the image as a PNG byte array
var png = new ByteArrayOutputStream();
try { ImageIO.write(img, "png", png); }
catch (Exception e) { }
return png.toByteArray();
}
// Embed bundle .png into template.html as a data URL
static void template(byte[] png, String bundleName) {
// Encode the PNG as a data URL
String url = "data:image/png;base64," +
Base64.getMimeEncoder().encodeToString(png)
.replaceAll("\\r\\n", "");
try {
// Read template.html into memory
var inStream = new FileInputStream("app/template.html");
String template =
new String(inStream.readAllBytes(), StandardCharsets.UTF_8)
.replace("src=\"\"", "src=\"" + url + "\"")
;
inStream.close();
// Write the output HTML file
var outStream = new FileOutputStream(bundleName + ".html");
outStream.write(template.getBytes(StandardCharsets.UTF_8));
outStream.close();
} catch (Exception e) { throw new RuntimeException(e.getMessage()); }
}
// Determine the filename of the bundle
static String bundleName(String name) {
var calendar = Calendar.getInstance();
return String.format("%s_%04d%02d%02d",
name,
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH) + 1,
calendar.get(Calendar.DAY_OF_MONTH)
);
}
// Program entry point
public static void main(String[] args) {
// Error checking
if (args.length != 1) {
System.err.println("Usage: Bundle <name>");
return;
}
// Program tasks
String bundleName = bundleName(args[0]);
var files = readFiles(args[0]);
manifest(files, bundleName);
var blob = blob(files);
var png = png(blob);
template(png, bundleName);
}
}