From 10e44b77c555e9c06fc467a65bca0271665d6f42 Mon Sep 17 00:00:00 2001 From: FObersteiner Date: Sat, 3 Jan 2026 17:11:59 +0100 Subject: [PATCH 1/5] std.fs is now integrated in std.Io --- src/Terminal.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.zig b/src/Terminal.zig index 79dacd5..2359259 100644 --- a/src/Terminal.zig +++ b/src/Terminal.zig @@ -4,7 +4,7 @@ const std = @import("std"); const ColorScheme = @import("ColorScheme.zig"); const tty = std.Io.tty; -const File = std.fs.File; +const File = std.Io.File; writer: *std.Io.Writer, config: tty.Config, From aee38cf4296e7f1a976d51dc42310cab62e3c59b Mon Sep 17 00:00:00 2001 From: FObersteiner Date: Sat, 3 Jan 2026 17:12:13 +0100 Subject: [PATCH 2/5] use debug print in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cf73a5..74bf45c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ pub fn main() !void { .{}, ); - try std.io.getStdOut().writer().print("Hello, {s}!\n", .{cli.username}); + try std.debug.print("Hello, {s}!\n", .{cli.username}); } const std = @import("std"); From be47e782a289e38acc773905fc3d6a489bc7f537 Mon Sep 17 00:00:00 2001 From: FObersteiner Date: Sat, 3 Jan 2026 19:28:19 +0100 Subject: [PATCH 3/5] tty is now Terminal --- src/ColorScheme.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ColorScheme.zig b/src/ColorScheme.zig index 94783f4..59755ce 100644 --- a/src/ColorScheme.zig +++ b/src/ColorScheme.zig @@ -2,7 +2,7 @@ const ColorScheme = @This(); const std = @import("std"); -const Color = std.Io.tty.Color; +const Color = std.Io.Terminal.Color; pub const Style = []const Color; From 230ac24fe88afd681774bd0dcffbf9df157f85d9 Mon Sep 17 00:00:00 2001 From: FObersteiner Date: Tue, 27 Jan 2026 14:09:50 +0100 Subject: [PATCH 4/5] implement current std.Io --- examples/colors.zig | 13 +++++----- examples/overview.zig | 18 +++++++------- examples/trailing.zig | 15 ++++++------ src/Help.zig | 14 +++++------ src/Parser.zig | 56 +++++++++++++++++++++++-------------------- src/Terminal.zig | 13 +++++----- src/flags.zig | 3 ++- 7 files changed, 67 insertions(+), 65 deletions(-) diff --git a/examples/colors.zig b/examples/colors.zig index 00239a3..46efb30 100644 --- a/examples/colors.zig +++ b/examples/colors.zig @@ -1,17 +1,16 @@ const std = @import("std"); const flags = @import("flags"); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}).init; - defer _ = gpa.deinit(); +pub fn main(init: std.process.Init) !void { + const allocator = init.arena.allocator(); + const io = init.io; - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); + const args = try init.minimal.args.toSlice(allocator); - _ = flags.parse(args, "colors", Flags, .{ + _ = flags.parse(io, args, "colors", Flags, .{ // Use the `colors` option to provide a colorscheme for the error/help messages. // Specifying this as empty: `.colors = &.{}` will disable colors. - // Each field is a list of type `std.io.tty.Color`. + // Each field is a list of type `std.Io.Terminal.Color`. .colors = &flags.ColorScheme{ .error_label = &.{ .bright_red, .bold }, .command_name = &.{.bright_green}, diff --git a/examples/overview.zig b/examples/overview.zig index 7966e1e..7d25715 100644 --- a/examples/overview.zig +++ b/examples/overview.zig @@ -1,23 +1,21 @@ const std = @import("std"); const flags = @import("flags"); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}).init; - defer _ = gpa.deinit(); +pub fn main(init: std.process.Init) !void { + const allocator = init.arena.allocator(); + const io = init.io; - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); + const args = try init.minimal.args.toSlice(allocator); - const options = flags.parse(args, "overview", Flags, .{}); + const options = flags.parse(io, args, "overview", Flags, .{}); + + var stdout = std.Io.File.stdout().writerStreaming(io, &.{}); - var buffer: [1024]u8 = undefined; - var file_writer = std.fs.File.stdout().writer(&buffer); try std.json.Stringify.value( options, .{ .whitespace = .indent_2 }, - &file_writer.interface, + &stdout.interface, ); - try file_writer.interface.flush(); } const Flags = struct { diff --git a/examples/trailing.zig b/examples/trailing.zig index 1262afb..0e745a3 100644 --- a/examples/trailing.zig +++ b/examples/trailing.zig @@ -1,20 +1,19 @@ const std = @import("std"); const flags = @import("flags"); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}).init; - defer _ = gpa.deinit(); +pub fn main(init: std.process.Init) !void { + const allocator = init.arena.allocator(); + const io = init.io; - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); + const args = try init.minimal.args.toSlice(allocator); - const options = flags.parse(args, "trailing", Flags, .{}); + const options = flags.parse(io, args, "trailing", Flags, .{}); - var stdout_writer = std.fs.File.stdout().writer(&.{}); + var stdout = std.Io.File.stdout().writerStreaming(io, &.{}); try std.json.Stringify.value( options, .{ .whitespace = .indent_2 }, - &stdout_writer.interface, + &stdout.interface, ); } diff --git a/src/Help.zig b/src/Help.zig index 5ad904f..99dd176 100644 --- a/src/Help.zig +++ b/src/Help.zig @@ -3,7 +3,7 @@ const Help = @This(); const std = @import("std"); const meta = @import("meta.zig"); -const File = std.fs.File; +const File = std.Io.File; const ColorScheme = @import("ColorScheme.zig"); const Terminal = @import("Terminal.zig"); @@ -17,9 +17,9 @@ pub const Usage = struct { command: []const u8, body: []const u8, - pub fn render(usage: Usage, stdout: File, colors: *const ColorScheme) void { - var stdout_writer = stdout.writer(&.{}); - const term = Terminal.init(stdout, &stdout_writer.interface); + pub fn render(io: std.Io, usage: Usage, stdout: File, colors: *const ColorScheme) void { + var stdout_writer = stdout.writerStreaming(io, &.{}); + const term = Terminal.init(io, stdout, &stdout_writer.interface) catch unreachable; usage.renderToTerminal(term, colors); } @@ -100,9 +100,9 @@ const Section = struct { } }; -pub fn render(help: *const Help, stdout: File, colors: *const ColorScheme) void { - var stdout_writer = stdout.writer(&.{}); - const term = Terminal.init(stdout, &stdout_writer.interface); +pub fn render(help: *const Help, io: std.Io, stdout: File, colors: *const ColorScheme) void { + var stdout_writer = stdout.writerStreaming(io, &.{}); + const term = Terminal.init(io, stdout, &stdout_writer.interface) catch unreachable; help.usage.renderToTerminal(term, colors); if (help.description) |description| { diff --git a/src/Parser.zig b/src/Parser.zig index e405d4b..bfb7f6e 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -14,16 +14,16 @@ args: []const [:0]const u8, current_arg: usize, colors: *const ColorScheme, -fn fatal(parser: *const Parser, comptime fmt: []const u8, args: anytype) noreturn { - const stderr_file = std.fs.File.stderr(); - var stderr_file_writer = stderr_file.writer(&.{}); - const stderr = Terminal.init(stderr_file, &stderr_file_writer.interface); +fn fatal(parser: *const Parser, io: std.Io, comptime fmt: []const u8, args: anytype) noreturn { + var stderr_file = std.Io.File.stderr(); + var stderr_writer = stderr_file.writerStreaming(io, &.{}); + const stderr = Terminal.init(io, stderr_file, &stderr_writer.interface) catch unreachable; stderr.print(parser.colors.error_label, "Error: ", .{}); stderr.print(parser.colors.error_message, fmt ++ "\n", args); std.process.exit(1); } -pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Flags { +pub fn parse(parser: *Parser, io: std.Io, Flags: type, comptime command_name: []const u8) Flags { const info = comptime meta.info(Flags); const help = comptime Help.generate(Flags, info, command_name); @@ -39,18 +39,18 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl next_arg: while (parser.nextArg()) |arg| { if (arg.len == 0) { - parser.fatal("empty argument", .{}); + parser.fatal(io, "empty argument", .{}); } if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { - help.render(std.fs.File.stdout(), parser.colors); + help.render(io, std.Io.File.stdout(), parser.colors); std.process.exit(0); } if (std.mem.eql(u8, arg, "--")) { // Blindly treat remaining arguments as positional. while (parser.nextArg()) |positional| { - if (parser.parsePositional(positional, positional_index, info.positionals, &flags) == .consumed_all) { + if (parser.parsePositional(io, positional, positional_index, info.positionals, &flags) == .consumed_all) { break :next_arg; } positional_index += 1; @@ -59,17 +59,17 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl if (std.mem.startsWith(u8, arg, "--")) { inline for (info.flags) |flag| if (std.mem.eql(u8, arg, flag.flag_name)) { - @field(flags, flag.field_name) = parser.parseOption(flag.type, flag.flag_name); + @field(flags, flag.field_name) = parser.parseOption(io, flag.type, flag.flag_name); @field(passed, flag.field_name) = true; continue :next_arg; }; - parser.fatal("unrecognized flag: {s}", .{arg}); + parser.fatal(io, "unrecognized flag: {s}", .{arg}); } if (std.mem.startsWith(u8, arg, "-")) { if (arg.len == 1) { - parser.fatal("unrecognized argument: '-'", .{}); + parser.fatal(io, "unrecognized argument: '-'", .{}); } const switch_set = arg[1..]; @@ -79,9 +79,10 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl // Removing this check would allow formats like: // `$ -abc value-for-a value-for-b value-for-c` if (flag.type != bool and i < switch_set.len - 1) { - parser.fatal("missing value after switch: {c}", .{switch_char}); + parser.fatal(io, "missing value after switch: {c}", .{switch_char}); } @field(flags, flag.field_name) = parser.parseOption( + io, flag.type, &.{ '-', switch_char }, ); @@ -89,21 +90,21 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl continue :next_switch; } }; - parser.fatal("unrecognized switch: {c}", .{ch}); + parser.fatal(io, "unrecognized switch: {c}", .{ch}); } continue :next_arg; } inline for (info.subcommands) |cmd| { if (std.mem.eql(u8, arg, cmd.command_name)) { - const cmd_flags = parser.parse(cmd.type, command_name ++ " " ++ cmd.command_name); + const cmd_flags = parser.parse(io, cmd.type, command_name ++ " " ++ cmd.command_name); flags.command = @unionInit(@TypeOf(flags.command), cmd.field_name, cmd_flags); passed.command = true; continue :next_arg; } } - if (parser.parsePositional(arg, positional_index, info.positionals, &flags) == .consumed_all) { + if (parser.parsePositional(io, arg, positional_index, info.positionals, &flags) == .consumed_all) { break :next_arg; } positional_index += 1; @@ -115,7 +116,7 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl .bool => false, .optional => null, else => { - parser.fatal("missing required flag: {s}", .{flag.flag_name}); + parser.fatal(io, "missing required flag: {s}", .{flag.flag_name}); }, }; }; @@ -126,14 +127,14 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl switch (@typeInfo(pos.type)) { .optional => null, else => { - parser.fatal("missing required argument: {s}", .{pos.arg_name}); + parser.fatal(io, "missing required argument: {s}", .{pos.arg_name}); }, }; } } if (info.subcommands.len > 0 and !passed.command) { - parser.fatal("missing subcommand", .{}); + parser.fatal(io, "missing subcommand", .{}); } return flags; @@ -141,6 +142,7 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl fn parsePositional( parser: *Parser, + io: std.Io, arg: [:0]const u8, index: usize, comptime positionals: []const meta.Positional, @@ -152,14 +154,14 @@ fn parsePositional( parser.current_arg = parser.args.len; return .consumed_all; } - parser.fatal("unexpected argument: {s}", .{arg}); + parser.fatal(io, "unexpected argument: {s}", .{arg}); } switch (index) { inline 0...positionals.len - 1 => |i| { const positional = positionals[i]; const T = meta.unwrapOptional(positional.type); - @field(flags.positional, positional.field_name) = parser.parseValue(T, arg); + @field(flags.positional, positional.field_name) = parser.parseValue(io, T, arg); return .consumed_one; }, @@ -167,27 +169,29 @@ fn parsePositional( } } -fn parseOption(parser: *Parser, T: type, option_name: []const u8) T { +fn parseOption(parser: *Parser, io: std.Io, T: type, option_name: []const u8) T { if (T == bool) return true; const value = parser.nextArg() orelse { - parser.fatal("missing value for '{s}'", .{option_name}); + parser.fatal(io, "missing value for '{s}'", .{option_name}); }; - return parser.parseValue(meta.unwrapOptional(T), value); + return parser.parseValue(io, meta.unwrapOptional(T), value); } -fn parseValue(parser: *const Parser, T: type, arg: [:0]const u8) T { +fn parseValue(parser: *const Parser, io: std.Io, T: type, arg: [:0]const u8) T { if (T == []const u8 or T == [:0]const u8) return arg; switch (@typeInfo(T)) { .int => |info| return std.fmt.parseInt(T, arg, 10) catch |err| { switch (err) { error.Overflow => parser.fatal( + io, "value out of bounds for {d}-bit {s} integer: {s}", .{ info.bits, @tagName(info.signedness), arg }, ), error.InvalidCharacter => parser.fatal( + io, "expected integer number, found '{s}'", .{arg}, ), @@ -196,7 +200,7 @@ fn parseValue(parser: *const Parser, T: type, arg: [:0]const u8) T { .float => return std.fmt.parseFloat(T, arg) catch |err| switch (err) { error.InvalidCharacter => { - parser.fatal("expected numerical value, found '{s}'", .{arg}); + parser.fatal(io, "expected numerical value, found '{s}'", .{arg}); }, }, @@ -207,7 +211,7 @@ fn parseValue(parser: *const Parser, T: type, arg: [:0]const u8) T { } } - parser.fatal("unrecognized option: '{s}'", .{arg}); + parser.fatal(io, "unrecognized option: '{s}'", .{arg}); }, else => comptime meta.compileError("invalid flag type: {s}", .{@typeName(T)}), diff --git a/src/Terminal.zig b/src/Terminal.zig index 2359259..875ef6a 100644 --- a/src/Terminal.zig +++ b/src/Terminal.zig @@ -3,16 +3,17 @@ const Terminal = @This(); const std = @import("std"); const ColorScheme = @import("ColorScheme.zig"); -const tty = std.Io.tty; +const tty = std.Io.Terminal; const File = std.Io.File; writer: *std.Io.Writer, -config: tty.Config, +t: tty, -pub fn init(file: File, writer: *std.Io.Writer) Terminal { +pub fn init(io: std.Io, file: File, writer: *std.Io.Writer) !Terminal { + const terminal_mode: std.Io.Terminal.Mode = try .detect(io, file, false, false); return .{ .writer = writer, - .config = tty.detectConfig(file), + .t = .{ .writer = writer, .mode = terminal_mode }, }; } @@ -23,12 +24,12 @@ pub fn print( args: anytype, ) void { for (style) |color| { - terminal.config.setColor(terminal.writer, color) catch {}; + terminal.t.setColor(color) catch {}; } terminal.writer.print(format, args) catch {}; if (style.len > 0) { - terminal.config.setColor(terminal.writer, .reset) catch {}; + terminal.t.setColor(.reset) catch {}; } } diff --git a/src/flags.zig b/src/flags.zig index eeb74c9..64079fd 100644 --- a/src/flags.zig +++ b/src/flags.zig @@ -12,6 +12,7 @@ pub const Options = struct { }; pub fn parse( + io: std.Io, args: []const [:0]const u8, /// The name of your program. comptime exe_name: []const u8, @@ -24,5 +25,5 @@ pub fn parse( .colors = options.colors, }; - return parser.parse(Flags, exe_name); + return parser.parse(io, Flags, exe_name); } From 6cb757b26e4a5c6418ec7c83e445ecd78b3cd3ca Mon Sep 17 00:00:00 2001 From: FObersteiner Date: Tue, 27 Jan 2026 14:13:59 +0100 Subject: [PATCH 5/5] simplify terminal wrapper --- src/Terminal.zig | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Terminal.zig b/src/Terminal.zig index 875ef6a..5cebdee 100644 --- a/src/Terminal.zig +++ b/src/Terminal.zig @@ -1,3 +1,6 @@ +//! wrapper around std.Io.Terminal that gives us a convenient +//! init and print method. + const Terminal = @This(); const std = @import("std"); @@ -6,14 +9,12 @@ const ColorScheme = @import("ColorScheme.zig"); const tty = std.Io.Terminal; const File = std.Io.File; -writer: *std.Io.Writer, -t: tty, +instance: tty, pub fn init(io: std.Io, file: File, writer: *std.Io.Writer) !Terminal { const terminal_mode: std.Io.Terminal.Mode = try .detect(io, file, false, false); return .{ - .writer = writer, - .t = .{ .writer = writer, .mode = terminal_mode }, + .instance = .{ .writer = writer, .mode = terminal_mode }, }; } @@ -24,12 +25,12 @@ pub fn print( args: anytype, ) void { for (style) |color| { - terminal.t.setColor(color) catch {}; + terminal.instance.setColor(color) catch {}; } - terminal.writer.print(format, args) catch {}; + terminal.instance.writer.print(format, args) catch {}; if (style.len > 0) { - terminal.t.setColor(.reset) catch {}; + terminal.instance.setColor(.reset) catch {}; } }