First commit, things mostly work locally, on linux x86
This commit is contained in:
		
						commit
						86bce941de
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
zig-cache/
 | 
			
		||||
zig-out/
 | 
			
		||||
							
								
								
									
										70
									
								
								build.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								build.zig
									
									
									
									
									
										Normal file
									
								
							@ -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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										237
									
								
								src/main.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								src/main.zig
									
									
									
									
									
										Normal file
									
								
							@ -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 <version>
 | 
			
		||||
                \\  Used to install zig versions, that will then be available to
 | 
			
		||||
                \\  the `zim use <version>` command.
 | 
			
		||||
                \\
 | 
			
		||||
                \\  To see all available versions to install, run `zim list global`
 | 
			
		||||
                \\
 | 
			
		||||
                \\
 | 
			
		||||
            , .{});
 | 
			
		||||
        },
 | 
			
		||||
        .list => {
 | 
			
		||||
            std.debug.print(
 | 
			
		||||
                \\zim list <local or global>
 | 
			
		||||
                \\  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 <version>
 | 
			
		||||
                \\  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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								src/utils.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/utils.zig
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
        \\
 | 
			
		||||
        \\
 | 
			
		||||
    , .{});
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								src/version.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/version.zig
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										348
									
								
								src/zim.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								src/zim.zig
									
									
									
									
									
										Normal file
									
								
							@ -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 "<ZimERR>";
 | 
			
		||||
                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;
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user