From fd8242784d885ac2763926e4f25c79b5014e1315 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sat, 12 Jul 2025 18:27:36 -0700 Subject: [PATCH] provider cleanup --- src/integration_tests.zig | 24 +-- src/main.zig | 42 ++--- src/providers/{codeberg.zig => Codeberg.zig} | 79 ++++----- src/providers/{github.zig => GitHub.zig} | 73 +++++---- src/providers/{gitlab.zig => GitLab.zig} | 79 ++++----- .../{sourcehut.zig => SourceHut.zig} | 155 +++++++++--------- 6 files changed, 233 insertions(+), 219 deletions(-) rename src/providers/{codeberg.zig => Codeberg.zig} (88%) rename src/providers/{github.zig => GitHub.zig} (84%) rename src/providers/{gitlab.zig => GitLab.zig} (87%) rename src/providers/{sourcehut.zig => SourceHut.zig} (83%) diff --git a/src/integration_tests.zig b/src/integration_tests.zig index f3e012e..ef1fa3c 100644 --- a/src/integration_tests.zig +++ b/src/integration_tests.zig @@ -4,10 +4,10 @@ const ArrayList = std.ArrayList; const atom = @import("atom.zig"); const Release = @import("main.zig").Release; -const github = @import("providers/github.zig"); -const gitlab = @import("providers/gitlab.zig"); -const codeberg = @import("providers/codeberg.zig"); -const sourcehut = @import("providers/sourcehut.zig"); +const GitHub = @import("providers/GitHub.zig"); +const GitLab = @import("providers/GitLab.zig"); +const Codeberg = @import("providers/Codeberg.zig"); +const SourceHut = @import("providers/SourceHut.zig"); const config = @import("config.zig"); test "Atom feed validates against W3C validator" { @@ -60,8 +60,8 @@ test "GitHub provider integration" { return; } - var provider = github.GitHubProvider{}; - const releases = provider.fetchReleases(allocator, app_config.github_token.?) catch |err| { + var provider = GitHub.init(app_config.github_token.?); + const releases = provider.fetchReleases(allocator) catch |err| { std.debug.print("GitHub provider error: {}\n", .{err}); return; }; @@ -98,8 +98,8 @@ test "GitLab provider integration" { return; } - var provider = gitlab.GitLabProvider{}; - const releases = provider.fetchReleases(allocator, app_config.gitlab_token.?) catch |err| { + var provider = GitLab.init(app_config.gitlab_token.?); + const releases = provider.fetchReleases(allocator) catch |err| { std.debug.print("GitLab provider error: {}\n", .{err}); return; // Skip test if provider fails }; @@ -139,8 +139,8 @@ test "Codeberg provider integration" { return; } - var provider = codeberg.CodebergProvider{}; - const releases = provider.fetchReleases(allocator, app_config.codeberg_token.?) catch |err| { + var provider = Codeberg.init(app_config.codeberg_token.?); + const releases = provider.fetchReleases(allocator) catch |err| { std.debug.print("Codeberg provider error: {}\n", .{err}); return; // Skip test if provider fails }; @@ -177,8 +177,8 @@ test "SourceHut provider integration" { return; } - var provider = sourcehut.SourceHutProvider{}; - const releases = provider.fetchReleasesForRepos(allocator, app_config.sourcehut.?.repositories, app_config.sourcehut.?.token) catch |err| { + var provider = SourceHut.init(app_config.sourcehut.?.token.?, app_config.sourcehut.?.repositories); + const releases = provider.fetchReleases(allocator) catch |err| { std.debug.print("SourceHut provider error: {}\n", .{err}); return; // Skip test if provider fails }; diff --git a/src/main.zig b/src/main.zig index e3062fb..d988e87 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,10 +5,10 @@ const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; const Thread = std.Thread; -const github = @import("providers/github.zig"); -const gitlab = @import("providers/gitlab.zig"); -const codeberg = @import("providers/codeberg.zig"); -const sourcehut = @import("providers/sourcehut.zig"); +const GitHub = @import("providers/GitHub.zig"); +const GitLab = @import("providers/GitLab.zig"); +const Codeberg = @import("providers/Codeberg.zig"); +const SourceHut = @import("providers/SourceHut.zig"); const atom = @import("atom.zig"); const config = @import("config.zig"); const zeit = @import("zeit"); @@ -102,33 +102,27 @@ pub fn main() !u8 { defer providers.deinit(); // Initialize providers with their tokens (need to persist for the lifetime of the program) - var github_provider: ?github.GitHubProvider = null; - var gitlab_provider: ?gitlab.GitLabProvider = null; - var codeberg_provider: ?codeberg.CodebergProvider = null; - var sourcehut_provider: ?sourcehut.SourceHutProvider = null; + var github_provider: ?GitHub = null; + var gitlab_provider: ?GitLab = null; + var codeberg_provider: ?Codeberg = null; + var sourcehut_provider: ?SourceHut = null; if (app_config.github_token) |token| { - github_provider = github.GitHubProvider.init(token); - try providers.append(Provider.init(&github_provider.?)); + github_provider = GitHub.init(token); + try providers.append(github_provider.?.provider()); } - if (app_config.gitlab_token) |token| { - gitlab_provider = gitlab.GitLabProvider.init(token); - try providers.append(Provider.init(&gitlab_provider.?)); + gitlab_provider = GitLab.init(token); + try providers.append(gitlab_provider.?.provider()); } - if (app_config.codeberg_token) |token| { - codeberg_provider = codeberg.CodebergProvider.init(token); - try providers.append(Provider.init(&codeberg_provider.?)); - } - - // Configure SourceHut provider with repositories if available - if (app_config.sourcehut) |sh_config| { - if (sh_config.repositories.len > 0 and sh_config.token != null) { - sourcehut_provider = sourcehut.SourceHutProvider.init(sh_config.token.?, sh_config.repositories); - try providers.append(Provider.init(&sourcehut_provider.?)); - } + codeberg_provider = Codeberg.init(token); + try providers.append(codeberg_provider.?.provider()); } + if (app_config.sourcehut) |sh_config| if (sh_config.repositories.len > 0 and sh_config.token != null) { + sourcehut_provider = SourceHut.init(sh_config.token.?, sh_config.repositories); + try providers.append(sourcehut_provider.?.provider()); + }; // Fetch releases from all providers concurrently using thread pool const provider_results = try fetchReleasesFromAllProviders(allocator, providers.items, existing_releases.items); diff --git a/src/providers/codeberg.zig b/src/providers/Codeberg.zig similarity index 88% rename from src/providers/codeberg.zig rename to src/providers/Codeberg.zig index b6dc05e..d03aa54 100644 --- a/src/providers/codeberg.zig +++ b/src/providers/Codeberg.zig @@ -6,51 +6,56 @@ const ArrayList = std.ArrayList; const zeit = @import("zeit"); const Release = @import("../main.zig").Release; +const Provider = @import("../Provider.zig"); -pub const CodebergProvider = struct { - token: []const u8, +token: []const u8, - pub fn init(token: []const u8) CodebergProvider { - return CodebergProvider{ .token = token }; - } +const Self = @This(); - pub fn fetchReleases(self: *@This(), allocator: Allocator) !ArrayList(Release) { - var client = http.Client{ .allocator = allocator }; - defer client.deinit(); +pub fn init(token: []const u8) Self { + return Self{ .token = token }; +} - var releases = ArrayList(Release).init(allocator); +pub fn provider(self: *Self) Provider { + return Provider.init(self); +} - // Get starred repositories (Codeberg uses Gitea API) - const starred_repos = try getStarredRepos(allocator, &client, self.token); - defer { - for (starred_repos.items) |repo| { - allocator.free(repo); - } - starred_repos.deinit(); - } +pub fn fetchReleases(self: *Self, allocator: Allocator) !ArrayList(Release) { + var client = http.Client{ .allocator = allocator }; + defer client.deinit(); - // Get releases for each repo + var releases = ArrayList(Release).init(allocator); + + // Get starred repositories (Codeberg uses Gitea API) + const starred_repos = try getStarredRepos(allocator, &client, self.token); + defer { for (starred_repos.items) |repo| { - const repo_releases = getRepoReleases(allocator, &client, self.token, repo) catch |err| { - std.debug.print("Error fetching Codeberg releases for {s}: {}\n", .{ repo, err }); - continue; - }; - defer repo_releases.deinit(); - - // Transfer ownership of the releases to the main list - for (repo_releases.items) |release| { - try releases.append(release); - } + allocator.free(repo); } - - return releases; + starred_repos.deinit(); } - pub fn getName(self: *@This()) []const u8 { - _ = self; - return "codeberg"; + // Get releases for each repo + for (starred_repos.items) |repo| { + const repo_releases = getRepoReleases(allocator, &client, self.token, repo) catch |err| { + std.debug.print("Error fetching Codeberg releases for {s}: {}\n", .{ repo, err }); + continue; + }; + defer repo_releases.deinit(); + + // Transfer ownership of the releases to the main list + for (repo_releases.items) |release| { + try releases.append(release); + } } -}; + + return releases; +} + +pub fn getName(self: *Self) []const u8 { + _ = self; + return "codeberg"; +} fn getStarredRepos(allocator: Allocator, client: *http.Client, token: []const u8) !ArrayList([]const u8) { var repos = ArrayList([]const u8).init(allocator); @@ -262,10 +267,10 @@ fn parseTimestamp(date_str: []const u8) !i64 { test "codeberg provider" { const allocator = std.testing.allocator; - var provider = CodebergProvider.init(""); + var codeberg_provider = init(""); // Test with empty token (should fail gracefully) - const releases = provider.fetchReleases(allocator) catch |err| { + const releases = codeberg_provider.fetchReleases(allocator) catch |err| { try std.testing.expect(err == error.Unauthorized or err == error.HttpRequestFailed); return; }; @@ -276,7 +281,7 @@ test "codeberg provider" { releases.deinit(); } - try std.testing.expectEqualStrings("codeberg", provider.getName()); + try std.testing.expectEqualStrings("codeberg", codeberg_provider.getName()); } test "codeberg release parsing with live data snapshot" { diff --git a/src/providers/github.zig b/src/providers/GitHub.zig similarity index 84% rename from src/providers/github.zig rename to src/providers/GitHub.zig index 221e882..92274d6 100644 --- a/src/providers/github.zig +++ b/src/providers/GitHub.zig @@ -6,48 +6,53 @@ const ArrayList = std.ArrayList; const zeit = @import("zeit"); const Release = @import("../main.zig").Release; +const Provider = @import("../Provider.zig"); -pub const GitHubProvider = struct { - token: []const u8, +token: []const u8, - pub fn init(token: []const u8) GitHubProvider { - return GitHubProvider{ .token = token }; - } +const Self = @This(); - pub fn fetchReleases(self: *@This(), allocator: Allocator) !ArrayList(Release) { - var client = http.Client{ .allocator = allocator }; - defer client.deinit(); +pub fn init(token: []const u8) Self { + return Self{ .token = token }; +} - var releases = ArrayList(Release).init(allocator); +pub fn provider(self: *Self) Provider { + return Provider.init(self); +} - // First, get starred repositories - const starred_repos = try getStarredRepos(allocator, &client, self.token); - defer { - for (starred_repos.items) |repo| { - allocator.free(repo); - } - starred_repos.deinit(); - } +pub fn fetchReleases(self: *Self, allocator: Allocator) !ArrayList(Release) { + var client = http.Client{ .allocator = allocator }; + defer client.deinit(); - // Then get releases for each repo + var releases = ArrayList(Release).init(allocator); + + // First, get starred repositories + const starred_repos = try getStarredRepos(allocator, &client, self.token); + defer { for (starred_repos.items) |repo| { - const repo_releases = getRepoReleases(allocator, &client, self.token, repo) catch |err| { - std.debug.print("Error fetching releases for {s}: {}\n", .{ repo, err }); - continue; - }; - defer repo_releases.deinit(); - - try releases.appendSlice(repo_releases.items); + allocator.free(repo); } - - return releases; + starred_repos.deinit(); } - pub fn getName(self: *@This()) []const u8 { - _ = self; - return "github"; + // Then get releases for each repo + for (starred_repos.items) |repo| { + const repo_releases = getRepoReleases(allocator, &client, self.token, repo) catch |err| { + std.debug.print("Error fetching releases for {s}: {}\n", .{ repo, err }); + continue; + }; + defer repo_releases.deinit(); + + try releases.appendSlice(repo_releases.items); } -}; + + return releases; +} + +pub fn getName(self: *Self) []const u8 { + _ = self; + return "github"; +} fn getStarredRepos(allocator: Allocator, client: *http.Client, token: []const u8) !ArrayList([]const u8) { var repos = ArrayList([]const u8).init(allocator); @@ -174,10 +179,10 @@ fn parseTimestamp(date_str: []const u8) !i64 { test "github provider" { const allocator = std.testing.allocator; - var provider = GitHubProvider.init(""); + var github_provider = init(""); // Test with empty token (should fail gracefully) - const releases = provider.fetchReleases(allocator) catch |err| { + const releases = github_provider.fetchReleases(allocator) catch |err| { try std.testing.expect(err == error.HttpRequestFailed); return; }; @@ -188,7 +193,7 @@ test "github provider" { releases.deinit(); } - try std.testing.expectEqualStrings("github", provider.getName()); + try std.testing.expectEqualStrings("github", github_provider.getName()); } test "github release parsing with live data snapshot" { diff --git a/src/providers/gitlab.zig b/src/providers/GitLab.zig similarity index 87% rename from src/providers/gitlab.zig rename to src/providers/GitLab.zig index 81fc249..85e00cf 100644 --- a/src/providers/gitlab.zig +++ b/src/providers/GitLab.zig @@ -6,51 +6,56 @@ const ArrayList = std.ArrayList; const zeit = @import("zeit"); const Release = @import("../main.zig").Release; +const Provider = @import("../Provider.zig"); -pub const GitLabProvider = struct { - token: []const u8, +token: []const u8, - pub fn init(token: []const u8) GitLabProvider { - return GitLabProvider{ .token = token }; - } +const Self = @This(); - pub fn fetchReleases(self: *@This(), allocator: Allocator) !ArrayList(Release) { - var client = http.Client{ .allocator = allocator }; - defer client.deinit(); +pub fn init(token: []const u8) Self { + return Self{ .token = token }; +} - var releases = ArrayList(Release).init(allocator); +pub fn provider(self: *Self) Provider { + return Provider.init(self); +} - // Get starred projects - const starred_projects = try getStarredProjects(allocator, &client, self.token); - defer { - for (starred_projects.items) |project| { - allocator.free(project); - } - starred_projects.deinit(); +pub fn fetchReleases(self: *Self, allocator: Allocator) !ArrayList(Release) { + var client = http.Client{ .allocator = allocator }; + defer client.deinit(); + + var releases = ArrayList(Release).init(allocator); + + // Get starred projects + const starred_projects = try getStarredProjects(allocator, &client, self.token); + defer { + for (starred_projects.items) |project| { + allocator.free(project); } + starred_projects.deinit(); + } - // Get releases for each project - for (starred_projects.items) |project_id| { - const project_releases = getProjectReleases(allocator, &client, self.token, project_id) catch |err| { - std.debug.print("Error fetching GitLab releases for project {s}: {}\n", .{ project_id, err }); - continue; - }; - defer project_releases.deinit(); + // Get releases for each project + for (starred_projects.items) |project_id| { + const project_releases = getProjectReleases(allocator, &client, self.token, project_id) catch |err| { + std.debug.print("Error fetching GitLab releases for project {s}: {}\n", .{ project_id, err }); + continue; + }; + defer project_releases.deinit(); - // Transfer ownership of the releases to the main list - for (project_releases.items) |release| { - try releases.append(release); - } + // Transfer ownership of the releases to the main list + for (project_releases.items) |release| { + try releases.append(release); } - - return releases; } - pub fn getName(self: *@This()) []const u8 { - _ = self; - return "gitlab"; - } -}; + return releases; +} + +pub fn getName(self: *Self) []const u8 { + _ = self; + return "gitlab"; +} fn getStarredProjects(allocator: Allocator, client: *http.Client, token: []const u8) !ArrayList([]const u8) { var projects = ArrayList([]const u8).init(allocator); @@ -258,10 +263,10 @@ fn parseTimestamp(date_str: []const u8) !i64 { test "gitlab provider" { const allocator = std.testing.allocator; - var provider = GitLabProvider.init(""); + var gitlab_provider = init(""); // Test with empty token (should fail gracefully) - const releases = provider.fetchReleases(allocator) catch |err| { + const releases = gitlab_provider.fetchReleases(allocator) catch |err| { try std.testing.expect(err == error.HttpRequestFailed); return; }; @@ -272,7 +277,7 @@ test "gitlab provider" { releases.deinit(); } - try std.testing.expectEqualStrings("gitlab", provider.getName()); + try std.testing.expectEqualStrings("gitlab", gitlab_provider.getName()); } test "gitlab release parsing with live data snapshot" { diff --git a/src/providers/sourcehut.zig b/src/providers/SourceHut.zig similarity index 83% rename from src/providers/sourcehut.zig rename to src/providers/SourceHut.zig index cf99546..3623a99 100644 --- a/src/providers/sourcehut.zig +++ b/src/providers/SourceHut.zig @@ -6,90 +6,95 @@ const ArrayList = std.ArrayList; const zeit = @import("zeit"); const Release = @import("../main.zig").Release; +const Provider = @import("../Provider.zig"); -pub const SourceHutProvider = struct { - repositories: [][]const u8, - token: []const u8, +repositories: [][]const u8, +token: []const u8, - pub fn init(token: []const u8, repositories: [][]const u8) SourceHutProvider { - return SourceHutProvider{ .token = token, .repositories = repositories }; +const Self = @This(); + +pub fn init(token: []const u8, repositories: [][]const u8) Self { + return Self{ .token = token, .repositories = repositories }; +} + +pub fn provider(self: *Self) Provider { + return Provider.init(self); +} + +pub fn fetchReleases(self: *Self, allocator: Allocator) !ArrayList(Release) { + return self.fetchReleasesForRepos(allocator, self.repositories, self.token); +} + +pub fn fetchReleasesForRepos(self: *Self, allocator: Allocator, repositories: [][]const u8, token: ?[]const u8) !ArrayList(Release) { + _ = self; + var client = http.Client{ .allocator = allocator }; + defer client.deinit(); + + var releases = ArrayList(Release).init(allocator); + errdefer { + for (releases.items) |release| { + release.deinit(allocator); + } + releases.deinit(); } - pub fn fetchReleases(self: *@This(), allocator: Allocator) !ArrayList(Release) { - return self.fetchReleasesForRepos(allocator, self.repositories, self.token); - } - - pub fn fetchReleasesForRepos(self: *@This(), allocator: Allocator, repositories: [][]const u8, token: ?[]const u8) !ArrayList(Release) { - _ = self; - var client = http.Client{ .allocator = allocator }; - defer client.deinit(); - - var releases = ArrayList(Release).init(allocator); - errdefer { - for (releases.items) |release| { - release.deinit(allocator); - } - releases.deinit(); - } - - for (repositories) |repo| { - const repo_tags = getRepoTags(allocator, &client, token, repo) catch |err| { - std.debug.print("Error fetching SourceHut tags for {s}: {}\n", .{ repo, err }); - continue; - }; - defer { - for (repo_tags.items) |release| { - release.deinit(allocator); - } - repo_tags.deinit(); - } - - for (repo_tags.items) |release| { - const duplicated_release = Release{ - .repo_name = try allocator.dupe(u8, release.repo_name), - .tag_name = try allocator.dupe(u8, release.tag_name), - .published_at = try allocator.dupe(u8, release.published_at), - .html_url = try allocator.dupe(u8, release.html_url), - .description = try allocator.dupe(u8, release.description), - .provider = try allocator.dupe(u8, release.provider), - }; - releases.append(duplicated_release) catch |err| { - duplicated_release.deinit(allocator); - return err; - }; - } - } - - return releases; - } - - pub fn fetchReleasesForReposFiltered(self: *@This(), allocator: Allocator, repositories: [][]const u8, token: ?[]const u8, existing_releases: []const Release) !ArrayList(Release) { - var latest_date: i64 = 0; - for (existing_releases) |release| { - if (std.mem.eql(u8, release.provider, "sourcehut")) { - const release_time = parseReleaseTimestamp(release.published_at) catch 0; - if (release_time > latest_date) { - latest_date = release_time; - } - } - } - - const all_releases = try self.fetchReleasesForRepos(allocator, repositories, token); + for (repositories) |repo| { + const repo_tags = getRepoTags(allocator, &client, token, repo) catch |err| { + std.debug.print("Error fetching SourceHut tags for {s}: {}\n", .{ repo, err }); + continue; + }; defer { - for (all_releases.items) |release| { + for (repo_tags.items) |release| { release.deinit(allocator); } - all_releases.deinit(); + repo_tags.deinit(); } - return filterNewReleases(allocator, all_releases.items, latest_date); + for (repo_tags.items) |release| { + const duplicated_release = Release{ + .repo_name = try allocator.dupe(u8, release.repo_name), + .tag_name = try allocator.dupe(u8, release.tag_name), + .published_at = try allocator.dupe(u8, release.published_at), + .html_url = try allocator.dupe(u8, release.html_url), + .description = try allocator.dupe(u8, release.description), + .provider = try allocator.dupe(u8, release.provider), + }; + releases.append(duplicated_release) catch |err| { + duplicated_release.deinit(allocator); + return err; + }; + } } - pub fn getName(self: *@This()) []const u8 { - _ = self; - return "sourcehut"; + return releases; +} + +pub fn fetchReleasesForReposFiltered(self: *Self, allocator: Allocator, repositories: [][]const u8, token: ?[]const u8, existing_releases: []const Release) !ArrayList(Release) { + var latest_date: i64 = 0; + for (existing_releases) |release| { + if (std.mem.eql(u8, release.provider, "sourcehut")) { + const release_time = parseReleaseTimestamp(release.published_at) catch 0; + if (release_time > latest_date) { + latest_date = release_time; + } + } } -}; + + const all_releases = try self.fetchReleasesForRepos(allocator, repositories, token); + defer { + for (all_releases.items) |release| { + release.deinit(allocator); + } + all_releases.deinit(); + } + + return filterNewReleases(allocator, all_releases.items, latest_date); +} + +pub fn getName(self: *Self) []const u8 { + _ = self; + return "sourcehut"; +} fn getRepoTags(allocator: Allocator, client: *http.Client, token: ?[]const u8, repo: []const u8) !ArrayList(Release) { var releases = ArrayList(Release).init(allocator); @@ -343,10 +348,10 @@ test "sourcehut provider" { const allocator = std.testing.allocator; const repos = [_][]const u8{}; - var provider = SourceHutProvider.init("", &repos); + var sourcehut_provider = init("", &repos); // Test with empty token (should fail gracefully) - const releases = provider.fetchReleases(allocator) catch |err| { + const releases = sourcehut_provider.fetchReleases(allocator) catch |err| { try std.testing.expect(err == error.HttpRequestFailed); return; }; @@ -357,7 +362,7 @@ test "sourcehut provider" { releases.deinit(); } - try std.testing.expectEqualStrings("sourcehut", provider.getName()); + try std.testing.expectEqualStrings("sourcehut", sourcehut_provider.getName()); } test "sourcehut release parsing with live data snapshot" {