| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- using Astralis;
- using Invercargill;
- using Invercargill.DataStructures;
- namespace Statum.Tools {
- /**
- * `statum-mkres` — generates a {@link Statum.ConstantStatumResource} Vala
- * subclass from an input file, precompressing it with identity/gzip/zstd/br.
- *
- * Mirrors `spry-mkssr --vala`. Intended to be wired as a meson
- * `custom_target` (see `tools/meson.build`).
- *
- * statum-mkres -o LogoResource.vala -n logo.png logo.png
- * statum-mkres --ns MyApp.Static -c text/css -o Styles.vala styles.css
- */
- public class Mkres : Object {
- private static string? output_file = null;
- private static string? content_type_override = null;
- private static string? resource_name_override = null;
- private static string? class_name_override = null;
- private static string? namespace_name = null;
- private const OptionEntry[] options = {
- { "output", 'o', 0, OptionArg.FILENAME, ref output_file, "Output Vala file name (default: <InputName>Resource.vala)", "FILE" },
- { "content-type", 'c', 0, OptionArg.STRING, ref content_type_override, "Override content type (e.g. text/html)", "TYPE" },
- { "name", 'n', 0, OptionArg.STRING, ref resource_name_override, "Resource name (default: input filename)", "NAME" },
- { "class-name", '\0', 0, OptionArg.STRING, ref class_name_override, "Generated Vala class name (default: <Name>Resource)", "CLASS" },
- { "ns", '\0', 0, OptionArg.STRING, ref namespace_name, "Namespace for the generated class", "NAMESPACE" },
- { null }
- };
- public static int main(string[] args) {
- try {
- var opt_context = new OptionContext("INPUT_FILE - Generate a Statum ConstantStatumResource");
- opt_context.add_main_entries(options, null);
- opt_context.parse(ref args);
- } catch (OptionError e) {
- stderr.printf("Error: %s\n", e.message);
- return 1;
- }
- string? input_file = null;
- if (args.length > 1) {
- input_file = args[1];
- }
- if (input_file == null) {
- stderr.printf("Error: No input file specified.\n");
- return 1;
- }
- var name = resource_name_override ?? Path.get_basename((!)input_file);
- var class_name_base = resource_name_override ?? Path.get_basename((!)input_file);
- var output_path = output_file ?? (make_pascal_case(class_name_base) + "Resource.vala");
- var class_name = class_name_override ?? (make_pascal_case(class_name_base) + "Resource");
- try {
- var tool = new Mkres();
- var generated = tool.process((!)input_file, name, class_name, content_type_override, namespace_name);
- write_file(output_path, generated);
- stdout.printf("Generated: %s\n", output_path);
- return 0;
- } catch (Error e) {
- stderr.printf("Error: %s\n", e.message);
- return 1;
- }
- }
- public string process(string input_path, string name, string class_name, string? content_type_override, string? namespace_name) throws Error {
- var input_data = read_all(input_path);
- string content_type;
- if (content_type_override != null) {
- content_type = (!)content_type_override;
- } else {
- content_type = guess_content_type(name, input_data);
- }
- var hash_hex = compute_hash_hex(input_data);
- var encodings = compress_all(input_data);
- return emit_vala(class_name, name, content_type, hash_hex, encodings, namespace_name);
- }
- private EncodedData[] compress_all(uint8[] input_data) {
- var encodings = new EncodedData[0];
- var identity_data = new uint8[input_data.length];
- Memory.copy(identity_data, input_data, input_data.length);
- encodings += new EncodedData("identity", (owned)identity_data);
- var input_buffer = new ByteBuffer.from_byte_array(input_data);
- // GZip
- var gzip = new GzipCompressor(9).compress_buffer(input_buffer, null).to_array();
- if (gzip.length < input_data.length) {
- encodings += new EncodedData("gzip", (owned)gzip);
- }
- // Zstandard
- var zstd = new ZstdCompressor(19).compress_buffer(input_buffer, null).to_array();
- if (zstd.length < input_data.length) {
- encodings += new EncodedData("zstd", (owned)zstd);
- }
- // Brotli
- var br = new BrotliCompressor(11).compress_buffer(input_buffer, null).to_array();
- if (br.length < input_data.length) {
- encodings += new EncodedData("br", (owned)br);
- }
- return encodings;
- }
- private string emit_vala(string class_name, string name, string content_type, string hash_hex, EncodedData[] encodings, string? namespace_name) {
- var b = new StringBuilder();
- string i1 = namespace_name != null ? " " : "";
- string i2 = namespace_name != null ? " " : " ";
- string i3 = namespace_name != null ? " " : " ";
- string i4 = namespace_name != null ? " " : " ";
- b.append("using Statum;\n");
- b.append("using Invercargill;\n");
- b.append("using Invercargill.DataStructures;\n\n");
- if (namespace_name != null) {
- b.append_printf("namespace %s {\n\n", namespace_name);
- }
- b.append_printf("%s// Generated by statum-mkres\n", i1);
- b.append_printf("%spublic class %s : ConstantStatumResource {\n\n", i1, class_name);
- b.append_printf("%spublic override string name { get { return \"%s\"; } }\n", i2, escape(name));
- b.append_printf("%spublic override string file_hash { get { return \"%s\"; } }\n", i2, hash_hex);
- b.append_printf("%spublic override string content_type { get { return \"%s\"; } }\n\n", i2, escape(content_type));
- // Order by size so the client gets the smallest supported encoding.
- var ordered = sort_by_size(encodings);
- b.append_printf("%spublic override string get_best_encoding(Set<string> supported) {\n", i2);
- foreach (var encoding in ordered) {
- if (encoding.type == "identity") {
- continue;
- }
- b.append_printf("%sif (supported.has(\"%s\")) return \"%s\";\n", i3, encoding.type, encoding.type);
- }
- b.append_printf("%sreturn \"identity\";\n", i3);
- b.append_printf("%s}\n\n", i2);
- b.append_printf("%spublic override unowned uint8[] get_encoding(string encoding) {\n", i2);
- b.append_printf("%sswitch (encoding) {\n", i3);
- foreach (var encoding in encodings) {
- var var_name = encoding_const(encoding.type);
- b.append_printf("%scase \"%s\": return %s;\n", i4, encoding.type, var_name);
- }
- b.append_printf("%sdefault: return IDENTITY_DATA;\n", i4);
- b.append_printf("%s}\n", i3);
- b.append_printf("%s}\n\n", i2);
- foreach (var encoding in encodings) {
- var var_name = encoding_const(encoding.type);
- b.append_printf("%sprivate const uint8[] %s = {\n", i2, var_name);
- append_byte_array(b, encoding.data, i3);
- b.append_printf("\n%s};\n\n", i2);
- }
- b.append_printf("%s}\n", i1);
- if (namespace_name != null) {
- b.append("}\n");
- }
- return b.str;
- }
- private static EncodedData[] sort_by_size(EncodedData[] encodings) {
- var copy = new EncodedData[encodings.length];
- for (var i = 0; i < encodings.length; i++) {
- copy[i] = encodings[i];
- }
- for (var i = 0; i < copy.length - 1; i++) {
- for (var j = i + 1; j < copy.length; j++) {
- if (copy[i].data.length > copy[j].data.length) {
- var tmp = copy[i];
- copy[i] = copy[j];
- copy[j] = tmp;
- }
- }
- }
- return copy;
- }
- private static string encoding_const(string encoding_type) {
- return encoding_type.replace("-", "_").up() + "_DATA";
- }
- private static void append_byte_array(StringBuilder b, uint8[] data, string indent) {
- for (var i = 0; i < data.length; i++) {
- if (i == 0) {
- b.append(indent);
- } else if (i % 16 == 0) {
- b.append(",\n").append(indent);
- } else {
- b.append(", ");
- }
- b.append_printf("0x%02x", data[i]);
- }
- }
- private static string escape(string s) {
- return s.replace("\\", "\\\\").replace("\"", "\\\"");
- }
- private static string guess_content_type(string filename, uint8[] sample_data) {
- bool uncertain;
- var content_type = ContentType.guess(filename, sample_data, out uncertain);
- var mime = ContentType.get_mime_type(content_type);
- return mime ?? "application/octet-stream";
- }
- private static string compute_hash_hex(uint8[] data) {
- var checksum = new Checksum(ChecksumType.SHA512);
- checksum.update(data, data.length);
- return checksum.get_string();
- }
- private static uint8[] read_all(string path) throws Error {
- var file = File.new_for_path(path);
- if (!file.query_exists()) {
- throw new IOError.NOT_FOUND(@"Input file '$path' does not exist");
- }
- var size = (size_t) file.query_info("standard::size", 0).get_size();
- var stream = new DataInputStream(file.read());
- var bytes = stream.read_bytes(size);
- stream.close();
- var data = new uint8[bytes.get_size()];
- Memory.copy(data, bytes.get_data(), bytes.get_size());
- return data;
- }
- private static void write_file(string path, string contents) throws Error {
- var file = File.new_for_path(path);
- var stream = new DataOutputStream(file.replace(null, false, FileCreateFlags.NONE));
- stream.put_string(contents);
- stream.close();
- }
- private static string make_pascal_case(string input) {
- var name = input;
- var dot = name.last_index_of(".");
- if (dot > 0) {
- name = name.substring(0, dot);
- }
- var result = new StringBuilder();
- var capitalize_next = true;
- foreach (var c in name.data) {
- if (c == '-' || c == '_' || c == ' ' || c == '.') {
- capitalize_next = true;
- } else {
- if (capitalize_next) {
- result.append_printf("%c", c >= 'a' && c <= 'z' ? c - 32 : c);
- capitalize_next = false;
- } else {
- result.append_printf("%c", c);
- }
- }
- }
- return result.str;
- }
- private class EncodedData {
- public string type;
- public uint8[] data;
- public EncodedData(string type, owned uint8[] data) {
- this.type = type;
- this.data = (owned)data;
- }
- }
- }
- }
|