215 lines
6.6 KiB
Java
215 lines
6.6 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("{" +
|
||
|
"name:\"" + file.filename + "\"," +
|
||
|
"size:" + file.data.length +
|
||
|
"},");
|
||
|
}
|
||
|
manifest.append("];\nlet bundleName=\"" + bundleName + "\";\n");
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
}
|