namespace Spry.Tools { public class Mkconst : Object { private static bool show_version = false; private static string? output_file = null; private static string? namespace_name = null; private static string? const_name_override = null; private const OptionEntry[] options = { { "output", 'o', 0, OptionArg.FILENAME, ref output_file, "Output file path (default: stdout)", "FILE" }, { "ns", '\0', 0, OptionArg.STRING, ref namespace_name, "Wrap const in namespace", "NAMESPACE" }, { "name", 'n', 0, OptionArg.STRING, ref const_name_override, "Override const name (default: derived from filename)", "NAME" }, { "version", 'v', 0, OptionArg.NONE, ref show_version, "Show version information", null }, { null } }; public static int main(string[] args) { try { var opt_context = new OptionContext(" - Convert file content to a Vala string constant"); opt_context.set_help_enabled(true); opt_context.add_main_entries(options, null); opt_context.set_description( "Arguments:\n" + " INPUT_FILE Input file (typically an HTML template file)\n" + "\n" + "The const name is derived from the input filename by converting to CAPITAL_SNAKE_CASE.\n" + "For example, 'UserContentComponent.vala.html' becomes USER_CONTENT_COMPONENT_VALA_HTML.\n" + "\n" + "Output goes to stdout by default, or use -o to specify an output file." ); opt_context.parse(ref args); } catch (OptionError e) { stderr.printf("Error: %s\n", e.message); stderr.printf("Run '%s --help' for more information.\n", args[0]); return 1; } if (show_version) { stdout.printf("spry-mkconst 0.1\n"); return 0; } // Get input file from remaining arguments string? input_file = null; if (args.length > 1) { input_file = args[1]; } if (input_file == null) { stderr.printf("Error: No input file specified.\n"); stderr.printf("Run '%s --help' for more information.\n", args[0]); return 1; } try { var tool = new Mkconst(); tool.process(input_file, output_file, namespace_name, const_name_override); return 0; } catch (Error e) { stderr.printf("%s\n", e.message); return 1; } } public void process(string input_path, string? output_path, string? ns_name, string? const_name) throws Error { // Check if input file exists var input_file = File.new_for_path(input_path); if (!input_file.query_exists()) { throw new IOError.NOT_FOUND(@"Error: Input file '$(input_path)' does not exist"); } // Read input file string content = read_file_content(input_path); // Normalize line endings to Unix style content = content.replace("\r\n", "\n").replace("\r", "\n"); // Derive const name from filename if not provided string actual_const_name; if (const_name != null) { actual_const_name = const_name; } else { actual_const_name = filename_to_const_name(input_path); } // Escape any triple quotes in content by replacing """ with \"\"\" string escaped_content = content.replace("\"\"\"", "\\\"\\\"\\\""); // Determine indentation based on namespace string indent = ns_name != null ? " " : ""; // Build output var output = new StringBuilder(); // Write namespace if provided if (ns_name != null) { output.append(@"namespace $(ns_name) {\n\n"); } // Write the const declaration // Remove trailing newline from content before wrapping in triple quotes string trimmed_content = escaped_content; if (trimmed_content.has_suffix("\n")) { trimmed_content = trimmed_content.substring(0, trimmed_content.length - 1); } output.append(@"$(indent)const string $(actual_const_name) = \"\"\"$(trimmed_content)\"\"\";\n"); // Close namespace if provided if (ns_name != null) { output.append("}\n"); } // Write output if (output_path != null) { write_file_content(output_path, output.str); } else { stdout.printf("%s", output.str); } } private string filename_to_const_name(string filepath) { // Get just the filename (without directory path) string basename = Path.get_basename(filepath); // Convert to CAPITAL_SNAKE_CASE // Assumes input is PascalCase with possible extensions // e.g., "UserContentComponent.vala.html" -> "USER_CONTENT_COMPONENT_VALA_HTML" var result = new StringBuilder(); var prev_was_lower = false; var prev_was_upper = false; foreach (var c in basename.to_utf8()) { if (c == '.') { // Convert dots to underscores result.append("_"); prev_was_lower = false; prev_was_upper = false; } else if (c == '-') { // Convert dashes to underscores result.append("_"); prev_was_lower = false; prev_was_upper = false; } else if (c == '_') { result.append("_"); prev_was_lower = false; prev_was_upper = false; } else if (c.isupper()) { if (prev_was_lower) { // Transition from lower to upper, insert underscore result.append("_"); } else if (result.len > 0 && prev_was_upper) { // Check if this is end of an acronym (next char would be lower) // We'll handle this in the next iteration } result.append(c.to_string()); prev_was_lower = false; prev_was_upper = true; } else if (c.islower()) { if (prev_was_upper && result.len > 1) { // Check if previous char was part of an acronym // e.g., "HTMLFile" -> we want "HTML_FILE" // Look back to see if we have multiple uppercase before this string current = result.str; if (current.length >= 2) { char prev_char = (char)current.get(current.length - 1); char prev_prev_char = (char)current.get(current.length - 2); if (prev_char.isupper() && prev_prev_char.isupper()) { // Insert underscore before the last uppercase char result.erase(result.len - 1); result.append("_"); result.append(prev_char.to_string()); } } } result.append(c.toupper().to_string()); prev_was_lower = true; prev_was_upper = false; } else if (c.isdigit()) { result.append(c.to_string()); prev_was_lower = false; prev_was_upper = false; } // Ignore other characters } return result.str; } private string read_file_content(string path) throws Error { var file = File.new_for_path(path); var input_stream = new DataInputStream(file.read()); var builder = new StringBuilder(); string line; while ((line = input_stream.read_line(null)) != null) { builder.append(line); builder.append("\n"); } input_stream.close(); return builder.str; } private void write_file_content(string path, string content) throws Error { var file = File.new_for_path(path); // Ensure parent directory exists var parent = file.get_parent(); if (parent != null && !parent.query_exists()) { parent.make_directory_with_parents(); } var output_stream = new DataOutputStream(file.replace(null, false, FileCreateFlags.NONE)); output_stream.put_string(content); output_stream.close(); } } }