Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions examples/colors.zig
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
15 changes: 7 additions & 8 deletions examples/overview.zig
Original file line number Diff line number Diff line change
@@ -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 },
Expand Down
14 changes: 7 additions & 7 deletions examples/trailing.zig
Original file line number Diff line number Diff line change
@@ -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 },
Expand Down
2 changes: 1 addition & 1 deletion src/ColorScheme.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
13 changes: 7 additions & 6 deletions src/Help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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);
}

Expand Down Expand Up @@ -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| {
Expand Down
56 changes: 30 additions & 26 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
Expand All @@ -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..];
Expand All @@ -79,31 +79,32 @@ pub fn parse(parser: *Parser, Flags: type, comptime command_name: []const u8) Fl
// Removing this check would allow formats like:
// `$ <cmd> -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 },
);
@field(passed, flag.field_name) = true;
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;
Expand All @@ -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});
},
};
};
Expand All @@ -126,21 +127,22 @@ 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;
}

fn parsePositional(
parser: *Parser,
io: std.Io,
arg: [:0]const u8,
index: usize,
comptime positionals: []const meta.Positional,
Expand All @@ -152,42 +154,44 @@ 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;
},

else => unreachable,
}
}

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},
),
Expand All @@ -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});
},
},

Expand All @@ -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)}),
Expand Down
25 changes: 15 additions & 10 deletions src/Terminal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
}

Expand All @@ -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 {};
}
}
3 changes: 2 additions & 1 deletion src/flags.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,5 +25,5 @@ pub fn parse(
.colors = options.colors,
};

return parser.parse(Flags, exe_name);
return parser.parse(io, Flags, exe_name);
}