diff --git a/examples/colors.zig b/examples/colors.zig index 00239a3..a204345 100644 --- a/examples/colors.zig +++ b/examples/colors.zig @@ -1,14 +1,14 @@ 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 io = init.io; + const gpa = init.gpa; - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); + const args = try init.minimal.args.toSlice(gpa); + defer gpa.free(args); - _ = 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`. diff --git a/examples/overview.zig b/examples/overview.zig index 7966e1e..56cdfa9 100644 --- a/examples/overview.zig +++ b/examples/overview.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 io = init.io; + const gpa = init.gpa; + const args = try init.minimal.args.toSlice(gpa); + defer gpa.free(args); - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); - - const options = flags.parse(args, "overview", Flags, .{}); + const options = flags.parse(io, args, "overview", Flags, .{}); var buffer: [1024]u8 = undefined; - var file_writer = std.fs.File.stdout().writer(&buffer); + var file_writer = std.Io.File.stdout().writer(io, &buffer); try std.json.Stringify.value( options, .{ .whitespace = .indent_2 }, diff --git a/examples/trailing.zig b/examples/trailing.zig index 1262afb..dca57d5 100644 --- a/examples/trailing.zig +++ b/examples/trailing.zig @@ -1,16 +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 io = init.io; + const gpa = init.gpa; - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); + const args = try init.minimal.args.toSlice(gpa); + defer gpa.free(args); - 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_writer = std.Io.File.stdout().writer(io, &.{}); try std.json.Stringify.value( options, .{ .whitespace = .indent_2 }, 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; diff --git a/src/Help.zig b/src/Help.zig index 5ad904f..e457a55 100644 --- a/src/Help.zig +++ b/src/Help.zig @@ -3,7 +3,8 @@ const Help = @This(); const std = @import("std"); const meta = @import("meta.zig"); -const File = std.fs.File; +const Io = std.Io; +const File = Io.File; const ColorScheme = @import("ColorScheme.zig"); const Terminal = @import("Terminal.zig"); @@ -17,9 +18,9 @@ pub const Usage = struct { command: []const u8, body: []const u8, - pub fn render(usage: Usage, stdout: File, colors: *const ColorScheme) void { + pub fn render(usage: Usage, io: Io, stdout: File, colors: *const ColorScheme) void { var stdout_writer = stdout.writer(&.{}); - const term = Terminal.init(stdout, &stdout_writer.interface); + const term = Terminal.init(io, stdout, &stdout_writer.interface); usage.renderToTerminal(term, colors); } @@ -100,9 +101,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.writer(io, &.{}); + const term = Terminal.init(io, stdout, &stdout_writer.interface); help.usage.renderToTerminal(term, colors); if (help.description) |description| { diff --git a/src/Parser.zig b/src/Parser.zig index e405d4b..130ba35 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 { + const stderr_file = std.Io.File.stderr(); + var stderr_file_writer = stderr_file.writer(io, &.{}); + const stderr = Terminal.init(io, stderr_file, &stderr_file_writer.interface); 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 79dacd5..20e7410 100644 --- a/src/Terminal.zig +++ b/src/Terminal.zig @@ -3,16 +3,19 @@ const Terminal = @This(); const std = @import("std"); const ColorScheme = @import("ColorScheme.zig"); -const tty = std.Io.tty; -const File = std.fs.File; +const Io = std.Io; +const File = Io.File; -writer: *std.Io.Writer, -config: tty.Config, +term: Io.Terminal, -pub fn init(file: File, writer: *std.Io.Writer) Terminal { +pub fn init(io: Io, file: File, writer: *std.Io.Writer) Terminal { return .{ - .writer = writer, - .config = tty.detectConfig(file), + .term = .{ + .writer = writer, + + // WARNING unsure what to do with these bool flags + .mode = Io.Terminal.Mode.detect(io, file, false, false) catch .no_color, + }, }; } @@ -22,13 +25,15 @@ pub fn print( comptime format: []const u8, args: anytype, ) void { + const term = &terminal.term; + for (style) |color| { - terminal.config.setColor(terminal.writer, color) catch {}; + term.setColor(color) catch {}; } - terminal.writer.print(format, args) catch {}; + term.writer.print(format, args) catch {}; if (style.len > 0) { - terminal.config.setColor(terminal.writer, .reset) catch {}; + term.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); }