zargs/src/utils.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");
}
}