Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
n0s4 committed May 23, 2024
1 parent 5f2b0bf commit a7915f7
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 96 deletions.
2 changes: 1 addition & 1 deletion examples/git.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub fn main() void {
var args = std.process.args();
const command = flags.parse(&args, Git);

prettyPrint(command.config, command.args);
prettyPrint(command.flags, command.args);
}

/// A very stripped-down model of the git CLI.
Expand Down
31 changes: 15 additions & 16 deletions examples/overview.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@ const std = @import("std");
const flags = @import("flags");
const prettyPrint = @import("prettyprint.zig").prettyPrint;

const Config = struct {
// Optionally, you can specify the size of the buffer for positional arguments if you wish to
// impose a specific limit or you expect more than the default (32).
pub const max_positional_flags = 3;

pub fn main() !void {
var args = std.process.args();
const result = flags.parse(&args, Command);

prettyPrint(
result.flags, // result, has type of `Command`
result.args, // extra positional arguments
);
}

const Command = struct {
// This field is required for your top-level command, and is used in help messages.
pub const name = "example";

Expand Down Expand Up @@ -52,18 +66,3 @@ const Config = struct {
.size = "How big?",
};
};

// Optionally, you can specify the size of the buffer for positional flags if you wish to
// impose a specific limit or you expect more flags than the default (32).
pub const max_positional_flags = 3;

pub fn main() !void {
var args = std.process.args();

const result = flags.parse(&args, Config);

prettyPrint(
result.config, // result, of passed `Config` type
result.args, // extra positional flags
);
}
17 changes: 8 additions & 9 deletions examples/prettyprint.zig
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
const std = @import("std");
const flags = @import("flags");

fn print(comptime fmt: []const u8, args: anytype) void {
const stdout = std.io.getStdOut().writer();
stdout.print(fmt, args) catch {};
}

/// Utility for pretty printing a parse result.
pub fn prettyPrint(config: anytype, args: []const []const u8) void {
switch (@typeInfo(@TypeOf(config))) {
.Struct => printOptions(config),
.Union => printCommand(config),
pub fn prettyPrint(command: anytype, args: []const []const u8) void {
switch (@typeInfo(@TypeOf(command))) {
.Struct => printFlags(command),
.Union => printCommand(command),
else => comptime unreachable,
}

Expand All @@ -28,7 +27,7 @@ fn printCommand(command: anytype) void {
switch (@typeInfo(field.type)) {
.Struct => {
print("\n", .{});
printOptions(@field(command, field.name));
printFlags(@field(command, field.name));
},
.Union => printCommand(@field(command, field.name)),
else => unreachable,
Expand All @@ -37,10 +36,10 @@ fn printCommand(command: anytype) void {
}
}

fn printOptions(options: anytype) void {
inline for (std.meta.fields(@TypeOf(options))) |field| {
fn printFlags(flags: anytype) void {
inline for (std.meta.fields(@TypeOf(flags))) |field| {
print("{s}: ", .{field.name});
const value = @field(options, field.name);
const value = @field(flags, field.name);
switch (field.type) {
[]const u8 => print("{s}", .{value}),
?[]const u8 => print("{?s}", .{value}),
Expand Down
29 changes: 17 additions & 12 deletions examples/subcommands.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@ const std = @import("std");
const flags = @import("flags");
const prettyPrint = @import("prettyprint.zig").prettyPrint;

pub fn main() !void {
var args = std.process.args();
const result = flags.parse(&args, Command);

prettyPrint(result.flags, result.args);
}

const Command = union(enum) {
pub const name = "subcommands";

pub const descriptions = .{
.add = "Create a new item",
.remove = "Delete an item",
.change = "Edit or move an existing item",
};

add: struct {
name: []const u8,
pub const switches = .{
.name = "n",
.name = 'n',
};
},

Expand All @@ -17,16 +30,16 @@ const Command = union(enum) {
all: bool,

pub const switches = .{
.name = "n",
.all = "a",
.name = 'n',
.all = 'a',
};

pub const descriptions = .{
.all = "Remove all items",
};
},

nested: union(enum) {
change: union(enum) {
edit: struct {
title: ?[]const u8,
content: ?[]const u8,
Expand All @@ -48,11 +61,3 @@ const Command = union(enum) {
},
},
};

pub fn main() !void {
var args = std.process.args();

const result = flags.parse(&args, Command);

prettyPrint(result.config, result.args);
}
30 changes: 15 additions & 15 deletions src/help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ const format = @import("format.zig");
pub fn helpMessage(comptime Command: type, comptime name: []const u8) []const u8 {
if (std.meta.fields(Command).len == 0) return "Usage: " ++ name ++ "\n";
return switch (@typeInfo(Command)) {
.Struct => helpOptions(Command, name),
.Struct => helpFlags(Command, name),
.Union => helpCommands(Command, name),
else => comptime unreachable,
};
}

const indent = " ";

/// This is used during comptime to collect info about options/commands.
/// This is used during comptime to collect info about flags/commands.
/// The `name` and `description` are stored separately then the `render` method is used
/// to concatenate them with the correct spacing.
const Description = struct {
/// For an option, this is the flag name and the switch, if present.
/// For a flag, this is the flag name and the switch, if present.
/// For a command, this is the command name.
/// For an enum variant, this is the variant name.
/// This includes the preceding indentation.
Expand Down Expand Up @@ -68,7 +68,7 @@ fn helpCommands(comptime Commands: type, comptime command_name: []const u8) []co
return help;
}

fn helpOptions(comptime Options: type, comptime command_name: []const u8) []const u8 {
fn helpFlags(comptime Flags: type, comptime command_name: []const u8) []const u8 {
comptime var help: []const u8 = std.fmt.comptimePrint(
// TODO: More specific usage expression.
\\Usage: {s} [options]
Expand All @@ -80,10 +80,10 @@ fn helpOptions(comptime Options: type, comptime command_name: []const u8) []cons
comptime var descriptions: []const Description = &.{};
comptime var max_name_len = Description.help.name.len;

for (std.meta.fields(Options)) |field| {
for (std.meta.fields(Flags)) |field| {
comptime var name: []const u8 = format.flagName(field);

if (comptime getSwitchFor(Options, field.name)) |swtch| {
if (comptime getSwitchFor(Flags, field.name)) |swtch| {
name = std.fmt.comptimePrint(
"-{c}, {s}",
.{ swtch, name },
Expand All @@ -96,19 +96,19 @@ fn helpOptions(comptime Options: type, comptime command_name: []const u8) []cons

descriptions = descriptions ++ [_]Description{.{
.name = name,
.description = getDescriptionFor(Options, field.name),
.description = getDescriptionFor(Flags, field.name),
}};

const OptionType = switch (@typeInfo(field.type)) {
const T = switch (@typeInfo(field.type)) {
.Optional => |optional| optional.child,
else => field.type,
};

if (@typeInfo(OptionType) == .Enum) {
for (std.meta.fields(OptionType)) |enum_field| {
if (@typeInfo(T) == .Enum) {
for (std.meta.fields(T)) |enum_field| {
const variant = .{
.name = indent ** 2 ++ format.toKebab(enum_field.name),
.description = getDescriptionFor(OptionType, enum_field.name),
.description = getDescriptionFor(T, enum_field.name),
};
if (variant.name.len > max_name_len) max_name_len = variant.name.len;
descriptions = descriptions ++ [_]Description{variant};
Expand All @@ -125,11 +125,11 @@ fn helpOptions(comptime Options: type, comptime command_name: []const u8) []cons
return help;
}

fn getSwitchFor(comptime Options: type, comptime name: []const u8) ?u8 {
if (@hasDecl(Options, "switches") and
@hasField(@TypeOf(Options.switches), name))
fn getSwitchFor(comptime Flags: type, comptime name: []const u8) ?u8 {
if (@hasDecl(Flags, "switches") and
@hasField(@TypeOf(Flags.switches), name))
{
return @field(Options.switches, name);
return @field(Flags.switches, name);
}
return null;
}
Expand Down
48 changes: 24 additions & 24 deletions src/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ const format = @import("format.zig");

const ArgIterator = std.process.ArgIterator;

/// Combines `Config` with an `args` field which stores positional arguments.
pub fn Result(comptime Config: type) type {
/// Combines `Command` with an `args` field which stores positional arguments.
pub fn Result(comptime Command: type) type {
return struct {
config: Config,
/// Stores extra positional arguments not linked to any flag or option.
flags: Command,
/// Stores extra positional arguments not linked to any flag.
args: []const []const u8,
};
}
Expand All @@ -22,7 +22,7 @@ pub fn fatal(comptime message: []const u8, args: anytype) noreturn {
std.process.exit(1);
}

pub fn printHelp(comptime Command: type, comptime command_name: []const u8) void {
fn printHelp(comptime Command: type, comptime command_name: []const u8) void {
const stdout = std.io.getStdOut().writer();
stdout.writeAll(comptime help.helpMessage(Command, command_name)) catch |err| {
fatal("could not write help to stdout: {!}", .{err});
Expand All @@ -49,8 +49,8 @@ pub fn parse(args: *ArgIterator, comptime Command: type) Result(Command) {
fn parseGeneric(args: *ArgIterator, comptime Command: type, comptime name: []const u8) Result(Command) {
return switch (@typeInfo(Command)) {
.Union => parseCommands(args, Command, name),
.Struct => parseOptions(args, Command, name),
else => @compileError("Command must be a struct or union type."),
.Struct => parseFlags(args, Command, name),
else => comptime unreachable,
};
}

Expand All @@ -65,7 +65,7 @@ fn parseCommands(args: *ArgIterator, comptime Commands: type, comptime command_n
if (std.mem.eql(u8, command.name, arg)) {
const sub_result = parseGeneric(args, command.type, command_name ++ " " ++ command.name);
return .{
.config = @unionInit(Commands, command.name, sub_result.config),
.flags = @unionInit(Commands, command.name, sub_result.flags),
.args = sub_result.args,
};
}
Expand All @@ -74,9 +74,9 @@ fn parseCommands(args: *ArgIterator, comptime Commands: type, comptime command_n
fatal("unrecognized subcommand: '{s}'", .{arg});
}

fn parseOptions(args: *ArgIterator, comptime Options: type, comptime command_name: []const u8) Result(Options) {
var options: Options = undefined;
var passed: std.enums.EnumFieldStruct(std.meta.FieldEnum(Options), bool, false) = .{};
fn parseFlags(args: *ArgIterator, comptime Flags: type, comptime command_name: []const u8) Result(Flags) {
var flags: Flags = undefined;
var passed: std.enums.EnumFieldStruct(std.meta.FieldEnum(Flags), bool, false) = .{};

var positional_count: u32 = 0;

Expand All @@ -92,36 +92,36 @@ fn parseOptions(args: *ArgIterator, comptime Options: type, comptime command_nam
}

if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
printHelp(Options, command_name);
printHelp(Flags, command_name);
}

if (arg.len == 1) fatal("unrecognized argument: '-'", .{});
if (arg[1] == '-') {
inline for (std.meta.fields(Options)) |field| {
inline for (std.meta.fields(Flags)) |field| {
if (std.mem.eql(u8, arg, format.flagName(field))) {
@field(passed, field.name) = true;

@field(options, field.name) = parseArg(field.type, args, format.flagName(field));
@field(flags, field.name) = parseArg(field.type, args, format.flagName(field));

continue :next_arg;
}
}
fatal("unrecognized flag: {s}", .{arg});
}

if (@hasDecl(Options, "switches")) {
if (@hasDecl(Flags, "switches")) {
next_switch: for (arg[1..], 1..) |char, i| {
inline for (std.meta.fields(@TypeOf(Options.switches))) |switch_field| {
if (char == @field(Options.switches, switch_field.name)) {
inline for (std.meta.fields(@TypeOf(Flags.switches))) |switch_field| {
if (char == @field(Flags.switches, switch_field.name)) {
@field(passed, switch_field.name) = true;

const FieldType = @TypeOf(@field(options, switch_field.name));
const FieldType = @TypeOf(@field(flags, switch_field.name));
// Removing this check would allow formats like "-abc value-for-a value-for-b value-for-c"
if (FieldType != bool and i != arg.len - 1) {
fatal("expected argument after switch '{c}'", .{char});
}
const value = parseArg(FieldType, args, &.{ '-', char });
@field(options, switch_field.name) = value;
@field(flags, switch_field.name) = value;

continue :next_switch;
}
Expand All @@ -133,13 +133,13 @@ fn parseOptions(args: *ArgIterator, comptime Options: type, comptime command_nam
}
}

inline for (std.meta.fields(Options)) |field| {
inline for (std.meta.fields(Flags)) |field| {
if (@field(passed, field.name) == false) {
if (field.default_value) |default_opaque| {
const default = @as(*const field.type, @ptrCast(@alignCast(default_opaque))).*;
@field(options, field.name) = default;
@field(flags, field.name) = default;
} else {
@field(options, field.name) = switch (@typeInfo(field.type)) {
@field(flags, field.name) = switch (@typeInfo(field.type)) {
.Optional => null,
.Bool => false,
else => fatal("missing required flag: '{s}'", .{format.flagName(field)}),
Expand All @@ -148,8 +148,8 @@ fn parseOptions(args: *ArgIterator, comptime Options: type, comptime command_nam
}
}

return Result(Options){
.config = options,
return Result(Flags){
.flags = flags,
.args = positionals[0..positional_count],
};
}
Expand Down
1 change: 1 addition & 0 deletions src/root.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");
const parser = @import("parser.zig");
const help = @import("help.zig");

pub const parse = parser.parse;
pub const fatal = parser.fatal;
pub const helpMessage = help.helpMessage;
Expand Down
Loading

0 comments on commit a7915f7

Please sign in to comment.