diff --git a/src/cli.zig b/src/cli.zig index ced22ab..62718b9 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -121,8 +121,8 @@ const command_aliases = std.StaticStringMap(Command).initComptime(.{ .{ "-v", .version }, }); -pub fn parse(allocator: std.mem.Allocator, args_data: std.process.Args) !struct { global: GlobalFlags, cmd: ParsedCommand } { - var args = std.process.Args.Iterator.initAllocator(args_data, allocator) catch return error.OutOfMemory; +pub fn parse(allocator: std.mem.Allocator, init: std.process.Init.Minimal) !struct { global: GlobalFlags, cmd: ParsedCommand } { + var args = std.process.Args.Iterator.initAllocator(init.args, allocator) catch return error.OutOfMemory; defer args.deinit(); // Skip program name @@ -130,6 +130,13 @@ pub fn parse(allocator: std.mem.Allocator, args_data: std.process.Args) !struct var global_flags: GlobalFlags = .{}; + // Check environment variables for color settings + if (init.environ.containsConstant("NO_COLOR")) { + global_flags.color = false; + } else if (init.environ.containsConstant("CLICOLOR_FORCE")) { + global_flags.color = true; + } + // Parse global flags first var maybe_cmd: ?Command = null; var cmd_raw: ?[]const u8 = null; @@ -137,9 +144,8 @@ pub fn parse(allocator: std.mem.Allocator, args_data: std.process.Args) !struct if (std.mem.eql(u8, arg, "--color")) { const val = args.next() orelse return error.MissingArgument; global_flags.color = parseColorValue(val); - } else if (std.mem.startsWith(u8, arg, "--color=")) { - const val = arg["--color=".len..]; - global_flags.color = parseColorValue(val); + } else if (std.mem.cutPrefix(u8, arg, "--color=")) |val| { + if (parseColorValue(val)) |c| global_flags.color = c; } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { return .{ .global = global_flags, .cmd = .help }; } else if (std.mem.eql(u8, arg, "--version") or std.mem.eql(u8, arg, "-v")) { diff --git a/src/command/clean.zig b/src/command/clean.zig index 4040f69..7bc41b2 100644 --- a/src/command/clean.zig +++ b/src/command/clean.zig @@ -3,17 +3,16 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); +const Console = @import("../core/Console.zig"); /// Remove archive files (.zip, .xz, .tar, .tar.xz) from the cache directory. /// These are leftover files from downloads that are no longer needed after extraction. pub fn run( zvm: *zvm_mod.ZVM, allocator: std.mem.Allocator, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { _ = allocator; - _ = stderr; const extensions = [_][]const u8{ ".zip", ".xz", ".tar", ".tar.xz" }; @@ -33,6 +32,5 @@ pub fn run( } } - try stdout.print("Cleaned {d} artifact(s)\n", .{count}); - try stdout.flush(); + console.plain("Cleaned {d} artifact(s)", .{count}); } diff --git a/src/command/install.zig b/src/command/install.zig index f4e2434..08c2649 100644 --- a/src/command/install.zig +++ b/src/command/install.zig @@ -7,7 +7,7 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); const cli = @import("../cli.zig"); const platform = @import("../core/platform.zig"); -const terminal = @import("../core/terminal.zig"); +const Console = @import("../core/Console.zig"); const version_map = @import("../network/version_map.zig"); const http_client = @import("../network/http_client.zig"); const archive = @import("archive.zig"); @@ -21,8 +21,7 @@ pub fn run( allocator: std.mem.Allocator, version: []const u8, flags: cli.InstallFlags, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { // Get system info for platform-specific download const sys_info = platform.zigStyleSystemInfo(); @@ -30,11 +29,10 @@ pub fn run( const target = platform.platformTarget(&platform_buf, sys_info); // Fetch version map from configured URL - try stdout.print("Fetching version map...\n", .{}); - try stdout.flush(); + console.plain("Fetching version map...", .{}); const parsed_map = version_map.fetchVersionMap(allocator, zvm.io, zvm.environ_map, zvm.settings.version_map_url, zvm.settings.proxy) catch |err| { - try terminal.printError(stderr, "Failed to fetch version map"); + console.err("Failed to fetch version map", .{}); return err; }; defer parsed_map.deinit(); @@ -54,7 +52,7 @@ pub fn run( .argv = &.{ zig_path, "version" }, .stdout_limit = .limited(1024), }) catch { - try installVersion(zvm, allocator, version, target, vmap, flags, stdout, stderr); + try installVersion(zvm, allocator, version, target, vmap, flags, console); return; }; defer allocator.free(result.stdout); @@ -62,19 +60,17 @@ pub fn run( const installed_ver = std.mem.trim(u8, result.stdout, " \n\r"); if (std.mem.eql(u8, installed_ver, remote_ver)) { - try stdout.print("Master is already up to date ({s}). Use --force to reinstall.\n", .{installed_ver}); - try stdout.flush(); + console.plain("Master is already up to date ({s}). Use --force to reinstall.", .{installed_ver}); return; } } } else { - try stdout.print("Zig {s} is already installed. Use --force to reinstall.\n", .{version}); - try stdout.flush(); + console.plain("Zig {s} is already installed. Use --force to reinstall.", .{version}); return; } } - try installVersion(zvm, allocator, version, target, vmap, flags, stdout, stderr); + try installVersion(zvm, allocator, version, target, vmap, flags, console); } /// Core installation logic: download, verify, extract, rename, symlink. @@ -85,19 +81,17 @@ fn installVersion( target: []const u8, vmap: *const version_map.VersionMap, flags: cli.InstallFlags, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { // Resolve the tarball download URL from the version map const tar_url = version_map.getTarPath(version, target, vmap) catch { - try terminal.printError(stderr, "Failed to find download for this version/platform"); + console.err("Failed to find download for this version/platform", .{}); return error.UnsupportedVersion; }; // Get expected SHA256 checksum (optional — warns if missing) const shasum = version_map.getVersionShasum(version, target, vmap) catch blk: { - try stdout.print("Warning: No shasum found, skipping verification.\n", .{}); - try stdout.flush(); + console.plain("Warning: No shasum found, skipping verification.", .{}); break :blk null; }; @@ -108,8 +102,9 @@ fn installVersion( const archive_path = try std.fmt.bufPrint(&archive_path_buf, "{s}/{s}", .{ zvm.cache_dir, archive_name }); // Download archive (with optional mirror support) - try stdout.print("Downloading Zig {s}...\n", .{version}); - try stdout.flush(); + console.plain("Downloading Zig {s}...", .{version}); + + const stdout = console.stdout.writer; const actual_url = if (flags.nomirror) blk: { try http_client.downloadToFileWithProxy(allocator, zvm.io, zvm.environ_map, tar_url, archive_path, zvm.settings.proxy, stdout); @@ -124,40 +119,37 @@ fn installVersion( // Show the actual download source if (actual_url.ptr != tar_url.ptr) { - try stdout.print(" from: {s}\n", .{actual_url}); - try stdout.flush(); + console.plain(" from: {s}", .{actual_url}); allocator.free(actual_url); } // Verify SHA256 checksum if (shasum) |expected| { - try stdout.print("Verifying checksum...\n", .{}); - try stdout.flush(); + console.plain("Verifying checksum...", .{}); const matches = crypto.verifyFileSha256(zvm.io, archive_path, expected) catch { - try terminal.printError(stderr, "Failed to verify checksum"); + console.err("Failed to verify checksum", .{}); return error.ShasumMismatch; }; if (!matches) { - try terminal.printError(stderr, "SHA256 checksum mismatch!"); + console.err("SHA256 checksum mismatch!", .{}); std.Io.Dir.cwd().deleteFile(zvm.io, archive_path) catch {}; return error.ShasumMismatch; } - try terminal.printSuccess(stdout, "Checksum verified."); + console.success("Checksum verified.", .{}); } // Extract the archive - try stdout.print("Extracting...\n", .{}); - try stdout.flush(); + console.plain("Extracting...", .{}); archive.extractArchive(allocator, zvm.io, archive_path, zvm.data_dir) catch { - try terminal.printError(stderr, "Failed to extract archive"); + console.err("Failed to extract archive", .{}); return error.ExtractionFailed; }; // Rename the extracted directory (e.g., zig-macos-x86_64-0.13.0 → 0.13.0) - try renameExtractedDir(zvm, allocator, version, target, stderr); + try renameExtractedDir(zvm, allocator, version, target, console); // Only auto-activate if there is no currently active version const active_opt = zvm.getActiveVersion(allocator); @@ -170,19 +162,17 @@ fn installVersion( // Clean up the downloaded archive std.Io.Dir.cwd().deleteFile(zvm.io, archive_path) catch {}; - // Clean up any leftover extracted zig-* directories cleanupExtractedDirs(zvm); - try terminal.printSuccess(stdout, "Installed Zig"); + console.success("Installed Zig", .{}); // Verify the installed binary can actually compile (detect platform compatibility issues) - try stdout.print("Verifying installation...\n", .{}); - try stdout.flush(); + console.plain("Verifying installation...", .{}); if (!try verifyInstall(zvm, allocator, version)) { - try terminal.printWarning(stdout, "This Zig version has linking issues on your platform."); - try stdout.print( + console.warn("This Zig version has linking issues on your platform.", .{}); + console.plain( \\This is a known issue with official Zig releases on macOS 26+. \\The binary can run basic commands but cannot compile programs. \\ @@ -191,20 +181,18 @@ fn installVersion( \\ 2. Use Mach engine builds: zvm vmu zig mach && zvm install \\ , .{}); - try stdout.flush(); return; } if (has_active) { - try stdout.print("Use `zvm use {s}` to activate this version.\n", .{version}); + console.plain("Use `zvm use {s}` to activate this version.", .{version}); } else { - try stdout.print("Now using Zig {s}\n", .{version}); + console.plain("Now using Zig {s}", .{version}); } - try stdout.flush(); // Optionally install ZLS alongside Zig if (flags.zls) { - try installZls(zvm, allocator, version, flags.full, stdout, stderr); + try installZls(zvm, allocator, version, flags.full, console); } } @@ -217,7 +205,7 @@ fn renameExtractedDir( allocator: std.mem.Allocator, version: []const u8, target: []const u8, - stderr: *std.Io.Writer, + console: Console, ) !void { _ = allocator; @@ -254,7 +242,7 @@ fn renameExtractedDir( // Rename the extracted directory to the version name dir.rename(found.?, dir, version, zvm.io) catch { - try terminal.printError(stderr, "Failed to rename extracted directory"); + console.err("Failed to rename extracted directory", .{}); return; }; } @@ -330,11 +318,9 @@ fn installZls( allocator: std.mem.Allocator, version: []const u8, full_compat: bool, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { - try stdout.print("Installing ZLS for Zig {s}...\n", .{version}); - try stdout.flush(); + console.plain("Installing ZLS for Zig {s}...", .{version}); // Get the installed Zig version string var ver_buf: [std.fs.max_path_bytes]u8 = undefined; @@ -346,7 +332,7 @@ fn installZls( .argv = &.{ zig_path, "version" }, .stdout_limit = .limited(1024), }) catch { - try terminal.printError(stderr, "Failed to get Zig version for ZLS lookup"); + console.err("Failed to get Zig version for ZLS lookup", .{}); return; }; defer allocator.free(result.stdout); @@ -364,13 +350,13 @@ fn installZls( defer allocator.free(zls_url); const zls_response = http_client.downloadToMemoryWithProxy(allocator, zvm.io, zvm.environ_map, zls_url, zvm.settings.proxy) catch { - try terminal.printError(stderr, "Failed to query ZLS version"); + console.err("Failed to query ZLS version", .{}); return; }; defer allocator.free(zls_response); const parsed = std.json.parseFromSlice(std.json.Value, allocator, zls_response, .{}) catch { - try terminal.printError(stderr, "Failed to parse ZLS response"); + console.err("Failed to parse ZLS response", .{}); return; }; defer parsed.deinit(); @@ -378,7 +364,7 @@ fn installZls( const zls_data = switch (parsed.value) { .object => |obj| obj, else => { - try terminal.printError(stderr, "Invalid ZLS response"); + console.err("Invalid ZLS response", .{}); return; }, }; @@ -409,7 +395,7 @@ fn installZls( } const zls_tarball = tarball_url orelse { - try terminal.printError(stderr, "No ZLS build found for your platform"); + console.err("No ZLS build found for your platform", .{}); return; }; @@ -419,7 +405,7 @@ fn installZls( var archive_buf: [std.fs.max_path_bytes * 2]u8 = undefined; const zls_archive_path = try std.fmt.bufPrint(&archive_buf, "{s}/{s}", .{ zvm.cache_dir, zls_archive_name }); - try http_client.downloadToFileWithProxy(allocator, zvm.io, zvm.environ_map, zls_tarball, zls_archive_path, zvm.settings.proxy, stdout); + try http_client.downloadToFileWithProxy(allocator, zvm.io, zvm.environ_map, zls_tarball, zls_archive_path, zvm.settings.proxy, console.stdout.writer); // Extract to a temporary directory var temp_buf: [std.fs.max_path_bytes * 2]u8 = undefined; @@ -427,7 +413,7 @@ fn installZls( std.Io.Dir.cwd().createDirPath(zvm.io, temp_dir) catch {}; archive.extractArchive(allocator, zvm.io, zls_archive_path, temp_dir) catch { - try terminal.printError(stderr, "Failed to extract ZLS archive"); + console.err("Failed to extract ZLS archive", .{}); return; }; @@ -460,8 +446,7 @@ fn installZls( _ = std.process.run(allocator, zvm.io, .{ .argv = &.{ "chmod", "+x", dst }, }) catch {}; - try terminal.printSuccess(stdout, "Installed ZLS"); - try stdout.flush(); + console.success("Installed ZLS", .{}); } } } diff --git a/src/command/list.zig b/src/command/list.zig index 8191cf6..90ca4f3 100644 --- a/src/command/list.zig +++ b/src/command/list.zig @@ -6,7 +6,7 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); -const terminal = @import("../core/terminal.zig"); +const Console = @import("../core/Console.zig"); const version_map = @import("../network/version_map.zig"); pub const ListFlags = @import("../cli.zig").ListFlags; @@ -17,21 +17,25 @@ pub fn run( zvm: *zvm_mod.ZVM, allocator: std.mem.Allocator, flags: ListFlags, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { // Show configured version map URLs if (flags.vmu) { - try stdout.print("Zig Version Map: {s}\n", .{zvm.settings.version_map_url}); - try stdout.print("ZLS VMU: {s}\n", .{zvm.settings.zls_vmu}); - try stdout.print("Mirror List: {s}\n", .{zvm.settings.mirror_list_url}); - try stdout.flush(); + console.plain( + \\Zig Version Map: {s} + \\ZLS VMU: {s} + \\Mirror List: {s} + , .{ + zvm.settings.version_map_url, + zvm.settings.zls_vmu, + zvm.settings.mirror_list_url, + }); return; } // List remote versions from the version map if (flags.all) { - try listRemote(zvm, allocator, stdout, stderr); + try listRemote(zvm, allocator, console); return; } @@ -46,21 +50,23 @@ pub fn run( defer if (active) |a| allocator.free(a); if (versions.items.len == 0) { - try stdout.print("No Zig versions installed.\n", .{}); - try stdout.print("Use 'zvm install ' to install one.\n", .{}); - try stdout.flush(); + console.plain( + \\No Zig versions installed. + \\Use 'zvm install ' to install one. + , .{}); return; } for (versions.items) |ver| { const is_active = if (active) |a| std.mem.eql(u8, a, ver) else false; if (is_active) { - try terminal.println(stdout, .green, "{s} (active)", .{ver}); + console.colorize(.stdout, .green, "{s} (active)", .{ver}); } else { - try stdout.print("{s}\n", .{ver}); + console.print(.stdout, "{s}", .{ver}); } + console.newline(.stdout); } - try stdout.flush(); + console.flush(.stdout); } /// Fetch and display all remote versions from the version map. @@ -68,14 +74,12 @@ pub fn run( fn listRemote( zvm: *zvm_mod.ZVM, allocator: std.mem.Allocator, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { - try stdout.print("Fetching available versions...\n", .{}); - try stdout.flush(); + console.plain("Fetching available versions...", .{}); const parsed = version_map.fetchVersionMap(allocator, zvm.io, zvm.environ_map, zvm.settings.version_map_url, zvm.settings.proxy) catch { - try terminal.printError(stderr, "Failed to fetch version map"); + console.err("Failed to fetch version map", .{}); return; }; defer parsed.deinit(); @@ -100,8 +104,8 @@ fn listRemote( } // Print table header - try stdout.print("{s:<30} {s:<12}\n", .{ "Version", "Installed" }); - try stdout.print("{s:<30} {s:<12}\n", .{ "-------", "---------" }); + console.println(.stdout, "{s:<30} {s:<12}", .{ "Version", "Installed" }); + console.println(.stdout, "{s:<30} {s:<12}", .{ "-------", "---------" }); // Print master version first (with its dev build version number) if (vmap.get("master")) |master| { @@ -112,15 +116,15 @@ fn listRemote( const is_installed = isInstalled(installed.items, "master"); const is_active = if (active) |a| std.mem.eql(u8, a, "master") else false; const status = if (is_active) "active" else if (is_installed) "yes" else ""; - try stdout.print("master ({s})", .{master_ver}); + console.print(.stdout, "master ({s})", .{master_ver}); if (status.len > 0) { if (is_active) { - try terminal.println(stdout, .green, " {s}", .{status}); + console.colorize(.stdout, .green, " {s}", .{status}); } else { - try stdout.print(" {s}\n", .{status}); + console.println(.stdout, " {s}", .{status}); } } else { - try stdout.print("\n", .{}); + console.newline(.stdout); } } @@ -130,18 +134,17 @@ fn listRemote( const is_installed = isInstalled(installed.items, key); const is_active_flag = if (active) |a| std.mem.eql(u8, a, key) else false; const status = if (is_active_flag) "active" else if (is_installed) "yes" else ""; - try stdout.print("{s}", .{key}); + console.print(.stdout, "{s}", .{key}); if (status.len > 0) { if (is_active_flag) { - try terminal.println(stdout, .green, " {s}", .{status}); + console.colorize(.stdout, .green, " {s}", .{status}); } else { - try stdout.print(" {s}\n", .{status}); + console.print(.stdout, " {s}", .{status}); } - } else { - try stdout.print("\n", .{}); } + console.newline(.stdout); } - try stdout.flush(); + console.flush(.stdout); } /// Check if a version exists in the installed versions list. diff --git a/src/command/mirrorlist.zig b/src/command/mirrorlist.zig index 74291fe..8142ab7 100644 --- a/src/command/mirrorlist.zig +++ b/src/command/mirrorlist.zig @@ -4,6 +4,7 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); +const Console = @import("../core/Console.zig"); /// Set or display the mirror list URL. /// "default" resets to the official community mirrors. @@ -12,21 +13,17 @@ pub fn run( zvm: *zvm_mod.ZVM, allocator: std.mem.Allocator, url: ?[]const u8, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { - _ = stderr; - if (url) |u| { if (std.mem.eql(u8, u, "default")) { zvm.settings.resetMirrorList(allocator, zvm.io) catch {}; - try stdout.print("Reset mirror list to default.\n", .{}); + console.plain("Reset mirror list to default.", .{}); } else { try zvm.settings.setMirrorListUrl(allocator, zvm.io, u); - try stdout.print("Set mirror list to {s}\n", .{u}); + console.plain("Set mirror list to {s}", .{u}); } } else { - try stdout.print("Current mirror list: {s}\n", .{zvm.settings.mirror_list_url}); + console.plain("Current mirror list: {s}", .{zvm.settings.mirror_list_url}); } - try stdout.flush(); } diff --git a/src/command/proxy.zig b/src/command/proxy.zig index 400a1d5..74a81bd 100644 --- a/src/command/proxy.zig +++ b/src/command/proxy.zig @@ -5,7 +5,7 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); -const terminal = @import("../core/terminal.zig"); +const Console = @import("../core/Console.zig"); /// Supported proxy schemes. const valid_schemes = [_][]const u8{ "http://", "https://", "socks5://", "socks5h://", "socks4://", "socks4a://" }; @@ -34,35 +34,33 @@ pub fn run( zvm: *zvm_mod.ZVM, allocator: std.mem.Allocator, url: ?[]const u8, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { if (url) |u| { if (std.mem.eql(u8, u, "default")) { try zvm.settings.setProxy(allocator, zvm.io, ""); - try stdout.print("Reset proxy to auto-detect (from environment).\n", .{}); + console.plain("Reset proxy to auto-detect (from environment).", .{}); } else { validateProxyUrl(u) catch { - try terminal.printError(stderr, "Invalid proxy URL"); - try stderr.print( + console.colorize(.stderr, .red, "Invalid proxy URL", .{}); + console.newline(.stderr); + console.println(.stderr, \\Supported formats: \\ http://host:port \\ https://host:port \\ socks5://host:port - \\ , .{}); - try stderr.flush(); + console.flush(.stderr); return; }; try zvm.settings.setProxy(allocator, zvm.io, u); - try stdout.print("Set proxy to {s}\n", .{u}); + console.plain("Set proxy to {s}", .{u}); } } else { if (zvm.settings.proxy.len > 0) { - try stdout.print("Current proxy: {s}\n", .{zvm.settings.proxy}); + console.plain("Current proxy: {s}", .{zvm.settings.proxy}); } else { - try stdout.print("No proxy set (auto-detect from environment).\n", .{}); + console.plain("No proxy set (auto-detect from environment).", .{}); } } - try stdout.flush(); } diff --git a/src/command/run.zig b/src/command/run.zig index 0cd31c6..7372199 100644 --- a/src/command/run.zig +++ b/src/command/run.zig @@ -3,6 +3,7 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); +const Console = @import("../core/Console.zig"); /// Run a Zig command using a specific installed version. /// All arguments after the version are passed through to the zig binary. @@ -12,17 +13,10 @@ pub fn run( allocator: std.mem.Allocator, version: []const u8, args: []const []const u8, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { - _ = stdout; - _ = stderr; - if (!zvm.isVersionInstalled(version)) { - var buf: [4096]u8 = undefined; - var stderr_writer = std.Io.File.stderr().writer(zvm.io, &buf); - try stderr_writer.interface.print("Zig {s} is not installed. Run 'zvm install {s}' first.\n", .{ version, version }); - try stderr_writer.interface.flush(); + console.err("Zig {s} is not installed. Run 'zvm install {s}' first.", .{ version, version }); std.process.exit(1); } @@ -44,11 +38,7 @@ pub fn run( var child = std.process.spawn(zvm.io, .{ .argv = argv.items, }) catch { - var buf: [4096]u8 = undefined; - var stderr_writer = std.Io.File.stderr().writer(zvm.io, &buf); - try stderr_writer.interface.print("Failed to spawn zig {s}\n", .{version}); - try stderr_writer.interface.flush(); - std.process.exit(1); + console.fatal("Failed to spawn zig {s}", .{version}); }; const term = child.wait(zvm.io) catch { std.process.exit(1); @@ -58,7 +48,8 @@ pub fn run( switch (term) { .exited => |code| std.process.exit(code), .signal => { - std.debug.print("Process killed by signal\n", .{}); + console.println(.stderr, "Process killed by signal", .{}); + console.flush(.stderr); std.process.exit(1); }, else => std.process.exit(1), diff --git a/src/command/uninstall.zig b/src/command/uninstall.zig index 766eda6..24ad653 100644 --- a/src/command/uninstall.zig +++ b/src/command/uninstall.zig @@ -4,7 +4,7 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); -const terminal = @import("../core/terminal.zig"); +const Console = @import("../core/Console.zig"); /// Remove an installed Zig version. /// Checks if the version exists and blocks removal if it's currently active. @@ -12,16 +12,15 @@ pub fn run( zvm: *zvm_mod.ZVM, allocator: std.mem.Allocator, version: []const u8, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { var buf: [std.fs.max_path_bytes]u8 = undefined; const path = zvm.versionPath(&buf, version); // Verify the version is installed std.Io.Dir.cwd().access(zvm.io, path, .{}) catch { - try stderr.print("Zig {s} is not installed.\n", .{version}); - try stderr.flush(); + console.println(.stderr, "Zig {s} is not installed.", .{version}); + console.flush(.stderr); return; }; @@ -29,7 +28,7 @@ pub fn run( if (zvm.getActiveVersion(allocator)) |active| { defer allocator.free(active); if (std.mem.eql(u8, active, version)) { - try terminal.printError(stderr, "Cannot remove the active version."); + console.err("Cannot remove the active version.", .{}); // List other installed versions so user can pick one to switch to var versions = zvm.getInstalledVersions(allocator) catch return; @@ -48,28 +47,27 @@ pub fn run( } if (others.items.len > 0) { - try stderr.print("Switch to another version first:\n", .{}); - try stderr.flush(); + console.println(.stderr, "Switch to another version first:", .{}); + console.flush(.stderr); for (others.items) |ver| { - try stdout.print(" {s}\n", .{ver}); + console.println(.stdout, " {s}", .{ver}); } } else { - try stderr.print("No other versions available. Install one first:\n", .{}); - try stderr.print(" zvm install \n", .{}); + console.println(.stderr, "No other versions available. Install one first:", .{}); + console.println(.stderr, " zvm install ", .{}); } - try stderr.flush(); - try stdout.flush(); + console.flush(.stderr); + console.flush(.stdout); return; } } // Delete the version directory tree std.Io.Dir.cwd().deleteTree(zvm.io, path) catch |err| { - try stderr.print("Failed to remove {s}: {}\n", .{ version, err }); - try stderr.flush(); + console.println(.stderr, "Failed to remove {s}: {}", .{ version, err }); + console.flush(.stderr); return; }; - try stdout.print("Uninstalled Zig {s}\n", .{version}); - try stdout.flush(); + console.plain("Uninstalled Zig {s}", .{version}); } diff --git a/src/command/upgrade.zig b/src/command/upgrade.zig index f23676e..aaf9ebc 100644 --- a/src/command/upgrade.zig +++ b/src/command/upgrade.zig @@ -6,7 +6,7 @@ const std = @import("std"); const builtin = @import("builtin"); const build_options = @import("build_options"); const zvm_mod = @import("../core/zvm.zig"); -const terminal = @import("../core/terminal.zig"); +const Console = @import("../core/Console.zig"); const platform = @import("../core/platform.zig"); const http_client = @import("../network/http_client.zig"); const update_check = @import("../core/update_check.zig"); @@ -42,17 +42,15 @@ pub fn run( zvm: *zvm_mod.ZVM, allocator: std.mem.Allocator, current_version: []const u8, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { - try stdout.print("Current version: v{s} ({s})\n", .{ current_version, build_options.git_commit }); - try stdout.print("Checking for zvm updates...\n", .{}); - try stdout.flush(); + console.plain("Current version: v{s} ({s})", .{ current_version, build_options.git_commit }); + console.plain("Checking for zvm updates...", .{}); // Fetch latest release info from GitHub API (single request) const proxy = zvm.settings.proxy; const release_json = http_client.downloadToMemoryWithProxy(allocator, zvm.io, zvm.environ_map, "https://api.github.com/repos/lispking/zvm/releases/latest", proxy) catch { - try terminal.printError(stderr, "Failed to check for updates"); + console.err("Failed to check for updates", .{}); return; }; defer allocator.free(release_json); @@ -60,7 +58,7 @@ pub fn run( const parsed = std.json.parseFromSlice(std.json.Value, allocator, release_json, .{ .ignore_unknown_fields = true, }) catch { - try terminal.printError(stderr, "Failed to parse release info"); + console.err("Failed to parse release info", .{}); return; }; defer parsed.deinit(); @@ -69,19 +67,17 @@ pub fn run( // Extract version from release JSON (reuses shared logic) const latest_version = update_check.extractVersionFromRelease(release) orelse { - try terminal.printError(stderr, "Invalid release response"); + console.err("Invalid release response", .{}); return; }; - try stdout.print("Latest version: {s}\n", .{latest_version}); - try stdout.flush(); + console.plain("Latest version: {s}", .{latest_version}); // Compare versions and skip download if already up-to-date const current_stripped = update_check.stripVPrefix(current_version); const latest_stripped = update_check.stripVPrefix(latest_version); if (!update_check.versionGt(latest_stripped, current_stripped)) { - try terminal.printSuccess(stdout, "Already up-to-date!"); - try stdout.flush(); + console.success("Already up-to-date!", .{}); return; } @@ -93,7 +89,7 @@ pub fn run( // Find the matching release asset for this platform const assets = switch (release) { .object => |obj| obj.get("assets") orelse { - try terminal.printError(stderr, "No assets found"); + console.err("No assets found", .{}); return; }, else => return, @@ -129,7 +125,7 @@ pub fn run( } const url = download_url orelse { - try terminal.printError(stderr, "No matching binary found for your platform"); + console.err("No matching binary found for your platform", .{}); return; }; @@ -141,23 +137,21 @@ pub fn run( var buf2: [std.fs.max_path_bytes * 2]u8 = undefined; const archive_path = try std.fmt.bufPrint(&buf2, "{s}/zvm-update.tar.gz", .{self_dir}); - try stdout.print("Downloading {s}...\n", .{latest_version}); - try stdout.flush(); + console.plain("Downloading {s}...", .{latest_version}); - http_client.downloadToFileWithProxy(allocator, zvm.io, zvm.environ_map, url, archive_path, proxy, stdout) catch { - try terminal.printError(stderr, "Failed to download update"); + http_client.downloadToFileWithProxy(allocator, zvm.io, zvm.environ_map, url, archive_path, proxy, console.stdout.writer) catch { + console.err("Failed to download update", .{}); std.Io.Dir.cwd().deleteFile(zvm.io, archive_path) catch {}; return; }; - // Extract the archive - try stdout.print("Installing update...\n", .{}); - try stdout.flush(); + console.plain("Installing update...", .{}); + // Extract the archive const result = std.process.run(allocator, zvm.io, .{ .argv = &.{ "tar", "-xf", archive_path, "-C", self_dir }, }) catch { - try terminal.printError(stderr, "Failed to extract update"); + console.err("Failed to extract update", .{}); return; }; defer allocator.free(result.stdout); @@ -179,17 +173,17 @@ pub fn run( } else { var exe_buf: [std.fs.max_path_bytes]u8 = undefined; const exe_path_len = std.process.executablePath(zvm.io, &exe_buf) catch { - try terminal.printError(stderr, "Could not determine running binary path"); + console.err("Could not determine running binary path", .{}); return; }; const exe_path = exe_buf[0..exe_path_len]; if (std.Io.Dir.path.dirname(exe_path)) |dir_path| { break :blk allocator.dupe(u8, dir_path) catch { - try terminal.printError(stderr, "Out of memory"); + console.err("Out of memory", .{}); return; }; } - try terminal.printError(stderr, "Could not determine install directory"); + console.err("Could not determine install directory", .{}); return; } }; @@ -198,7 +192,7 @@ pub fn run( // Find the extracted zvm binary (may be nested in a versioned subdirectory) var src_buf: [std.fs.max_path_bytes * 2]u8 = undefined; const src_path = findBinary(allocator, zvm.io, self_dir, exe_name, &src_buf) catch { - try terminal.printError(stderr, "Could not find extracted zvm binary"); + console.err("Could not find extracted zvm binary", .{}); return; }; @@ -215,7 +209,7 @@ pub fn run( // Stream-copy the new binary platform.copyFile(zvm.io, src_path, dst_path) catch { - try terminal.printError(stderr, "Failed to copy binary"); + console.err("Failed to copy binary", .{}); return; }; @@ -229,9 +223,8 @@ pub fn run( // Clean up old binary std.Io.Dir.cwd().deleteFile(zvm.io, old_path) catch {}; - try terminal.printSuccess(stdout, "Updated zvm to latest version!"); - try stdout.print("Now running zvm {s}\n", .{latest_version}); - try stdout.flush(); + console.success("Updated zvm to latest version!", .{}); + console.plain("Now running zvm {s}", .{latest_version}); // Show the active Zig version if (zvm.getActiveVersion(allocator)) |active| { @@ -250,8 +243,7 @@ pub fn run( if (ver_result.stdout.len > 0) { const ver = std.mem.trim(u8, ver_result.stdout, " \n\r"); - try stdout.print("Active Zig: {s} ({s})\n", .{ active, ver }); - try stdout.flush(); + console.plain("Active Zig: {s} ({s})", .{ active, ver }); } } diff --git a/src/command/use.zig b/src/command/use.zig index c5fe0fd..ba7d7bd 100644 --- a/src/command/use.zig +++ b/src/command/use.zig @@ -3,6 +3,7 @@ const std = @import("std"); const zvm_mod = @import("../core/zvm.zig"); +const Console = @import("../core/Console.zig"); /// Switch to an installed Zig version by updating the bin symlink. /// Prints an error if the requested version is not installed. @@ -11,20 +12,16 @@ pub fn run( allocator: std.mem.Allocator, version: []const u8, flags: anytype, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { _ = allocator; _ = flags; - _ = stderr; if (!zvm.isVersionInstalled(version)) { - try stdout.print("Zig {s} is not installed. Run 'zvm install {s}' first.\n", .{ version, version }); - try stdout.flush(); + console.plain("Zig {s} is not installed. Run 'zvm install {s}' first.", .{ version, version }); return; } try zvm.setBin(version); - try stdout.print("Now using Zig {s}\n", .{version}); - try stdout.flush(); + console.plain("Now using Zig {s}", .{version}); } diff --git a/src/completion.zig b/src/completion.zig index bcbd961..21df449 100644 --- a/src/completion.zig +++ b/src/completion.zig @@ -5,13 +5,15 @@ const std = @import("std"); const cli = @import("cli.zig"); +const Console = @import("core/Console.zig"); -pub fn run(shell: cli.ShellType, writer: *std.Io.Writer) !void { +pub fn run(shell: cli.ShellType, console: Console) !void { + const writer = console.stdout.writer; switch (shell) { .zsh => try writeZshCompletion(writer), .bash => try writeBashCompletion(writer), } - try writer.flush(); + console.flush(.stdout); } // ─── Comptime metadata ─────────────────────────────────── diff --git a/src/core/Console.zig b/src/core/Console.zig new file mode 100644 index 0000000..1c3c329 --- /dev/null +++ b/src/core/Console.zig @@ -0,0 +1,143 @@ +const std = @import("std"); + +const Console = @This(); + +stdout: std.Io.Terminal, +stderr: std.Io.Terminal, + +pub const Target = enum { + stdout, + stderr, +}; + +pub const Level = enum { + err, + warn, + info, + success, + + pub fn color(level: Level) ?std.Io.Terminal.Color { + return switch (level) { + .err => .red, + .warn => .yellow, + .info => .cyan, + .success => .green, + }; + } + + pub fn label(level: Level) ?[]const u8 { + return switch (level) { + .err => "error", + .warn => "warning", + .info, .success => null, + }; + } + + pub fn target(level: Level) Target { + return switch (level) { + .err, .warn => .stderr, + .info, .success => .stdout, + }; + } +}; + +pub fn init(stdout: *std.Io.Writer, stderr: *std.Io.Writer, mode: ?std.Io.Terminal.Mode) Console { + const m = mode orelse .no_color; + return .{ + .stdout = .{ .writer = stdout, .mode = m }, + .stderr = .{ .writer = stderr, .mode = m }, + }; +} + +// --- High-level APIs for formatted logging with color, labels, and automatic flushing --- + +pub fn log(self: Console, comptime level: Level, comptime fmt: []const u8, args: anytype) void { + const target = comptime level.target(); + if (comptime level.label()) |lbl| { + self.colorize(target, level.color(), lbl, .{}); + self.print(target, ": " ++ fmt, args); + } else { + self.colorize(target, level.color(), fmt, args); + } + self.newline(target); + self.flush(target); +} + +pub fn fatal(self: Console, comptime fmt: []const u8, args: anytype) noreturn { + self.log(.err, fmt, args); + std.process.exit(1); +} + +pub fn err(self: Console, comptime fmt: []const u8, args: anytype) void { + self.log(.err, fmt, args); +} + +pub fn warn(self: Console, comptime fmt: []const u8, args: anytype) void { + self.log(.warn, fmt, args); +} + +pub fn info(self: Console, comptime fmt: []const u8, args: anytype) void { + self.log(.info, fmt, args); +} + +pub fn success(self: Console, comptime fmt: []const u8, args: anytype) void { + self.log(.success, fmt, args); +} + +pub fn plain(self: Console, comptime fmt: []const u8, args: anytype) void { + self.println(.stdout, fmt, args); + self.flush(.stdout); +} + +// --- Low-level APIs for direct terminal access (no color, labels, or flushing) --- + +pub fn newline(self: Console, comptime target: Target) void { + const t = self.terminal(target); + t.writer.writeAll("\n") catch {}; +} + +pub fn write(self: Console, comptime target: Target, data: []const u8) void { + const t = self.terminal(target); + t.writer.writeAll(data) catch {}; +} + +pub fn writeln(self: Console, comptime target: Target, data: []const u8) void { + const t = self.terminal(target); + t.writer.writeAll(data) catch {}; + t.writer.writeAll("\n") catch {}; +} + +pub fn print(self: Console, comptime target: Target, comptime fmt: []const u8, args: anytype) void { + const t = self.terminal(target); + t.writer.print(fmt, args) catch {}; +} + +pub fn println(self: Console, comptime target: Target, comptime fmt: []const u8, args: anytype) void { + const t = self.terminal(target); + t.writer.print(fmt ++ "\n", args) catch {}; +} + +pub fn colorize( + self: Console, + comptime target: Target, + color: ?std.Io.Terminal.Color, + comptime fmt: []const u8, + args: anytype, +) void { + const t = self.terminal(target); + if (color) |c| { + t.setColor(c) catch {}; + t.writer.print(fmt, args) catch {}; + t.setColor(.reset) catch {}; + } else { + t.writer.print(fmt, args) catch {}; + } +} + +pub fn flush(self: Console, comptime target: Target) void { + self.terminal(target).writer.flush() catch {}; +} + +inline fn terminal(self: Console, comptime target: Target) std.Io.Terminal { + return if (target == .stdout) self.stdout else self.stderr; +} diff --git a/src/core/terminal.zig b/src/core/terminal.zig deleted file mode 100644 index cd761b0..0000000 --- a/src/core/terminal.zig +++ /dev/null @@ -1,65 +0,0 @@ -//! ANSI terminal color helpers for formatted console output. -//! Provides colorized printing functions (error, success, info, warning) -//! that wrap text with ANSI escape codes. - -const std = @import("std"); - -/// Supported ANSI color/style codes. -pub const AnsiColor = enum { - reset, - bold, - red, - green, - yellow, - blue, - magenta, - cyan, - white, - dim, -}; - -/// Returns the ANSI escape sequence for the given color. -fn ansiCode(color: AnsiColor) []const u8 { - return switch (color) { - .reset => "\x1b[0m", - .bold => "\x1b[1m", - .dim => "\x1b[2m", - .red => "\x1b[31m", - .green => "\x1b[32m", - .yellow => "\x1b[33m", - .blue => "\x1b[34m", - .magenta => "\x1b[35m", - .cyan => "\x1b[36m", - .white => "\x1b[37m", - }; -} - -/// Write text wrapped in the specified ANSI color, then reset. -pub fn colorize(writer: *std.Io.Writer, comptime color: AnsiColor, text: []const u8) !void { - try writer.print("{s}{s}{s}", .{ ansiCode(color), text, ansiCode(.reset) }); -} - -/// Print a formatted line in the specified color. -pub fn println(writer: *std.Io.Writer, comptime color: AnsiColor, comptime fmt: []const u8, args: anytype) !void { - try writer.print("{s}" ++ fmt ++ "{s}\n", .{ansiCode(color)} ++ args ++ .{ansiCode(.reset)}); -} - -/// Print an error message in red with "error:" prefix. -pub fn printError(writer: *std.Io.Writer, msg: []const u8) !void { - try writer.print("{s}error{s}: {s}\n", .{ ansiCode(.red), ansiCode(.reset), msg }); -} - -/// Print a success message in green. -pub fn printSuccess(writer: *std.Io.Writer, msg: []const u8) !void { - try writer.print("{s}{s}{s}\n", .{ ansiCode(.green), msg, ansiCode(.reset) }); -} - -/// Print an informational message in cyan. -pub fn printInfo(writer: *std.Io.Writer, msg: []const u8) !void { - try writer.print("{s}{s}{s}\n", .{ ansiCode(.cyan), msg, ansiCode(.reset) }); -} - -/// Print a warning message in yellow with "warning:" prefix. -pub fn printWarning(writer: *std.Io.Writer, msg: []const u8) !void { - try writer.print("{s}warning{s}: {s}\n", .{ ansiCode(.yellow), ansiCode(.reset), msg }); -} diff --git a/src/core/update_check.zig b/src/core/update_check.zig index 43c27ba..56dd7bb 100644 --- a/src/core/update_check.zig +++ b/src/core/update_check.zig @@ -7,7 +7,7 @@ const std = @import("std"); const build_options = @import("build_options"); const zvm_mod = @import("zvm.zig"); const http_client = @import("../network/http_client.zig"); -const terminal = @import("terminal.zig"); +const Console = @import("../core/Console.zig"); /// Cache file name for the update check result. const cache_filename = "_update_check"; @@ -184,12 +184,12 @@ pub fn printUpdateHint( environ_map: *std.process.Environ.Map, cache_dir: []const u8, proxy: []const u8, - stdout: *std.Io.Writer, + console: Console, ) void { const latest = checkForUpdate(allocator, io, environ_map, cache_dir, proxy) orelse return; defer allocator.free(latest); - terminal.println(stdout, .yellow, "A new version of zvm is available: {s} (current: v{s})", .{ latest, VERSION }) catch {}; - stdout.print("Run `zvm upgrade` to update.\n\n", .{}) catch {}; - stdout.flush() catch {}; + console.colorize(.stdout, .yellow, "A new version of zvm is available: {s} (current: v{s})", .{ latest, VERSION }); + console.print(.stdout, "Run `zvm upgrade` to update.\n\n", .{}); + console.flush(.stdout); } diff --git a/src/core/vmu.zig b/src/core/vmu.zig index d4ad5be..543ff13 100644 --- a/src/core/vmu.zig +++ b/src/core/vmu.zig @@ -5,6 +5,7 @@ const std = @import("std"); const zvm_mod = @import("zvm.zig"); const cli = @import("../cli.zig"); +const Console = @import("Console.zig"); /// Set the version map source for Zig or ZLS. /// Special values: "default" resets to official, "mach" (Zig only) uses Mach engine builds. @@ -14,33 +15,30 @@ pub fn run( allocator: std.mem.Allocator, target: cli.VmuTarget, value: []const u8, - stdout: *std.Io.Writer, - stderr: *std.Io.Writer, + console: Console, ) !void { - _ = stderr; - switch (target) { .zig => { if (std.mem.eql(u8, value, "default")) { zvm.settings.resetVersionMap(allocator, zvm.io) catch {}; - try stdout.print("Reset Zig version map to default.\n", .{}); + console.println(.stdout, "Reset Zig version map to default.", .{}); } else if (std.mem.eql(u8, value, "mach")) { try zvm.settings.setVersionMapUrl(allocator, zvm.io, "https://machengine.org/zig/index.json"); - try stdout.print("Set Zig version map to Mach engine.\n", .{}); + console.println(.stdout, "Set Zig version map to Mach engine.", .{}); } else { try zvm.settings.setVersionMapUrl(allocator, zvm.io, value); - try stdout.print("Set Zig version map to {s}\n", .{value}); + console.println(.stdout, "Set Zig version map to {s}", .{value}); } }, .zls => { if (std.mem.eql(u8, value, "default")) { zvm.settings.resetZlsVMU(allocator, zvm.io) catch {}; - try stdout.print("Reset ZLS VMU to default.\n", .{}); + console.println(.stdout, "Reset ZLS VMU to default.", .{}); } else { try zvm.settings.setZlsVMU(allocator, zvm.io, value); - try stdout.print("Set ZLS VMU to {s}\n", .{value}); + console.println(.stdout, "Set ZLS VMU to {s}", .{value}); } }, } - try stdout.flush(); + console.flush(.stdout); } diff --git a/src/main.zig b/src/main.zig index 382b61e..18e888f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,13 +4,15 @@ //! and provides shell completion support. const std = @import("std"); +const Io = std.Io; +const build_options = @import("build_options"); + const cli = @import("cli.zig"); -const zvm_mod = @import("core/zvm.zig"); -const terminal = @import("core/terminal.zig"); -const update_check = @import("core/update_check.zig"); +const Console = @import("core/Console.zig"); const errors = @import("core/errors.zig"); const platform = @import("core/platform.zig"); -const build_options = @import("build_options"); +const update_check = @import("core/update_check.zig"); +const zvm_mod = @import("core/zvm.zig"); /// Current version of zvm, injected at build time from git tag or -Dversion=. const VERSION = build_options.version; @@ -21,10 +23,8 @@ fn fullVersion() []const u8 { } /// Print error and exit on command failure. -fn commandFail(stderr: *std.Io.Writer, err: anyerror) noreturn { - terminal.printError(stderr, @errorName(err)) catch {}; - stderr.flush() catch {}; - std.process.exit(1); +fn commandFail(console: Console, err: anyerror) noreturn { + console.fatal("{s}", .{@errorName(err)}); } /// Application entry point. @@ -38,19 +38,29 @@ pub fn main(init: std.process.Init) !void { defer arena.deinit(); // Parse CLI arguments - const parsed = cli.parse(arena.allocator(), init.minimal.args) catch { - var stderr_buf: [4096]u8 = undefined; - var stderr_writer = std.Io.File.stderr().writer(init.io, &stderr_buf); - try terminal.printError(&stderr_writer.interface, "Failed to parse arguments"); - try cli.printHelp(&stderr_writer.interface); + const parsed = cli.parse(arena.allocator(), init.minimal) catch |err| { + std.log.err("Failed to parse arguments: {s}\n", .{@errorName(err)}); std.process.exit(1); }; + // Setup console with appropriate color mode based on terminal capabilities + const console: Console = blk: { + var stdout_buffer: [8192]u8 = undefined; + var stdout_file_writer: Io.File.Writer = .init(.stdout(), init.io, &stdout_buffer); + const stdout = &stdout_file_writer.interface; + + var stderr_buffer: [4096]u8 = undefined; + var stderr_file_writer: Io.File.Writer = .init(.stderr(), init.io, &stderr_buffer); + const stderr = &stderr_file_writer.interface; + + const no_color, const clicolor_force = if (parsed.global.color) |val| .{ !val, val } else .{ false, true }; + const mode = std.Io.Terminal.Mode.detect(init.io, .stderr(), no_color, clicolor_force) catch null; + break :blk .init(stdout, stderr, mode); + }; + // Initialize ZVM environment (XDG dirs, settings, etc.) - var zvm = zvm_mod.ZVM.init(allocator, init.io, init.environ_map) catch { - var stderr_buf: [4096]u8 = undefined; - var stderr_writer = std.Io.File.stderr().writer(init.io, &stderr_buf); - try terminal.printError(&stderr_writer.interface, "Failed to initialize ZVM"); + var zvm = zvm_mod.ZVM.init(allocator, init.io, init.environ_map) catch |err| { + console.err("Failed to initialize ZVM: {s}", .{@errorName(err)}); std.process.exit(1); }; defer zvm.deinit(); @@ -60,74 +70,63 @@ pub fn main(init: std.process.Init) !void { zvm.settings.use_color = color_val; } - // Setup buffered stdout/stderr writers - var stdout_buf: [8192]u8 = undefined; - var stdout_writer = std.Io.File.stdout().writer(init.io, &stdout_buf); - const stdout = &stdout_writer.interface; - - var stderr_buf: [4096]u8 = undefined; - var stderr_writer = std.Io.File.stderr().writer(init.io, &stderr_buf); - const stderr = &stderr_writer.interface; - // Dispatch to the appropriate command handler switch (parsed.cmd) { .help => { - try cli.printHelp(stdout); + try cli.printHelp(console.stdout.writer); }, .version => { - try stdout.print("zvm {s}\n", .{fullVersion()}); - try stdout.flush(); + console.plain("zvm {s}", .{fullVersion()}); }, .install => |inst| { const install = @import("command/install.zig"); - install.run(&zvm, allocator, inst.version, inst.flags, stdout, stderr) catch |err| commandFail(stderr, err); - update_check.printUpdateHint(allocator, zvm.io, zvm.environ_map, zvm.cache_dir, zvm.settings.proxy, stdout); + install.run(&zvm, allocator, inst.version, inst.flags, console) catch |err| commandFail(console, err); + update_check.printUpdateHint(allocator, zvm.io, zvm.environ_map, zvm.cache_dir, zvm.settings.proxy, console); }, .use => |use_cmd| { const use_mod = @import("command/use.zig"); const ver = use_cmd.version orelse { - try terminal.printError(stderr, "No version specified. Use 'zvm use '"); - try stderr.flush(); + console.err("No version specified. Use 'zvm use '", .{}); std.process.exit(1); }; - use_mod.run(&zvm, allocator, ver, use_cmd.flags, stdout, stderr) catch |err| commandFail(stderr, err); + use_mod.run(&zvm, allocator, ver, use_cmd.flags, console) catch |err| commandFail(console, err); }, .list => |list_cmd| { const list = @import("command/list.zig"); - list.run(&zvm, allocator, list_cmd.flags, stdout, stderr) catch |err| commandFail(stderr, err); + list.run(&zvm, allocator, list_cmd.flags, console) catch |err| commandFail(console, err); }, .uninstall => |uninst| { const uninstall = @import("command/uninstall.zig"); - uninstall.run(&zvm, allocator, uninst.version, stdout, stderr) catch |err| commandFail(stderr, err); + uninstall.run(&zvm, allocator, uninst.version, console) catch |err| commandFail(console, err); }, .clean => { const clean = @import("command/clean.zig"); - clean.run(&zvm, allocator, stdout, stderr) catch |err| commandFail(stderr, err); + clean.run(&zvm, allocator, console) catch |err| commandFail(console, err); }, .run => |run_cmd| { const run_mod = @import("command/run.zig"); - run_mod.run(&zvm, allocator, run_cmd.version, run_cmd.args, stdout, stderr) catch |err| commandFail(stderr, err); + run_mod.run(&zvm, allocator, run_cmd.version, run_cmd.args, console) catch |err| commandFail(console, err); }, .upgrade => { const upgrade = @import("command/upgrade.zig"); - upgrade.run(&zvm, allocator, VERSION, stdout, stderr) catch |err| commandFail(stderr, err); + upgrade.run(&zvm, allocator, VERSION, console) catch |err| commandFail(console, err); }, .vmu => |vmu_cmd| { const vmu = @import("core/vmu.zig"); - vmu.run(&zvm, allocator, vmu_cmd.target, vmu_cmd.value, stdout, stderr) catch |err| commandFail(stderr, err); + vmu.run(&zvm, allocator, vmu_cmd.target, vmu_cmd.value, console) catch |err| commandFail(console, err); }, .mirrorlist => |ml_cmd| { const mirrorlist = @import("command/mirrorlist.zig"); - mirrorlist.run(&zvm, allocator, ml_cmd.url, stdout, stderr) catch |err| commandFail(stderr, err); - update_check.printUpdateHint(allocator, zvm.io, zvm.environ_map, zvm.cache_dir, zvm.settings.proxy, stdout); + mirrorlist.run(&zvm, allocator, ml_cmd.url, console) catch |err| commandFail(console, err); + update_check.printUpdateHint(allocator, zvm.io, zvm.environ_map, zvm.cache_dir, zvm.settings.proxy, console); }, .proxy => |proxy_cmd| { const proxy_mod = @import("command/proxy.zig"); - proxy_mod.run(&zvm, allocator, proxy_cmd.url, stdout, stderr) catch |err| commandFail(stderr, err); + proxy_mod.run(&zvm, allocator, proxy_cmd.url, console) catch |err| commandFail(console, err); }, .completion => |comp_cmd| { const completion = @import("completion.zig"); - completion.run(comp_cmd.shell, stdout) catch |err| commandFail(stderr, err); + completion.run(comp_cmd.shell, console) catch |err| commandFail(console, err); }, } }