From 52337ab5a9e3a59275044b32cc7dcf31f44f0411 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Tue, 3 Mar 2026 12:27:23 -0800 Subject: [PATCH] title case sector in enrich command --- src/commands/enrich.zig | 8 ++++++-- src/format.zig | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/commands/enrich.zig b/src/commands/enrich.zig index 530fa82..d71d7aa 100644 --- a/src/commands/enrich.zig +++ b/src/commands/enrich.zig @@ -55,7 +55,9 @@ fn enrichSymbol(allocator: std.mem.Allocator, av_key: []const u8, sym: []const u if (overview.asset_type) |at| allocator.free(at); } - const sector_str = overview.sector orelse "Unknown"; + const sector_raw = overview.sector orelse "Unknown"; + var sector_buf: [64]u8 = undefined; + const sector_str = cli.fmt.toTitleCase(§or_buf, sector_raw); const country_str = overview.country orelse "US"; const geo_str = if (std.mem.eql(u8, country_str, "USA")) "US" else country_str; @@ -166,7 +168,9 @@ fn enrichPortfolio(allocator: std.mem.Allocator, av_key: []const u8, file_path: if (overview.asset_type) |at| allocator.free(at); } - const sector_str = overview.sector orelse "Unknown"; + const sector_raw = overview.sector orelse "Unknown"; + var sector_buf: [64]u8 = undefined; + const sector_str = cli.fmt.toTitleCase(§or_buf, sector_raw); const country_str = overview.country orelse "US"; const geo_str = if (std.mem.eql(u8, country_str, "USA")) "US" else country_str; diff --git a/src/format.zig b/src/format.zig index ad0ec0e..e80fd1a 100644 --- a/src/format.zig +++ b/src/format.zig @@ -350,6 +350,25 @@ pub fn isCusipLike(s: []const u8) bool { return has_digit; } +/// Convert a string to title case ("TECHNOLOGY" -> "Technology", "CONSUMER CYCLICAL" -> "Consumer Cyclical"). +/// Writes into a caller-provided buffer and returns the slice. +pub fn toTitleCase(buf: []u8, s: []const u8) []const u8 { + const len = @min(s.len, buf.len); + var capitalize_next = true; + for (s[0..len], 0..) |c, i| { + if (c == ' ') { + buf[i] = ' '; + capitalize_next = true; + } else if (capitalize_next) { + buf[i] = std.ascii.toUpper(c); + capitalize_next = false; + } else { + buf[i] = std.ascii.toLower(c); + } + } + return buf[0..len]; +} + /// Format an options contract line: strike + last + bid + ask + volume + OI + IV. pub fn fmtContractLine(alloc: std.mem.Allocator, prefix: []const u8, c: OptionContract) ![]const u8 { var last_buf: [12]u8 = undefined; @@ -1070,6 +1089,15 @@ test "isCusipLike" { try std.testing.expect(!isCusipLike("12345")); // Too short } +test "toTitleCase" { + var buf: [64]u8 = undefined; + try std.testing.expectEqualStrings("Technology", toTitleCase(&buf, "TECHNOLOGY")); + try std.testing.expectEqualStrings("Consumer Cyclical", toTitleCase(&buf, "CONSUMER CYCLICAL")); + try std.testing.expectEqualStrings("Healthcare", toTitleCase(&buf, "HEALTHCARE")); + try std.testing.expectEqualStrings("Technology", toTitleCase(&buf, "Technology")); + try std.testing.expectEqualStrings("Unknown", toTitleCase(&buf, "Unknown")); +} + test "lotSortFn" { const open_new = Lot{ .symbol = "A",