111 lines
4.2 KiB
Zig
111 lines
4.2 KiB
Zig
const std = @import("std");
|
|
const zfin = @import("../root.zig");
|
|
const cli = @import("common.zig");
|
|
const fmt = cli.fmt;
|
|
|
|
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, symbol: []const u8, color: bool, out: *std.Io.Writer) !void {
|
|
const result = svc.getEarnings(symbol) catch |err| switch (err) {
|
|
zfin.DataError.NoApiKey => {
|
|
try cli.stderrPrint("Error: FINNHUB_API_KEY not set. Get a free key at https://finnhub.io\n");
|
|
return;
|
|
},
|
|
else => {
|
|
try cli.stderrPrint("Error fetching earnings data.\n");
|
|
return;
|
|
},
|
|
};
|
|
defer allocator.free(result.data);
|
|
|
|
// Sort chronologically (oldest first) — providers may return in any order
|
|
if (result.data.len > 1) {
|
|
std.mem.sort(zfin.EarningsEvent, result.data, {}, struct {
|
|
fn f(_: void, a: zfin.EarningsEvent, b: zfin.EarningsEvent) bool {
|
|
return a.date.days < b.date.days;
|
|
}
|
|
}.f);
|
|
}
|
|
|
|
if (result.source == .cached) try cli.stderrPrint("(using cached earnings data)\n");
|
|
|
|
try display(result.data, symbol, color, out);
|
|
}
|
|
|
|
pub fn display(events: []const zfin.EarningsEvent, symbol: []const u8, color: bool, out: *std.Io.Writer) !void {
|
|
try cli.setBold(out, color);
|
|
try out.print("\nEarnings History for {s}\n", .{symbol});
|
|
try cli.reset(out, color);
|
|
try out.print("========================================\n", .{});
|
|
|
|
if (events.len == 0) {
|
|
try cli.setFg(out, color, cli.CLR_MUTED);
|
|
try out.print(" No earnings data found.\n\n", .{});
|
|
try cli.reset(out, color);
|
|
return;
|
|
}
|
|
|
|
try cli.setFg(out, color, cli.CLR_MUTED);
|
|
try out.print("{s:>12} {s:>4} {s:>12} {s:>12} {s:>12} {s:>10} {s:>5}\n", .{
|
|
"Date", "Q", "EPS Est", "EPS Act", "Surprise", "Surprise %", "When",
|
|
});
|
|
try out.print("{s:->12} {s:->4} {s:->12} {s:->12} {s:->12} {s:->10} {s:->5}\n", .{
|
|
"", "", "", "", "", "", "",
|
|
});
|
|
try cli.reset(out, color);
|
|
|
|
for (events) |e| {
|
|
var row_buf: [128]u8 = undefined;
|
|
const row = fmt.fmtEarningsRow(&row_buf, e);
|
|
|
|
if (row.is_future) {
|
|
try cli.setFg(out, color, cli.CLR_MUTED);
|
|
} else if (row.is_positive) {
|
|
try cli.setFg(out, color, cli.CLR_POSITIVE);
|
|
} else {
|
|
try cli.setFg(out, color, cli.CLR_NEGATIVE);
|
|
}
|
|
|
|
try out.print("{s}", .{row.text});
|
|
try out.print(" {s:>5}", .{@tagName(e.report_time)});
|
|
try cli.reset(out, color);
|
|
try out.print("\n", .{});
|
|
}
|
|
|
|
try out.print("\n{d} earnings event(s)\n\n", .{events.len});
|
|
}
|
|
|
|
// ── Tests ────────────────────────────────────────────────────
|
|
|
|
test "display shows earnings with beat" {
|
|
var buf: [4096]u8 = undefined;
|
|
var w: std.Io.Writer = .fixed(&buf);
|
|
const events = [_]zfin.EarningsEvent{
|
|
.{ .symbol = "AAPL", .date = .{ .days = 19000 }, .quarter = 4, .estimate = 1.50, .actual = 1.65, .surprise = 0.15, .surprise_percent = 10.0, .report_time = .amc },
|
|
};
|
|
try display(&events, "AAPL", false, &w);
|
|
const out = w.buffered();
|
|
try std.testing.expect(std.mem.indexOf(u8, out, "AAPL") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, out, "Q4") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, out, "$1.50") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, out, "$1.65") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, out, "1 earnings event(s)") != null);
|
|
}
|
|
|
|
test "display shows empty message" {
|
|
var buf: [4096]u8 = undefined;
|
|
var w: std.Io.Writer = .fixed(&buf);
|
|
const events = [_]zfin.EarningsEvent{};
|
|
try display(&events, "XYZ", false, &w);
|
|
const out = w.buffered();
|
|
try std.testing.expect(std.mem.indexOf(u8, out, "No earnings data found") != null);
|
|
}
|
|
|
|
test "display no ANSI without color" {
|
|
var buf: [4096]u8 = undefined;
|
|
var w: std.Io.Writer = .fixed(&buf);
|
|
const events = [_]zfin.EarningsEvent{
|
|
.{ .symbol = "MSFT", .date = .{ .days = 19000 }, .report_time = .bmo },
|
|
};
|
|
try display(&events, "MSFT", false, &w);
|
|
const out = w.buffered();
|
|
try std.testing.expect(std.mem.indexOf(u8, out, "\x1b[") == null);
|
|
}
|