From 86bce941de2c41101ebed43e01865968ba89d1b4 Mon Sep 17 00:00:00 2001 From: Nathan Anderson Date: Mon, 9 Oct 2023 22:54:00 -0600 Subject: [PATCH] First commit, things mostly work locally, on linux x86 --- .gitignore | 2 + .ignore | 2 + build.zig | 70 ++++++++++ src/main.zig | 237 +++++++++++++++++++++++++++++++++ src/utils.zig | 47 +++++++ src/version.zig | 101 ++++++++++++++ src/zim.zig | 348 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 807 insertions(+) create mode 100644 .gitignore create mode 100644 .ignore create mode 100644 build.zig create mode 100644 src/main.zig create mode 100644 src/utils.zig create mode 100644 src/version.zig create mode 100644 src/zim.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a0641e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ \ No newline at end of file diff --git a/.ignore b/.ignore new file mode 100644 index 0000000..e73c965 --- /dev/null +++ b/.ignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..2210c88 --- /dev/null +++ b/build.zig @@ -0,0 +1,70 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "zim", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..8639573 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,237 @@ +const std = @import("std"); +const zim = @import("zim.zig"); +const utils = @import("utils.zig"); + +const string = []const u8; + +pub const ZIM_MAJOR_VER = 0; +pub const ZIM_MINOR_VER = 0; +pub const ZIM_BUILD = 1; + +/// The availble arguments for the ZiM program +const Args = enum(u8) { + help, + install, + list, + use, + gir, + version, +}; + +/// A type of argument for the ZiM program +const ArgType = enum { + single, + expectsOneParam, +}; + +/// Allocates a string with the version of Zim, call `allocator.free()` with +/// the string when done using it. +pub fn versionStr(allocator: std.mem.Allocator) ![]const u8 { + var buf = try allocator.alloc(u8, 10); + _ = try std.fmt.bufPrint(buf, "{d}.{d}.{d}", .{ ZIM_MAJOR_VER, ZIM_MINOR_VER, ZIM_BUILD }); + return buf; +} + +/// Returns what each argument expects to follow it +fn get_arg_parse_type(arg: Args) ArgType { + return switch (arg) { + .help => ArgType.single, + .install => ArgType.expectsOneParam, + .list => ArgType.expectsOneParam, + .use => ArgType.expectsOneParam, + .gir => ArgType.single, + .version => ArgType.single, + }; +} + +/// For a given `Args`, returns a string description of what each does +fn command_desc(arg: Args) string { + return switch (arg) { + .help => " help\t\tDisplays this help message", + .install => " install\tInstall the zig version", + .list => " list\t\tLists all versions available on the system", + .use => " use\t\tSets the active version of zig to use", + .version => " version\t\tPrint out the zim version you are using", + .gir => " gir\t\tPrint out ascii Gir", + }; +} + +/// Shows the main help menu for the program +fn show_help() void { + std.debug.print("Welcome to the ZIg version Manager (ZIM)\nHelp Menu:\n\n", .{}); + inline for (@typeInfo(Args).Enum.fields) |arg_type| { + const tag = utils.nameToEnumTag(arg_type.name, Args) catch { + unreachable; + }; + std.debug.print("{s}\n", .{command_desc(tag)}); + } + std.debug.print("\n", .{}); +} + +/// Shows a help menu for a specific `Args` action, like a help sub-menu +fn show_context_help(arg: Args) void { + switch (arg) { + .install => { + std.debug.print( + \\zim install + \\ Used to install zig versions, that will then be available to + \\ the `zim use ` command. + \\ + \\ To see all available versions to install, run `zim list global` + \\ + \\ + , .{}); + }, + .list => { + std.debug.print( + \\zim list + \\ Used to list zig versions, either locally or remotely available. + \\ Running zim global will make a network request to fetch the latest + \\ versions of zig. + \\ + \\ + , .{}); + }, + .use => { + std.debug.print( + \\zim use + \\ Used to switch the actively used zig version on the system. If + \\ your system PATH environment is set to use `~/.config/zim/zig`. + \\ If your system is not setup, you can run `zim init` and cross + \\ your fingers that it supports your setup. + \\ + \\ + , .{}); + }, + .gir, .version, .help => { + return; + }, + } +} + +/// Given `arg`, does the action with the provided optional `param` +fn do_arg_action(allocator: std.mem.Allocator, arg: Args, param: ?[]const u8) void { + switch (arg) { + .help => { + show_help(); + }, + .gir => { + utils.printGir(); + }, + .version => { + const ver = versionStr(allocator) catch { + std.debug.print("Error getting version\n", .{}); + return; + }; + defer allocator.free(ver); + + if (param) |command| { + std.debug.print("{s}\nRunning ZiM Version {s}\n", .{ command, ver }); + } else { + std.debug.print("Running ZiM Version {s}\n", .{ver}); + } + + utils.printGir(); + }, + .install => { + if (param == null) { + std.debug.print("Error: Expected version to follow\n\n", .{}); + std.os.exit(1); + return; + } + const p = param.?; + if (std.mem.eql(u8, p, "help")) { + show_context_help(arg); + std.os.exit(0); + return; + } + zim.install(allocator, param.?) catch |err| { + std.debug.print("Error running `install {s}`\n{any}", .{ param.?, err }); + std.os.exit(1); + }; + }, + .list => { + if (param == null) { + std.debug.print("Error: Expected `local` or `global` to follow.\nRun `zim list help` for details.\n\n", .{}); + std.os.exit(1); + } + const p = param.?; + if (std.mem.eql(u8, p, "help")) { + show_context_help(arg); + std.os.exit(0); + return; + } + if (!std.mem.eql(u8, p, "local") and !std.mem.eql(u8, p, "global")) { + std.debug.print("Error: List available versions either `list local` or `list global`\n\n", .{}); + show_context_help(arg); + std.os.exit(1); + } + zim.list(allocator, param.?) catch |err| { + std.debug.print("Error running `list {s}`\n{any}", .{ param.?, err }); + std.os.exit(1); + }; + }, + .use => { + if (param == null) { + std.debug.print("Error: Expected version to follow\n\n", .{}); + std.os.exit(1); + } + const p = param.?; + if (std.mem.eql(u8, p, "help")) { + show_context_help(arg); + std.os.exit(0); + return; + } + zim.use(allocator, param.?) catch |err| { + std.debug.print("Error running `use {s}`\n{any}", .{ param.?, err }); + std.os.exit(1); + }; + }, + } +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + + var args_iter = try std.process.argsWithAllocator(allocator); + defer args_iter.deinit(); + + // var parsed_args = std.ArrayList(string).init(allocator); + // defer parsed_args.deinit(); + + const valid = arg_loop: { + var index: u8 = 0; + const command = args_iter.next(); + + while (args_iter.next()) |arg| { + index += 1; + const arg_tag = utils.nameToEnumTag(arg, Args) catch { + break :arg_loop false; + }; + var param: ?[]const u8 = null; + if (get_arg_parse_type(arg_tag) == ArgType.expectsOneParam) { + param = args_iter.next(); + } + if (arg_tag == .version) { + do_arg_action( + allocator, + arg_tag, + command, + ); + break; + } + do_arg_action(allocator, arg_tag, param); + break; + } + if (index == 0) { + show_help(); + } + break :arg_loop true; + }; + + if (!valid) { + std.debug.print("Something is not right with the command, try running `zim help` for details.\n", .{}); + std.os.exit(1); + } +} diff --git a/src/utils.zig b/src/utils.zig new file mode 100644 index 0000000..899c352 --- /dev/null +++ b/src/utils.zig @@ -0,0 +1,47 @@ +const std = @import("std"); + +const enumError = error{EnumFieldNotFound}; + +pub fn nameToEnumTag(name: []const u8, comptime Enum: type) enumError!Enum { + comptime { + if (@typeInfo(Enum) != .Enum) { + @compileError("Non enum type passed into function: " ++ @typeName(Enum)); + } + } + + inline for (@typeInfo(Enum).Enum.fields) |field| { + if (std.mem.eql(u8, name, field.name)) { + const tag: Enum = @enumFromInt(field.value); + return tag; + } + } + return enumError.EnumFieldNotFound; +} + +test { + const State = enum { + good, + bad, + }; + + const enum_tag = try nameToEnumTag("good", State); + try std.testing.expect(enum_tag == State.good); +} + +pub fn printGir() void { + std.debug.print( + \\ n n + \\ o ( ) + \\ ___/_ / I am \ + \\ / / . o ( your ) + \\ 0> 0 / \ Doom! / + \\ Lu__/ ( ) + \\ || - u - + \\ o^ ^ o + \\ _/ / / \_ + \\ |__| + \\ O=> (O + \\ + \\ + , .{}); +} diff --git a/src/version.zig b/src/version.zig new file mode 100644 index 0000000..8c653a6 --- /dev/null +++ b/src/version.zig @@ -0,0 +1,101 @@ +const std = @import("std"); +const string = []const u8; + +pub const ZigVersion = struct { + allocator: std.mem.Allocator, + version_string: string, + tarball_url: ?string, + src_tarball_url: ?string, + platform_string: ?string, + docs_url: ?string, + + pub fn init(allocator: std.mem.Allocator, scanner: *std.json.Scanner, ver_string: string) ZigVersion { + var version_string = allocator.dupe(u8, ver_string) catch unreachable; + var docs_url: ?string = null; + var tarball_url: ?string = null; + var platform_string: ?string = null; + var src_tarball_url: ?string = null; + + var scanning = true; + while (scanning) { + if (scanner.*.next()) |token| { + switch (token) { + .object_begin => {}, + .object_end => { + const t_type = scanner.*.peekNextTokenType() catch { + unreachable; + }; + if (t_type == std.json.TokenType.object_end) { + scanning = false; + } + }, + .string => { + if (std.mem.eql(u8, token.string, "docs")) { + const docs_token = scanner.*.next() catch unreachable; + docs_url = allocator.dupe(u8, docs_token.string) catch unreachable; + } else if (std.mem.eql(u8, token.string, "src")) { + // ignore the object begin + checkNextToken(scanner, .object_begin); + _ = scanner.*.next() catch unreachable; + // ignore tarball string + checkNextToken(scanner, .string); + _ = scanner.*.next() catch unreachable; + const tar_token = scanner.*.next() catch unreachable; + src_tarball_url = allocator.dupe(u8, tar_token.string) catch unreachable; + } else if (std.mem.eql(u8, token.string, "x86_64-linux")) { + platform_string = allocator.dupe(u8, token.string) catch unreachable; + // ignore object begin + checkNextToken(scanner, .object_begin); + _ = scanner.*.next() catch unreachable; + checkNextToken(scanner, .string); + const tar_token = scanner.*.next() catch unreachable; + if (std.mem.eql(u8, tar_token.string, "tarball")) { + const tar_url_token = scanner.*.next() catch unreachable; + tarball_url = allocator.dupe(u8, tar_url_token.string) catch unreachable; + } + } + }, + else => { + unreachable; + }, + } + } else |err| { + std.debug.print("Error scanning in ZigVersion: {any}\n", .{err}); + scanning = false; + unreachable; + } + } + + return ZigVersion{ .allocator = allocator, .version_string = version_string, .tarball_url = tarball_url, .src_tarball_url = src_tarball_url, .platform_string = platform_string, .docs_url = docs_url }; + } + + pub fn deinit() void {} + + pub fn fmtPrint(zv: *const ZigVersion) void { + std.debug.print(" zig-{s}\n", .{zv.version_string}); + if (zv.platform_string) |plat| { + std.debug.print("\tplatform: {s}\n", .{plat}); + } + if (zv.docs_url) |docs| { + std.debug.print("\tdocs_url: {s}\n", .{docs}); + } + if (zv.tarball_url) |tar| { + std.debug.print("\ttar_url: {s}\n", .{tar}); + } + if (zv.src_tarball_url) |tar| { + std.debug.print("\tsrc_tar_url: {s}\n", .{tar}); + } + } + + pub fn hasTar(zv: *ZigVersion) bool { + return zv.tarball_url != null; + } +}; + +fn checkNextToken(scanner: *std.json.Scanner, t_type: std.json.TokenType) void { + const t = scanner.*.peekNextTokenType() catch unreachable; + if (t != t_type) { + std.debug.print("Expected to find {any} but got {any}\n", .{ t_type, t }); + unreachable; + } +} diff --git a/src/zim.zig b/src/zim.zig new file mode 100644 index 0000000..5d38144 --- /dev/null +++ b/src/zim.zig @@ -0,0 +1,348 @@ +const std = @import("std"); +const utils = @import("utils.zig"); +const version = @import("version.zig"); +const string = []const u8; + +const ZigVersion = version.ZigVersion; + +const ZimError = error{ + BadParameter, + MalformedJson, + NetworkError, + NotImplemented, + NoHomeConfigured, + Unexpected, +}; + +const BIN_SYM_ZIG_PATH = "bin/zig"; + +fn createSubDir(dir: std.fs.Dir, path: string) ZimError!void { + dir.makeDir(path) catch |err| blk: { + if (err == error.PathAlreadyExists) { + // std.debug.print("{s} already exists\n", .{path}); + break :blk; + } + std.debug.print("Encountered error creating directory {s}: {any}\n", .{ path, err }); + return ZimError.Unexpected; + }; +} + +fn openZimDir(allocator: std.mem.Allocator) ZimError!std.fs.Dir { + const home_dir = std.os.getenv("HOME"); + if (home_dir == null) { + return ZimError.NoHomeConfigured; + } + // std.debug.print("Found home env to be: {s}\n", .{home_dir.?}); + const zimPath = std.mem.concat(allocator, u8, &[_][]const u8{ home_dir.?, "/.config/zim" }) catch { + return ZimError.Unexpected; + }; + defer allocator.free(zimPath); + + // std.debug.print("Creating zim directory in {s}\n", .{zimPath}); + std.fs.makeDirAbsolute(zimPath) catch |err| blk: { + if (err == error.PathAlreadyExists) { + // std.debug.print("Do not need to create new directory\n", .{}); + break :blk; + } + std.debug.print("Error creating directory: {any}\n", .{err}); + return ZimError.Unexpected; + }; + + var zim_dir = std.fs.openDirAbsolute(zimPath, .{}) catch { + // std.debug.print("Encountered error opening directory: {any}\n", .{err}); + return ZimError.Unexpected; + }; + + try createSubDir(zim_dir, "bin"); + try createSubDir(zim_dir, "versions"); + return zim_dir; +} + +fn getLocalVersionsList( + allocator: std.mem.Allocator, + zim_dir: std.fs.Dir, +) ZimError!std.ArrayList(string) { + var versions_dir = zim_dir.openIterableDir("versions", .{}) catch { + return ZimError.Unexpected; + }; + defer versions_dir.close(); + + var ver_walker = versions_dir.walk(allocator) catch { + return ZimError.Unexpected; + }; + defer ver_walker.deinit(); + + var versions_list = std.ArrayList(string).init(allocator); + + var walking = true; + while (walking) blk: { + var ver = ver_walker.next() catch { + walking = false; + break :blk; + }; + if (ver == null) { + walking = false; + break :blk; + } + // Dont recursively enter into any of the zig directories + if (!std.mem.eql(u8, ver.?.basename, ver.?.path)) { + break :blk; + } + if (ver.?.kind == .directory) { + const path = ver.?.path; + const start_name = path[0..3]; + if (std.mem.eql(u8, start_name, "zig")) { + var p = allocator.dupe(u8, path) catch { + return ZimError.Unexpected; + }; + + versions_list.append(p) catch { + return ZimError.Unexpected; + }; + } + } + // std.debug.print("Got entry in `versions:` base: {s}, path: {s}, kind: {any}\n", .{ ver.?.basename, ver.?.path, ver.?.kind }); + } + return versions_list; +} + +fn getRemoteVersionsList(allocator: std.mem.Allocator) ZimError!std.ArrayList(ZigVersion) { + const versions_json_url = "https://ziglang.org/download/index.json"; + const versions_json_uri = std.Uri.parse(versions_json_url) catch { + return ZimError.Unexpected; + }; + + var headers = std.http.Headers{ .allocator = allocator }; + defer headers.deinit(); + + // Accept anything. + headers.append("accept", "*/*") catch { + std.debug.print("Error adding headers\n", .{}); + return ZimError.Unexpected; + }; + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + var request = client.request(.GET, versions_json_uri, headers, .{}) catch { + std.debug.print("Error creating request\n", .{}); + return ZimError.NetworkError; + }; + defer request.deinit(); + + request.start() catch { + std.debug.print("Error starting request\n", .{}); + return ZimError.NetworkError; + }; + std.debug.print("Querying ziglang.org for latest zig versions...\n", .{}); + request.wait() catch { + std.debug.print("Error waiting for request\n", .{}); + return ZimError.NetworkError; + }; + + const sixty_four_kilobytes = 65536; + const body = request.reader().readAllAlloc(allocator, sixty_four_kilobytes) catch |err| { + std.debug.print("Error getting body: {any}\n", .{err}); + return ZimError.NetworkError; + }; + defer allocator.free(body); + + var diag = std.json.Diagnostics{}; + var body_scanner = std.json.Scanner.initCompleteInput(allocator, body); + body_scanner.enableDiagnostics(&diag); + + var remote_zig_versions = std.ArrayList(ZigVersion).init(allocator); + + var scanning = true; + var last_version: string = ""; + while (scanning) { + if (body_scanner.next()) |token| { + switch (token) { + .object_begin => { + const zv = ZigVersion.init(allocator, &body_scanner, last_version); + remote_zig_versions.append(zv) catch { + return ZimError.Unexpected; + }; + }, + .end_of_document => { + scanning = false; + }, + .string => { + last_version = token.string; + }, + else => {}, + } + } else |err| switch (err) { + error.SyntaxError => { + std.debug.print("Syntax error at line {d} col {d}\n", .{ diag.line_number, diag.getColumn() }); + scanning = false; + }, + else => { + std.debug.print("Got error {any}\n", .{err}); + scanning = false; + }, + } + } + return remote_zig_versions; +} + +const ShellType = enum { + bash, + zsh, +}; + +fn shellName(s: ?ShellType) string { + comptime switch (s) { + .bash => "bash", + .zsh => "zsh", + }; +} + +fn printZimPathHelp(shell_tag: ?ShellType, zim_path: string) void { + if (shell_tag == undefined) { + std.debug.print("Unrecognized shell\n", .{}); + return; + } + const t = "something"; + _ = t; + switch (shell_tag.?) { + .bash => { + std.debug.print("\nDetected shell as {s}\n", .{@tagName(shell_tag.?)}); + std.debug.print("For ZiM to work, you need to add\n'{s}'\nto your PATH. To do that, you can\nrun the following command:\n\n", .{zim_path}); + std.debug.print("\techo \"export PATH={s}:$PATH\" >> ~/.zshrc\n\nOr edit your .zshrc to add it manually.", .{zim_path}); + }, + .zsh => { + std.debug.print("\nDetected shell as {s}\n", .{@tagName(shell_tag.?)}); + std.debug.print("For ZiM to work, you need to add\n'{s}'\nto your PATH. To do that, you can\nrun the following command:\n\n", .{zim_path}); + std.debug.print("\techo \"export PATH={s}:$PATH\" >> ~/.zshrc\n\nOr edit your .zshrc to add it manually.", .{zim_path}); + }, + } +} + +pub fn install(allocator: std.mem.Allocator, param: string) ZimError!void { + _ = param; + + var zim_dir = try openZimDir(allocator); + defer zim_dir.close(); + + return ZimError.NotImplemented; +} + +pub fn use(allocator: std.mem.Allocator, param: string) ZimError!void { + var zim_dir = try openZimDir(allocator); + defer zim_dir.close(); + + var versions_list = try getLocalVersionsList(allocator, zim_dir); + defer versions_list.deinit(); + + const version_num = std.fmt.parseInt(u8, param, 0) catch null; + if (version_num != null) { + const num = version_num.?; + if (num > versions_list.items.len) { + std.debug.print("{d} exceeds the range of available versions: {d}\n", .{ num, versions_list.items.len }); + return ZimError.BadParameter; + } + const ver_path = versions_list.items[version_num.? - 1]; + std.debug.print("Using version {s}\n", .{ver_path}); + + const zim_path = zim_dir.realpathAlloc(allocator, "./") catch "err"; + defer allocator.free(zim_path); + + const target_version_path = std.mem.concat(allocator, u8, &[_][]const u8{ zim_path, "/versions/", ver_path }) catch { + return ZimError.Unexpected; + }; + defer allocator.free(target_version_path); + + zim_dir.deleteFile(BIN_SYM_ZIG_PATH) catch |err| { + if (err != error.FileNotFound) { + std.debug.print("Error deleting old link: {any}\n", .{err}); + return ZimError.Unexpected; + } + }; + std.debug.print("Creating sym link to {s}\n", .{target_version_path}); + zim_dir.symLink(target_version_path, BIN_SYM_ZIG_PATH, .{ .is_directory = true }) catch |err| { + std.debug.print("Could not link new version: {any}\n", .{err}); + return ZimError.Unexpected; + }; + + const path_environment = std.os.getenv("PATH"); + if (path_environment) |path_env| { + if (!std.mem.containsAtLeast(u8, path_env, 1, zim_path)) { + var shell_tag: ?ShellType = null; + const shell_env = std.os.getenv("SHELL"); + if (shell_env) |s| { + var split = std.mem.splitSequence(u8, s, "/"); + var shell: []const u8 = ""; + while (split.next()) |field| { + shell = field; + } + shell_tag = utils.nameToEnumTag(shell, ShellType) catch null; + } + const shell_bin_path = + std.mem.concat(allocator, u8, &[_][]const u8{ zim_path, "/", BIN_SYM_ZIG_PATH }) catch ""; + printZimPathHelp( + shell_tag, + shell_bin_path, + ); + } + } + return; + } else { + std.debug.print("Using raw string version {s}\n", .{param}); + return ZimError.NotImplemented; + } + + // std.debug.print("Local Zig Versions:\n", .{}); + // var i: u8 = 0; + // while (versions_list.popOrNull()) |version| { + // std.debug.print("\n [{d}]\tZig Version {s}", .{ i + 1, version }); + // i += 1; + // } + +} + +const ZimListType = enum { local, global }; + +/// Runs the `list` subcommand +pub fn list(allocator: std.mem.Allocator, param: string) !void { + const list_type = utils.nameToEnumTag(param, ZimListType) catch { + std.debug.print("Unexpected parameter to `list`: {s}\n", .{param}); + return ZimError.BadParameter; + }; + switch (list_type) { + .local => { + var zim_dir = try openZimDir(allocator); + defer zim_dir.close(); + + var versions_list = try getLocalVersionsList(allocator, zim_dir); + defer versions_list.deinit(); + + std.debug.print("Local Zig Versions:\n", .{}); + var i: u8 = 0; + while (versions_list.popOrNull()) |version_str| { + std.debug.print("\n [{d}]\tZig Version {s}", .{ i + 1, version_str }); + i += 1; + } + std.debug.print("\n", .{}); + std.debug.print( + \\ + \\ Run `zim use` to select which version you want active in your environment. + \\ You can also just specify the index of the version you would like to use. + \\ + , .{}); + + return; + }, + .global => { + var remote_versions = try getRemoteVersionsList(allocator); + if (remote_versions.items.len == 0) { + std.debug.print("Failed to get remote versions\n", .{}); + return ZimError.Unexpected; + } + std.debug.print("Retrieved remote zig versions:\n", .{}); + for (remote_versions.items) |remote_version| { + remote_version.fmtPrint(); + } + return; + }, + } +}