From e29fb5b743f43af5ca6540d812f791f73fdd490f Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 19 Mar 2026 13:00:57 -0700 Subject: [PATCH] consolidate watchlist loading/use srf --- src/commands/common.zig | 35 +++++++++++++++++++++++++++++++++++ src/commands/portfolio.zig | 28 +++++++++------------------- src/tui.zig | 37 ++----------------------------------- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/src/commands/common.zig b/src/commands/common.zig index 89e78ff..75941e0 100644 --- a/src/commands/common.zig +++ b/src/commands/common.zig @@ -123,6 +123,41 @@ pub const LoadProgress = struct { } }; +// ── Watchlist loading ──────────────────────────────────────── + +/// Load a watchlist file using the library's SRF deserializer. +/// Returns owned symbol strings. Returns null if file missing or empty. +pub fn loadWatchlist(allocator: std.mem.Allocator, path: []const u8) ?[][]const u8 { + const file_data = std.fs.cwd().readFileAlloc(allocator, path, 1024 * 1024) catch return null; + defer allocator.free(file_data); + + var portfolio = zfin.cache.deserializePortfolio(allocator, file_data) catch return null; + defer portfolio.deinit(); + + if (portfolio.lots.len == 0) return null; + + var syms: std.ArrayList([]const u8) = .empty; + for (portfolio.lots) |lot| { + const duped = allocator.dupe(u8, lot.symbol) catch continue; + syms.append(allocator, duped) catch { + allocator.free(duped); + continue; + }; + } + if (syms.items.len == 0) { + syms.deinit(allocator); + return null; + } + return syms.toOwnedSlice(allocator) catch null; +} + +pub fn freeWatchlist(allocator: std.mem.Allocator, watchlist: ?[][]const u8) void { + if (watchlist) |wl| { + for (wl) |sym| allocator.free(sym); + allocator.free(wl); + } +} + // ── Tests ──────────────────────────────────────────────────── test "setFg emits ANSI when color enabled" { diff --git a/src/commands/portfolio.zig b/src/commands/portfolio.zig index becb5ea..6711244 100644 --- a/src/commands/portfolio.zig +++ b/src/commands/portfolio.zig @@ -161,25 +161,15 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer // Separate watchlist file (backward compat) if (watchlist_path) |wl_path| { - const wl_data = std.fs.cwd().readFileAlloc(allocator, wl_path, 1024 * 1024) catch null; - if (wl_data) |wd| { - defer allocator.free(wd); - var wl_lines = std.mem.splitScalar(u8, wd, '\n'); - while (wl_lines.next()) |line| { - const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace); - if (trimmed.len == 0 or trimmed[0] == '#') continue; - if (std.mem.indexOf(u8, trimmed, "symbol::")) |idx| { - const rest = trimmed[idx + "symbol::".len ..]; - const end = std.mem.indexOfScalar(u8, rest, ',') orelse rest.len; - const sym = std.mem.trim(u8, rest[0..end], &std.ascii.whitespace); - if (sym.len > 0 and sym.len <= 10) { - if (watch_seen.contains(sym)) continue; - try watch_seen.put(sym, {}); - try watch_list.append(allocator, sym); - if (svc.getCachedLastClose(sym)) |close| { - try watch_prices.put(sym, close); - } - } + const wl_syms = cli.loadWatchlist(allocator, wl_path); + defer cli.freeWatchlist(allocator, wl_syms); + if (wl_syms) |syms_list| { + for (syms_list) |sym| { + if (watch_seen.contains(sym)) continue; + try watch_seen.put(sym, {}); + try watch_list.append(allocator, sym); + if (svc.getCachedLastClose(sym)) |close| { + try watch_prices.put(sym, close); } } } diff --git a/src/tui.zig b/src/tui.zig index 6605b36..f92d0ad 100644 --- a/src/tui.zig +++ b/src/tui.zig @@ -1670,41 +1670,8 @@ pub fn renderBrailleToStyledLines(arena: std.mem.Allocator, lines: *std.ArrayLis } } -pub fn loadWatchlist(allocator: std.mem.Allocator, path: []const u8) ?[][]const u8 { - const file_data = std.fs.cwd().readFileAlloc(allocator, path, 1024 * 1024) catch return null; - defer allocator.free(file_data); - - var syms: std.ArrayList([]const u8) = .empty; - var file_lines = std.mem.splitScalar(u8, file_data, '\n'); - while (file_lines.next()) |line| { - const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace); - if (trimmed.len == 0 or trimmed[0] == '#') continue; - if (std.mem.indexOf(u8, trimmed, "symbol::")) |idx| { - const rest = trimmed[idx + "symbol::".len ..]; - const end = std.mem.indexOfScalar(u8, rest, ',') orelse rest.len; - const sym = std.mem.trim(u8, rest[0..end], &std.ascii.whitespace); - if (sym.len > 0 and sym.len <= 10) { - const duped = allocator.dupe(u8, sym) catch continue; - syms.append(allocator, duped) catch { - allocator.free(duped); - continue; - }; - } - } - } - if (syms.items.len == 0) { - syms.deinit(allocator); - return null; - } - return syms.toOwnedSlice(allocator) catch null; -} - -pub fn freeWatchlist(allocator: std.mem.Allocator, watchlist: ?[][]const u8) void { - if (watchlist) |wl| { - for (wl) |sym| allocator.free(sym); - allocator.free(wl); - } -} +pub const loadWatchlist = cli.loadWatchlist; +pub const freeWatchlist = cli.freeWatchlist; // Force test discovery for imported TUI sub-modules comptime {