Last active
January 3, 2025 15:12
-
-
Save paulosuzart/37aaeb8ab1de70e68404259bf928e371 to your computer and use it in GitHub Desktop.
Simple fetching starred items from github
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! A simple test that creates a http client, fetches the first page of starred items for a given user | |
//! and prints each repo with their topics, as well as each language and the amount of repos for that language. | |
//! Sample output: | |
//! Repo name is gluesql/gluesql ({ database, nosql, rust, schemaless, sql, storage, webassembly, websql }) | |
//! Repo name is efugier/smartcat ({ ai, chatgpt, cli, command-line, command-line-tool, copilot, llm, mistral-ai, unix }) | |
//! Repo name is regolith-labs/steel ({ solana }) | |
//! Language Rust: 13 repos | |
//! Language Zig: 2 repos | |
//! Language Jupyter Notebook: 1 repos | |
//! Language HTML: 1 repos | |
//! ... | |
const std = @import("std"); | |
const lib = @import("root.zig"); | |
const Location = std.http.Client.FetchOptions.Location; | |
const URI = "https://api.github.com/user/starred"; | |
pub fn main() !void { | |
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | |
const allocator = gpa.allocator(); | |
// gets the token from the env | |
const token = std.posix.getenv("TOKEN") orelse { | |
std.log.err("Please provide a TOKEN env", .{}); | |
return; | |
}; | |
// creates the bearer | |
const bearer = try std.fmt.allocPrint(allocator, "Bearer {s}", .{token}); | |
// allocates the http client | |
var client = std.http.Client{ | |
.allocator = allocator, | |
}; | |
const uri = try std.Uri.parse(URI); | |
// We allocate the space where the resultg body will be placed | |
var respStorage = std.ArrayList(u8).init(allocator); | |
const opts = std.http.Client.FetchOptions{ | |
.location = Location{ .uri = uri }, | |
.method = std.http.Method.GET, | |
.headers = std.http.Client.Request.Headers{ .authorization = .{ .override = bearer } }, | |
.response_storage = .{ .dynamic = &respStorage }, | |
}; | |
const res = try client.fetch(opts); | |
if (res.status != std.http.Status.ok) { | |
std.debug.print("Error general: {s}", .{respStorage.items}); | |
return; | |
} | |
const repos = try std.json.parseFromSlice([]lib.Repo, allocator, respStorage.items, .{ | |
.ignore_unknown_fields = true, | |
}); | |
for (repos.value) |r| { | |
std.debug.print("Repo name is {s}/{s} ({s})\n", .{ r.owner.login, r.name, r.topics }); | |
} | |
const x = &repos.value; | |
var groupped = std.StringHashMap([]*lib.Repo).init(allocator); | |
try lib.byLanguage(allocator, x, &groupped); | |
var langIter = groupped.iterator(); | |
while (langIter.next()) |g| { | |
std.debug.print("Language {s}: {d} repos\n", .{ g.key_ptr.*, g.value_ptr.*.len }); | |
} | |
// careful ordered deinit and free is needed in order for everything to work. Otherwise we get runtime panics. | |
// There's so much stuff to deinit and free that the would need to get a LanguageGroup that takes the allocator and | |
// does the dirty work by itself. Same for the client. | |
defer { | |
var iter = groupped.iterator(); | |
while (iter.next()) |o| { | |
allocator.free(o.value_ptr.*); | |
} | |
groupped.deinit(); | |
repos.deinit(); | |
respStorage.deinit(); | |
allocator.free(bearer); | |
client.deinit(); | |
_ = gpa.deinit(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Aggregates the struct and the by_language stuff. | |
const std = @import("std"); | |
const testing = std.testing; | |
pub const Owner = struct { | |
login: []u8, | |
}; | |
pub const Repo = struct { | |
name: []u8, | |
owner: Owner, | |
description: ?[]u8, | |
topics: [][]u8, | |
language: ?[]u8, | |
}; | |
/// Group the repositories by language. Takes the target HashMap as a parameter because I couldn't make it | |
/// work in a way that the hashmap was returned. (At least not without breaking here and there). | |
/// The calling code is responsible for cleaning up the hashmap INCLUDING THE allocated slices for pointers to Repo. | |
pub fn byLanguage(allocator: std.mem.Allocator, repos: *const []Repo, target: *std.StringHashMap([]*Repo)) !void { | |
for (repos.*) |*repo| { | |
const language = repo.language orelse "not-set"; | |
const gop = try target.getOrPut(language); | |
if (!gop.found_existing) { | |
// Allocate a slice of one repo pointer for new languages | |
gop.value_ptr.* = try allocator.alloc(*Repo, 1); | |
gop.value_ptr.*[0] = repo; // repo is now a pointer | |
} else { | |
// Extend the existing slice of repo pointers | |
const current_slice = gop.value_ptr.*; | |
var new_slice = try allocator.realloc(current_slice, current_slice.len + 1); | |
new_slice[current_slice.len] = repo; // repo is now a pointer | |
gop.value_ptr.* = new_slice; | |
} | |
} | |
} | |
test "byLanguage works" { | |
const allocator = std.testing.allocator; | |
const repos = [_]Repo{ | |
.{ | |
.name = @constCast("repo1"), | |
.language = @constCast("zig"), | |
.owner = Owner{ .login = @constCast("ps") }, | |
.topics = undefined, | |
.description = @constCast("sample"), | |
}, | |
.{ | |
.name = @constCast("repo2"), | |
.language = @constCast("rust"), | |
.owner = Owner{ .login = @constCast("ps") }, | |
.topics = undefined, | |
.description = @constCast("sample"), | |
}, | |
.{ | |
.name = @constCast("repo3"), | |
.language = @constCast("rust"), | |
.owner = Owner{ .login = @constCast("ps") }, | |
.topics = undefined, | |
.description = @constCast("sample"), | |
}, | |
}; | |
const repo_slice = try allocator.alloc(Repo, repos.len); | |
defer allocator.free(repo_slice); | |
@memcpy(repo_slice, &repos); | |
var groupped = std.StringHashMap([]*Repo).init(allocator); | |
try byLanguage(allocator, @constCast(&repo_slice), &groupped); | |
defer { | |
var iter = groupped.iterator(); | |
while (iter.next()) |e| { | |
allocator.free(e.value_ptr.*); | |
} | |
groupped.deinit(); | |
} | |
try std.testing.expectEqual(@as(usize, 2), groupped.count()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment