const std = @import("std"); /// Convert a camelCase or snake_case identifier to kebab-case at compile time /// Examples: /// "verboseMode" -> "verbose-mode" /// "output_file" -> "output-file" /// "logLevel" -> "log-level" /// "HTTPServer" -> "http-server" /// Returns a comptime string literal that persists and can be used anywhere pub fn toKebabCase(comptime name: []const u8) *const [kebabCaseLen(name):0]u8 { comptime { const len = kebabCaseLen(name); var result: [len:0]u8 = undefined; var result_len: usize = 0; var prev_was_lower = false; var prev_was_underscore = false; for (name, 0..) |c, i| { // Replace underscores with hyphens if (c == '_') { if (result_len > 0 and !prev_was_underscore) { result[result_len] = '-'; result_len += 1; } prev_was_underscore = true; prev_was_lower = false; continue; } prev_was_underscore = false; // Add hyphen before uppercase letter if: // 1. Not at the start // 2. Previous char was lowercase (camelCase boundary) // 3. OR next char is lowercase and current is uppercase (HTTPServer -> http-server) if (std.ascii.isUpper(c)) { const should_add_hyphen = result_len > 0 and ( prev_was_lower or (i + 1 < name.len and std.ascii.isLower(name[i + 1])) ); if (should_add_hyphen) { result[result_len] = '-'; result_len += 1; } result[result_len] = std.ascii.toLower(c); result_len += 1; prev_was_lower = false; } else { result[result_len] = c; result_len += 1; prev_was_lower = std.ascii.isLower(c); } } result[result_len] = 0; const final = result; return &final; } } /// Calculate the length needed for kebab-case version fn kebabCaseLen(comptime name: []const u8) usize { comptime { if (name.len == 0) return 0; var len: usize = 0; var prev_was_lower = false; var prev_was_underscore = false; for (name, 0..) |c, i| { if (c == '_') { if (len > 0 and !prev_was_underscore) { len += 1; // for hyphen } prev_was_underscore = true; prev_was_lower = false; continue; } prev_was_underscore = false; if (std.ascii.isUpper(c)) { const should_add_hyphen = len > 0 and ( prev_was_lower or (i + 1 < name.len and std.ascii.isLower(name[i + 1])) ); if (should_add_hyphen) { len += 1; // for hyphen } len += 1; // for lowercase char prev_was_lower = false; } else { len += 1; prev_was_lower = std.ascii.isLower(c); } } return len; } } // Compile-time verification tests comptime { // Basic camelCase const result1 = toKebabCase("verboseMode"); if (!std.mem.eql(u8, result1, "verbose-mode")) { @compileError("toKebabCase failed: verboseMode"); } // snake_case const result2 = toKebabCase("output_file"); if (!std.mem.eql(u8, result2, "output-file")) { @compileError("toKebabCase failed: output_file"); } // Multiple uppercase (acronyms) const result3 = toKebabCase("HTTPServer"); if (!std.mem.eql(u8, result3, "http-server")) { @compileError("toKebabCase failed: HTTPServer"); } // Single word const result4 = toKebabCase("verbose"); if (!std.mem.eql(u8, result4, "verbose")) { @compileError("toKebabCase failed: verbose"); } // Empty string const result5 = toKebabCase(""); if (!std.mem.eql(u8, result5, "")) { @compileError("toKebabCase failed: empty string"); } // Already kebab-case const result6 = toKebabCase("log-level"); if (!std.mem.eql(u8, result6, "log-level")) { @compileError("toKebabCase failed: log-level"); } // Mixed formats const result7 = toKebabCase("parse_XMLFile"); if (!std.mem.eql(u8, result7, "parse-xml-file")) { @compileError("toKebabCase failed: parse_XMLFile"); } }