From 2a5df4609efabd21775e792df9ba7e1d38e098e2 Mon Sep 17 00:00:00 2001 From: drvxor Date: Sun, 23 Aug 2026 21:43:19 +0300 Subject: [PATCH] Initial Commit --- .gitignore | 3 ++ build.zig | 24 +++++++++ build.zig.zon | 10 ++++ index/main/natural.mt | 2 + src/config.zig | 8 +++ src/index.zig | 30 +++++++++++ src/install.zig | 37 ++++++++++++++ src/main.zig | 56 +++++++++++++++++++++ src/network.zig | 57 +++++++++++++++++++++ src/package.zig | 8 +++ src/remove.zig | 0 src/repo.zig | 113 ++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 348 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 index/main/natural.mt create mode 100644 src/config.zig create mode 100644 src/index.zig create mode 100644 src/install.zig create mode 100644 src/main.zig create mode 100644 src/network.zig create mode 100644 src/package.zig create mode 100644 src/remove.zig create mode 100644 src/repo.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4791fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +./index +.zig-cache +zig-out diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..4b83251 --- /dev/null +++ b/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "xpk", + .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize }), + }); + + b.installArtifact(exe); + + const run_step = b.step("run", "Run the app"); + + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..a2f4577 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = .xpk, + .version = "0.0.0", + + .fingerprint = 0x50fc21dcc3f3cc25, + .minimum_zig_version = "0.16.0", + .dependencies = .{}, + + .paths = .{ "build.zig", "build.zig.zon", "src", ".gitignore" }, +} diff --git a/index/main/natural.mt b/index/main/natural.mt new file mode 100644 index 0000000..ae39190 --- /dev/null +++ b/index/main/natural.mt @@ -0,0 +1,2 @@ +description A small example package +license MIT diff --git a/src/config.zig b/src/config.zig new file mode 100644 index 0000000..81cdf6f --- /dev/null +++ b/src/config.zig @@ -0,0 +1,8 @@ +pub const installed_directory = "./installed"; +pub const index_directory = "./index"; + +pub const arch = "x86_64"; + +pub const repos = .{ + .main = "http://localhost:3000", +}; diff --git a/src/index.zig b/src/index.zig new file mode 100644 index 0000000..15660f5 --- /dev/null +++ b/src/index.zig @@ -0,0 +1,30 @@ +const std = @import("std"); + +const config = @import("config.zig"); + +pub fn load( + init: std.process.Init, + repo_name: []const u8, + package_name: []const u8, +) ![]u8 { + const allocator = init.gpa; + const cwd = std.Io.Dir.cwd(); + + const path = try std.fmt.allocPrint( + allocator, + "{s}/{s}/{s}.mt", + .{ + config.index_directory, + repo_name, + package_name, + }, + ); + defer allocator.free(path); + + return try cwd.readFileAlloc( + init.io, + path, + allocator, + .limited(1024 * 1024), + ); +} diff --git a/src/install.zig b/src/install.zig new file mode 100644 index 0000000..182bb58 --- /dev/null +++ b/src/install.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +const index = @import("index.zig"); + +pub fn run(init: std.process.Init, args: []const [:0]const u8) !void { + if (args.len < 3) { + std.debug.print("usage: xpk install \n", .{}); + return; + } + + const package_name = args[2]; + + const metadata = index.load( + init, + "main", + package_name, + ) catch |err| { + if (err == error.FileNotFound) { + std.debug.print( + "package '{s}' is not in the local index\n", + .{package_name}, + ); + return; + } + + return err; + }; + defer init.gpa.free(metadata); + + std.debug.print( + "metadata for {s}:\n{s}\n", + .{ + package_name, + metadata, + }, + ); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..9233179 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,56 @@ +const std = @import("std"); +const Io = std.Io; + +const config = @import("config.zig"); +const network = @import("network.zig"); +const install = @import("install.zig"); +const repo = @import("repo.zig"); + +const Command = enum { + install, + remove, + sync, + search, + info, +}; + +fn parse_command(name: []const u8) !Command { + if (std.mem.eql(u8, name, "install")) return .install; + if (std.mem.eql(u8, name, "remove")) return .remove; + if (std.mem.eql(u8, name, "sync")) return .sync; + if (std.mem.eql(u8, name, "search")) return .search; + if (std.mem.eql(u8, name, "info")) return .info; + + return error.UnknownCommand; +} + +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + + const args = try init.minimal.args.toSlice(arena); + + if (args.len < 2) { + std.debug.print("usage: xpk \n", .{}); + return; + } + + const command = try parse_command(args[1]); + + switch (command) { + .install => try install.run(init, args), + + .remove => { + // later + }, + + .sync => try repo.sync(init), + + .search => { + // later + }, + + .info => { + // later + }, + } +} diff --git a/src/network.zig b/src/network.zig new file mode 100644 index 0000000..174760a --- /dev/null +++ b/src/network.zig @@ -0,0 +1,57 @@ +const std = @import("std"); + +pub fn download( + io: std.Io, + allocator: std.mem.Allocator, + url: []const u8, + path: []const u8, +) !void { + var client = std.http.Client{ + .allocator = allocator, + .io = io, + }; + defer client.deinit(); + + const cwd = std.Io.Dir.cwd(); + + var file = try cwd.createFile(io, path, .{}); + defer file.close(io); + + var writer_buffer: [64 * 1024]u8 = undefined; + var writer = file.writer(io, &writer_buffer); + + const response = try client.fetch(.{ + .location = .{ .url = url }, + .response_writer = &writer.interface, + }); + + if (response.status != .ok) { + return error.HttpError; + } +} + +pub fn get( + io: std.Io, + allocator: std.mem.Allocator, + url: []const u8, +) ![]u8 { + var client = std.http.Client{ + .allocator = allocator, + .io = io, + }; + defer client.deinit(); + + var body = std.Io.Writer.Allocating.init(allocator); + defer body.deinit(); + + const response = try client.fetch(.{ + .location = .{ .url = url }, + .response_writer = &body.writer, + }); + + if (response.status != .ok) { + return error.HttpError; + } + + return body.toOwnedSlice(); +} diff --git a/src/package.zig b/src/package.zig new file mode 100644 index 0000000..bae8672 --- /dev/null +++ b/src/package.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +pub const Package = struct { + name: []const u8, + version: []const u8, + description: ?[]const u8, + license: ?[]const u8, +}; diff --git a/src/remove.zig b/src/remove.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/repo.zig b/src/repo.zig new file mode 100644 index 0000000..adcf347 --- /dev/null +++ b/src/repo.zig @@ -0,0 +1,113 @@ +const std = @import("std"); + +const config = @import("config.zig"); +const network = @import("network.zig"); + +pub fn sync(init: std.process.Init) !void { + const allocator = init.gpa; + const repo = "main"; + + const list_url = try std.fmt.allocPrint( + allocator, + "{s}/{s}/{s}/.mt", + .{ + config.repos.main, + repo, + config.arch, + }, + ); + defer allocator.free(list_url); + + std.debug.print("syncing {s}\n", .{list_url}); + + const list = try network.get( + init.io, + allocator, + list_url, + ); + defer allocator.free(list); + + const cwd = std.Io.Dir.cwd(); + + cwd.createDir( + init.io, + config.index_directory, + .default_dir, + ) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => return err, + }; + + const index_path = try std.fmt.allocPrint( + allocator, + "{s}/{s}", + .{ + config.index_directory, + repo, + }, + ); + defer allocator.free(index_path); + + cwd.createDir( + init.io, + index_path, + .default_dir, + ) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => return err, + }; + + var lines = std.mem.splitScalar(u8, list, '\n'); + + while (lines.next()) |line| { + const package = std.mem.trim(u8, line, " \r\t"); + + if (package.len == 0) + continue; + + const url = try std.fmt.allocPrint( + allocator, + "{s}/{s}/{s}/{s}/.mt", + .{ + config.repos.main, + repo, + config.arch, + package, + }, + ); + defer allocator.free(url); + + std.debug.print(" {s}\n", .{package}); + + const metadata = try network.get( + init.io, + init.gpa, + url, + ); + defer init.gpa.free(metadata); + + const path = try std.fmt.allocPrint( + allocator, + "{s}/{s}.mt", + .{ + index_path, + package, + }, + ); + defer allocator.free(path); + + var file = try cwd.createFile( + init.io, + path, + .{}, + ); + defer file.close(init.io); + + try file.writeStreamingAll( + init.io, + metadata, + ); + } + + std.debug.print("sync complete\n", .{}); +}