From 87fc872f7dc1c0730e8cb127dac7b3240089d832 Mon Sep 17 00:00:00 2001 From: Simon Hartcher Date: Wed, 30 Apr 2025 14:19:42 +1000 Subject: [PATCH] chore: replace custom snake case with case package --- build.zig | 5 ++ build.zig.zon | 4 ++ codegen/src/main.zig | 9 +-- codegen/src/snake.zig | 157 ------------------------------------------ 4 files changed, 14 insertions(+), 161 deletions(-) delete mode 100644 codegen/src/snake.zig diff --git a/build.zig b/build.zig index 90d4d3e..5cc94b8 100644 --- a/build.zig +++ b/build.zig @@ -215,6 +215,7 @@ fn configure(compile: *std.Build.Module, modules: std.StringHashMap(*std.Build.M compile.addImport("smithy", modules.get("smithy").?); compile.addImport("date", modules.get("date").?); compile.addImport("json", modules.get("json").?); + compile.addImport("case", modules.get("case").?); if (include_time) compile.addImport("zeit", modules.get("zeit").?); } @@ -229,6 +230,10 @@ fn getDependencyModules(b: *std.Build, args: anytype) !std.StringHashMap(*std.Bu const dep_zeit = b.dependency("zeit", args); const mod_zeit = dep_zeit.module("zeit"); try result.putNoClobber("zeit", mod_zeit); + + const dep_case = b.dependency("case", args); + const mod_case = dep_case.module("case"); + try result.putNoClobber("case", mod_case); // End External dependencies // Private modules/dependencies diff --git a/build.zig.zon b/build.zig.zon index bfc99ca..85f8e73 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -31,5 +31,9 @@ .json = .{ .path = "lib/json", }, + .case = .{ + .url = "git+https://github.com/travisstaloch/case.git#610caade88ca54d2745f115114b08e73e2c6fe02", + .hash = "N-V-__8AAIfIAAC_RzCtghVVBVdqUzB8AaaGIyvK2WWz38bC", + }, }, } diff --git a/codegen/src/main.zig b/codegen/src/main.zig index 3ea2801..33f8116 100644 --- a/codegen/src/main.zig +++ b/codegen/src/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); const smithy = @import("smithy"); -const snake = @import("snake.zig"); const Hasher = @import("Hasher.zig"); +const case = @import("case"); var verbose = false; @@ -479,9 +479,10 @@ fn constantName(allocator: std.mem.Allocator, id: []const u8) ![]const u8 { // snake turns this into dev_ops, which is a little weird if (std.mem.eql(u8, id, "DevOps Guru")) return try std.fmt.allocPrint(allocator, "devops_guru", .{}); if (std.mem.eql(u8, id, "FSx")) return try std.fmt.allocPrint(allocator, "fsx", .{}); + if (std.mem.eql(u8, id, "ETag")) return try std.fmt.allocPrint(allocator, "e_tag", .{}); // Not a special case - just snake it - return try snake.fromPascalCase(allocator, id); + return try case.allocTo(allocator, .snake, id); } const FileGenerationState = struct { @@ -503,7 +504,7 @@ fn outputIndent(state: GenerationState, writer: anytype) !void { try writer.writeByteNTimes(' ', n_chars); } fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo, file_state: FileGenerationState, writer: anytype) !void { - const snake_case_name = try snake.fromPascalCase(allocator, operation.name); + const snake_case_name = try constantName(allocator, operation.name); defer allocator.free(snake_case_name); var type_stack = std.ArrayList(*const smithy.ShapeInfo).init(allocator); @@ -822,7 +823,7 @@ fn generateComplexTypeFor(shape_id: []const u8, members: []smithy.TypeMember, ty var payload: ?[]const u8 = null; for (members) |member| { // This is our mapping - const snake_case_member = try snake.fromPascalCase(state.allocator, member.name); + const snake_case_member = try constantName(state.allocator, member.name); // So it looks like some services have duplicate names?! Check out "httpMethod" // in API Gateway. Not sure what we're supposed to do there. Checking the go // sdk, they move this particular duplicate to 'http_method' - not sure yet diff --git a/codegen/src/snake.zig b/codegen/src/snake.zig deleted file mode 100644 index 8f944a7..0000000 --- a/codegen/src/snake.zig +++ /dev/null @@ -1,157 +0,0 @@ -const std = @import("std"); -const expectEqualStrings = std.testing.expectEqualStrings; - -pub fn fromPascalCase(allocator: std.mem.Allocator, name: []const u8) ![]u8 { - const rc = try allocator.alloc(u8, name.len * 2); // This is overkill, but is > the maximum length possibly needed - errdefer allocator.free(rc); - var utf8_name = (std.unicode.Utf8View.init(name) catch unreachable).iterator(); - var target_inx: u64 = 0; - var curr_char = (try isAscii(utf8_name.nextCodepoint())).?; - target_inx = setNext(lowercase(curr_char), rc, target_inx); - var prev_char = curr_char; - if (try isAscii(utf8_name.nextCodepoint())) |ch| { - curr_char = ch; - } else { - // Single character only - we're done here - _ = setNext(0, rc, target_inx); - return rc[0..target_inx]; - } - while (try isAscii(utf8_name.nextCodepoint())) |next_char| { - if (next_char == ' ') { - // a space shouldn't be happening. But if it does, it clues us - // in pretty well: - // - // MyStuff Is Awesome - // |^ - // |next_char - // ^ - // prev_codepoint/ascii_prev_char (and target_inx) - target_inx = setNext(lowercase(curr_char), rc, target_inx); - target_inx = setNext('_', rc, target_inx); - var maybe_curr_char = (try isAscii(utf8_name.nextCodepoint())); - if (maybe_curr_char == null) { - std.log.err("Error on fromPascalCase processing name '{s}'", .{name}); - } - curr_char = maybe_curr_char.?; - maybe_curr_char = (try isAscii(utf8_name.nextCodepoint())); - if (maybe_curr_char == null) { - // We have reached the end of the string (e.g. "Resource Explorer 2") - // We need to do this check before we setNext, so that we don't - // end up duplicating the last character - break; - // std.log.err("Error on fromPascalCase processing name '{s}', curr_char = '{}'", .{ name, curr_char }); - } - target_inx = setNext(lowercase(curr_char), rc, target_inx); - prev_char = curr_char; - curr_char = maybe_curr_char.?; - continue; - } - if (between(curr_char, 'A', 'Z')) { - if (isAcronym(curr_char, next_char)) { - // We could be in an acronym at the start of a word. This - // is the only case where we actually need to look back at the - // previous character, and if that's the case, throw in an - // underscore - // "SAMLMySAMLAcronymThing"); - if (between(prev_char, 'a', 'z')) - target_inx = setNext('_', rc, target_inx); - - //we are in an acronym - don't snake, just lower - target_inx = setNext(lowercase(curr_char), rc, target_inx); - } else { - target_inx = setNext('_', rc, target_inx); - target_inx = setNext(lowercase(curr_char), rc, target_inx); - } - } else { - target_inx = setNext(curr_char, rc, target_inx); - } - prev_char = curr_char; - curr_char = next_char; - } - // work in the last codepoint - force lowercase - target_inx = setNext(lowercase(curr_char), rc, target_inx); - - rc[target_inx] = 0; - _ = allocator.resize(rc, target_inx); - return rc[0..target_inx]; -} - -fn isAcronym(char1: u8, char2: u8) bool { - return isAcronymChar(char1) and isAcronymChar(char2); -} -fn isAcronymChar(char: u8) bool { - return between(char, 'A', 'Z') or between(char, '0', '9'); -} -fn isAscii(codepoint: ?u21) !?u8 { - if (codepoint) |cp| { - if (cp > 0xff) return error.UnicodeNotSupported; - return @as(u8, @truncate(cp)); - } - return null; -} - -fn setNext(ascii: u8, slice: []u8, inx: u64) u64 { - slice[inx] = ascii; - return inx + 1; -} - -fn lowercase(ascii: u8) u8 { - var lowercase_char = ascii; - if (between(ascii, 'A', 'Z')) - lowercase_char = ascii + ('a' - 'A'); - return lowercase_char; -} - -fn between(char: u8, from: u8, to: u8) bool { - return char >= from and char <= to; -} - -test "converts from PascalCase to snake_case" { - const allocator = std.testing.allocator; - const snake_case = try fromPascalCase(allocator, "MyPascalCaseThing"); - defer allocator.free(snake_case); - try expectEqualStrings("my_pascal_case_thing", snake_case); -} -test "handles from PascalCase acronyms to snake_case" { - const allocator = std.testing.allocator; - const snake_case = try fromPascalCase(allocator, "SAMLMySAMLAcronymThing"); - defer allocator.free(snake_case); - try expectEqualStrings("saml_my_saml_acronym_thing", snake_case); -} -test "spaces in the name" { - const allocator = std.testing.allocator; - const snake_case = try fromPascalCase(allocator, "API Gateway"); - defer allocator.free(snake_case); - try expectEqualStrings("api_gateway", snake_case); -} - -test "S3" { - const allocator = std.testing.allocator; - const snake_case = try fromPascalCase(allocator, "S3"); - defer allocator.free(snake_case); - try expectEqualStrings("s3", snake_case); -} - -test "ec2" { - const allocator = std.testing.allocator; - const snake_case = try fromPascalCase(allocator, "EC2"); - defer allocator.free(snake_case); - try expectEqualStrings("ec2", snake_case); -} - -test "IoT 1Click Devices Service" { - const allocator = std.testing.allocator; - const snake_case = try fromPascalCase(allocator, "IoT 1Click Devices Service"); - defer allocator.free(snake_case); - // NOTE: There is some debate amoung humans about what this should - // turn into. Should it be iot_1click_... or iot_1_click...? - try expectEqualStrings("iot_1_click_devices_service", snake_case); -} -test "Resource Explorer 2" { - const allocator = std.testing.allocator; - const snake_case = try fromPascalCase(allocator, "Resource Explorer 2"); - defer allocator.free(snake_case); - // NOTE: There is some debate amoung humans about what this should - // turn into. Should it be iot_1click_... or iot_1_click...? - try expectEqualStrings("resource_explorer_2", snake_case); -}