zfin/src/commands/lookup.zig
2026-03-03 12:58:14 -08:00

124 lines
4.5 KiB
Zig

const std = @import("std");
const zfin = @import("../root.zig");
const cli = @import("common.zig");
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, cusip: []const u8, color: bool, out: *std.Io.Writer) !void {
if (!cli.fmt.isCusipLike(cusip)) {
try cli.setFg(out, color, cli.CLR_MUTED);
try out.print("Note: '{s}' doesn't look like a CUSIP (expected 9 alphanumeric chars with digits)\n", .{cusip});
try cli.reset(out, color);
}
try cli.stderrPrint("Looking up via OpenFIGI...\n");
// Try full batch lookup for richer output
const results = svc.lookupCusips(&.{cusip}) catch {
try cli.stderrPrint("Error: OpenFIGI request failed (network error)\n");
return;
};
defer {
for (results) |r| {
if (r.ticker) |t| allocator.free(t);
if (r.name) |n| allocator.free(n);
if (r.security_type) |s| allocator.free(s);
}
allocator.free(results);
}
if (results.len == 0 or !results[0].found) {
try out.print("No result from OpenFIGI for '{s}'\n", .{cusip});
return;
}
try display(results[0], cusip, color, out);
// Also cache it
if (results[0].ticker) |ticker| {
svc.cacheCusipTicker(cusip, ticker);
}
}
pub fn display(result: zfin.CusipResult, cusip: []const u8, color: bool, out: *std.Io.Writer) !void {
if (result.ticker) |ticker| {
try cli.setBold(out, color);
try out.print("{s}", .{cusip});
try cli.reset(out, color);
try out.print(" -> ", .{});
try cli.setFg(out, color, cli.CLR_ACCENT);
try out.print("{s}", .{ticker});
try cli.reset(out, color);
try out.print("\n", .{});
if (result.name) |name| {
try cli.setFg(out, color, cli.CLR_MUTED);
try out.print(" Name: {s}\n", .{name});
try cli.reset(out, color);
}
if (result.security_type) |st| {
try cli.setFg(out, color, cli.CLR_MUTED);
try out.print(" Type: {s}\n", .{st});
try cli.reset(out, color);
}
try out.print("\n To use in portfolio: ticker::{s}\n", .{ticker});
} else {
try out.print("No ticker found for CUSIP '{s}'\n", .{cusip});
if (result.name) |name| {
try cli.setFg(out, color, cli.CLR_MUTED);
try out.print(" Name: {s}\n", .{name});
try cli.reset(out, color);
}
try out.print("\n Tip: For mutual funds, OpenFIGI often has no coverage.\n", .{});
try out.print(" Add manually: symbol::{s},ticker::XXXX,...\n", .{cusip});
}
}
// ── Tests ────────────────────────────────────────────────────
test "display shows ticker mapping" {
var buf: [4096]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
const result: zfin.CusipResult = .{
.ticker = "AAPL",
.name = "Apple Inc",
.security_type = "Common Stock",
.found = true,
};
try display(result, "037833100", false, &w);
const out = w.buffered();
try std.testing.expect(std.mem.indexOf(u8, out, "037833100") != null);
try std.testing.expect(std.mem.indexOf(u8, out, "AAPL") != null);
try std.testing.expect(std.mem.indexOf(u8, out, "Apple Inc") != null);
try std.testing.expect(std.mem.indexOf(u8, out, "Common Stock") != null);
try std.testing.expect(std.mem.indexOf(u8, out, "ticker::AAPL") != null);
}
test "display shows no-ticker message" {
var buf: [4096]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
const result: zfin.CusipResult = .{
.ticker = null,
.name = "Some Fund",
.security_type = null,
.found = true,
};
try display(result, "123456789", false, &w);
const out = w.buffered();
try std.testing.expect(std.mem.indexOf(u8, out, "No ticker found") != null);
try std.testing.expect(std.mem.indexOf(u8, out, "Some Fund") != null);
try std.testing.expect(std.mem.indexOf(u8, out, "mutual funds") != null);
}
test "display no ANSI without color" {
var buf: [4096]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
const result: zfin.CusipResult = .{
.ticker = "MSFT",
.name = null,
.security_type = null,
.found = true,
};
try display(result, "594918104", false, &w);
const out = w.buffered();
try std.testing.expect(std.mem.indexOf(u8, out, "\x1b[") == null);
}