150 lines
4.5 KiB
Zig
150 lines
4.5 KiB
Zig
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");
|
|
}
|
|
}
|