provider cleanup

This commit is contained in:
Emil Lerch 2025-07-12 18:27:36 -07:00
parent 7490ff3bc5
commit fd8242784d
Signed by: lobo
GPG key ID: A7B62D657EF764F8
6 changed files with 233 additions and 219 deletions

View file

@ -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
};

View file

@ -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);

View file

@ -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" {

View file

@ -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" {

View file

@ -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" {

View file

@ -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" {