diff --git a/src/main.zig b/src/main.zig index 05a5ed5..067fb00 100644 --- a/src/main.zig +++ b/src/main.zig @@ -18,6 +18,9 @@ const zeit = @import("zeit"); const Provider = @import("Provider.zig"); +// Configuration: Only include releases from the last year in the output +const RELEASE_AGE_LIMIT_SECONDS: i64 = 365 * 24 * 60 * 60; // 1 year in seconds + pub const Release = struct { repo_name: []const u8, tag_name: []const u8, @@ -147,27 +150,38 @@ pub fn main() !u8 { print("Found {} new releases from {s}\n", .{ result.releases.items.len, result.provider_name }); } - // Combine existing and new releases + // Combine all releases (existing and new) var all_releases = ArrayList(Release).init(allocator); defer all_releases.deinit(); - // Add new releases first (they'll appear at the top of the Atom feed) + // Add new releases try all_releases.appendSlice(new_releases.items); - // Add existing releases (up to a reasonable limit to prevent Atom feed from growing indefinitely) - const max_total_releases = 1000; - const remaining_slots = if (new_releases.items.len < max_total_releases) - max_total_releases - new_releases.items.len - else - 0; - - const existing_to_add = @min(existing_releases.items.len, remaining_slots); - try all_releases.appendSlice(existing_releases.items[0..existing_to_add]); + // Add all existing releases + try all_releases.appendSlice(existing_releases.items); // Sort all releases by published date (most recent first) std.mem.sort(Release, all_releases.items, {}, compareReleasesByDate); - // Generate Atom feed + // Filter releases by age in-place - zero extra allocations + const now = std.time.timestamp(); + const cutoff_time = now - RELEASE_AGE_LIMIT_SECONDS; + + var write_index: usize = 0; + const original_count = all_releases.items.len; + + for (all_releases.items) |release| { + const release_time = parseReleaseTimestamp(release.published_at) catch 0; + if (release_time >= cutoff_time) { + all_releases.items[write_index] = release; + write_index += 1; + } + } + + // Shrink the array to only include filtered items + all_releases.shrinkRetainingCapacity(write_index); + + // Generate Atom feed from filtered releases const atom_content = try atom.generateFeed(allocator, all_releases.items); defer allocator.free(atom_content); @@ -178,7 +192,7 @@ pub fn main() !u8 { // Log to stderr for user feedback std.debug.print("Found {} new releases\n", .{new_releases.items.len}); - std.debug.print("Total releases in feed: {}\n", .{all_releases.items.len}); + std.debug.print("Total releases in feed: {} (filtered from {} total, showing last {} days)\n", .{ all_releases.items.len, original_count, @divTrunc(RELEASE_AGE_LIMIT_SECONDS, 24 * 60 * 60) }); std.debug.print("Updated feed written to: {s}\n", .{output_file}); return 0; @@ -761,6 +775,92 @@ test { std.testing.refAllDecls(@import("xml_parser_tests.zig")); } +test "Age-based release filtering" { + const allocator = std.testing.allocator; + + const now = std.time.timestamp(); + const one_year_ago = now - RELEASE_AGE_LIMIT_SECONDS; + const two_years_ago = now - (2 * RELEASE_AGE_LIMIT_SECONDS); + + // Create releases with different ages + const recent_release = Release{ + .repo_name = "test/recent", + .tag_name = "v1.0.0", + .published_at = try std.fmt.allocPrint(allocator, "{}", .{now - 86400}), // 1 day ago + .html_url = "https://github.com/test/recent/releases/tag/v1.0.0", + .description = "Recent release", + .provider = "github", + }; + defer allocator.free(recent_release.published_at); + + const old_release = Release{ + .repo_name = "test/old", + .tag_name = "v0.1.0", + .published_at = try std.fmt.allocPrint(allocator, "{}", .{two_years_ago}), + .html_url = "https://github.com/test/old/releases/tag/v0.1.0", + .description = "Old release", + .provider = "github", + }; + defer allocator.free(old_release.published_at); + + const borderline_release = Release{ + .repo_name = "test/borderline", + .tag_name = "v0.5.0", + .published_at = try std.fmt.allocPrint(allocator, "{}", .{one_year_ago + 3600}), // 1 hour within limit + .html_url = "https://github.com/test/borderline/releases/tag/v0.5.0", + .description = "Borderline release", + .provider = "github", + }; + defer allocator.free(borderline_release.published_at); + + const releases = [_]Release{ recent_release, old_release, borderline_release }; + + // Test filtering logic + var filtered = ArrayList(Release).init(allocator); + defer filtered.deinit(); + + const cutoff_time = now - RELEASE_AGE_LIMIT_SECONDS; + + for (releases) |release| { + const release_time = parseReleaseTimestamp(release.published_at) catch 0; + if (release_time >= cutoff_time) { + try filtered.append(release); + } + } + + // Should include recent and borderline, but not old + try std.testing.expectEqual(@as(usize, 2), filtered.items.len); + + // Verify the correct releases were included + var found_recent = false; + var found_borderline = false; + var found_old = false; + + for (filtered.items) |release| { + if (std.mem.eql(u8, release.repo_name, "test/recent")) { + found_recent = true; + } else if (std.mem.eql(u8, release.repo_name, "test/borderline")) { + found_borderline = true; + } else if (std.mem.eql(u8, release.repo_name, "test/old")) { + found_old = true; + } + } + + try std.testing.expect(found_recent); + try std.testing.expect(found_borderline); + try std.testing.expect(!found_old); +} + +test "RELEASE_AGE_LIMIT_SECONDS constant verification" { + // Verify the constant is set to 1 year in seconds + const expected_year_in_seconds = 365 * 24 * 60 * 60; + try std.testing.expectEqual(expected_year_in_seconds, RELEASE_AGE_LIMIT_SECONDS); + + // Verify it's approximately 31.5 million seconds (1 year) + try std.testing.expect(RELEASE_AGE_LIMIT_SECONDS > 31_000_000); + try std.testing.expect(RELEASE_AGE_LIMIT_SECONDS < 32_000_000); +} + // Import timestamp tests test { std.testing.refAllDecls(@import("timestamp_tests.zig"));