From 68392efec48979086a56b3a68305886a273de3c1 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Mon, 27 Apr 2026 19:53:42 -0700 Subject: [PATCH] centralize portfolio load --- src/commands/analysis.zig | 22 ++++----------- src/commands/common.zig | 54 ++++++++++++++++++++++++++++++++++++ src/commands/portfolio.zig | 25 ++++------------- src/commands/projections.zig | 22 ++++----------- 4 files changed, 69 insertions(+), 54 deletions(-) diff --git a/src/commands/analysis.zig b/src/commands/analysis.zig index 67c6222..ff53771 100644 --- a/src/commands/analysis.zig +++ b/src/commands/analysis.zig @@ -5,24 +5,12 @@ const fmt = cli.fmt; /// CLI `analysis` command: show portfolio analysis breakdowns. pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void { - // Load portfolio - const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch { - try cli.stderrPrint("Error: Cannot read portfolio file\n"); - return; - }; - defer allocator.free(file_data); + var loaded = cli.loadPortfolio(allocator, file_path) orelse return; + defer loaded.deinit(allocator); - var portfolio = zfin.cache.deserializePortfolio(allocator, file_data) catch { - try cli.stderrPrint("Error: Cannot parse portfolio file\n"); - return; - }; - defer portfolio.deinit(); - - const positions = try portfolio.positions(allocator); - defer allocator.free(positions); - - const syms = try portfolio.stockSymbols(allocator); - defer allocator.free(syms); + const portfolio = loaded.portfolio; + const positions = loaded.positions; + const syms = loaded.syms; // Build prices from cache var prices = std.StringHashMap(f64).init(allocator); diff --git a/src/commands/common.zig b/src/commands/common.zig index fecd248..1e083bc 100644 --- a/src/commands/common.zig +++ b/src/commands/common.zig @@ -250,6 +250,60 @@ pub fn loadPortfolioPrices( return result; } +// ── Portfolio loading ──────────────────────────────────────── + +/// Result of loading and parsing a portfolio file. Caller must call deinit(). +pub const LoadedPortfolio = struct { + file_data: []const u8, + portfolio: zfin.Portfolio, + positions: []const zfin.Position, + syms: []const []const u8, + + pub fn deinit(self: *LoadedPortfolio, allocator: std.mem.Allocator) void { + allocator.free(self.syms); + allocator.free(self.positions); + self.portfolio.deinit(); + allocator.free(self.file_data); + } +}; + +/// Read, deserialize, and extract positions + symbols from a portfolio file. +/// Returns null (with stderr message) on read/parse errors. +pub fn loadPortfolio(allocator: std.mem.Allocator, file_path: []const u8) ?LoadedPortfolio { + const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch { + stderrPrint("Error: Cannot read portfolio file\n") catch {}; + return null; + }; + + var portfolio = zfin.cache.deserializePortfolio(allocator, file_data) catch { + allocator.free(file_data); + stderrPrint("Error: Cannot parse portfolio file\n") catch {}; + return null; + }; + + const positions = portfolio.positions(allocator) catch { + portfolio.deinit(); + allocator.free(file_data); + stderrPrint("Error: Cannot compute positions\n") catch {}; + return null; + }; + + const syms = portfolio.stockSymbols(allocator) catch { + allocator.free(positions); + portfolio.deinit(); + allocator.free(file_data); + stderrPrint("Error: Cannot get stock symbols\n") catch {}; + return null; + }; + + return .{ + .file_data = file_data, + .portfolio = portfolio, + .positions = positions, + .syms = syms, + }; +} + // ── Portfolio data pipeline ────────────────────────────────── /// Result of the shared portfolio data pipeline. Caller must call deinit(). diff --git a/src/commands/portfolio.zig b/src/commands/portfolio.zig index 60f023a..7d6def4 100644 --- a/src/commands/portfolio.zig +++ b/src/commands/portfolio.zig @@ -11,33 +11,18 @@ fn setIntentFg(out: *std.Io.Writer, color: bool, intent: fmt.StyleIntent) !void pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, watchlist_path: ?[]const u8, force_refresh: bool, color: bool, out: *std.Io.Writer) !void { // Load portfolio from SRF file - const data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch |err| { - try cli.stderrPrint("Error reading portfolio file: "); - try cli.stderrPrint(@errorName(err)); - try cli.stderrPrint("\n"); - return; - }; - defer allocator.free(data); + var loaded = cli.loadPortfolio(allocator, file_path) orelse return; + defer loaded.deinit(allocator); - var portfolio = zfin.cache.deserializePortfolio(allocator, data) catch { - try cli.stderrPrint("Error parsing portfolio file.\n"); - return; - }; - defer portfolio.deinit(); + const portfolio = loaded.portfolio; + const positions = loaded.positions; + const syms = loaded.syms; if (portfolio.lots.len == 0) { try cli.stderrPrint("Portfolio is empty.\n"); return; } - // Get stock/ETF positions (excludes options, CDs, cash) - const positions = try portfolio.positions(allocator); - defer allocator.free(positions); - - // Get unique stock/ETF symbols and fetch current prices - const syms = try portfolio.stockSymbols(allocator); - defer allocator.free(syms); - var prices = std.StringHashMap(f64).init(allocator); defer prices.deinit(); diff --git a/src/commands/projections.zig b/src/commands/projections.zig index 951a430..332faa2 100644 --- a/src/commands/projections.zig +++ b/src/commands/projections.zig @@ -19,24 +19,12 @@ const stock_benchmark = "SPY"; const bond_benchmark = "AGG"; pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void { - // Load portfolio - const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch { - try cli.stderrPrint("Error: Cannot read portfolio file\n"); - return; - }; - defer allocator.free(file_data); + var loaded = cli.loadPortfolio(allocator, file_path) orelse return; + defer loaded.deinit(allocator); - var portfolio = zfin.cache.deserializePortfolio(allocator, file_data) catch { - try cli.stderrPrint("Error: Cannot parse portfolio file\n"); - return; - }; - defer portfolio.deinit(); - - const positions = try portfolio.positions(allocator); - defer allocator.free(positions); - - const syms = try portfolio.stockSymbols(allocator); - defer allocator.free(syms); + const portfolio = loaded.portfolio; + const positions = loaded.positions; + const syms = loaded.syms; // Build prices from cache var prices = std.StringHashMap(f64).init(allocator);