[FIX] fix bugs of parsing arguments

This commit is contained in:
Juliane Alanisia 2024-01-16 13:57:39 +08:00
parent 04d51e79eb
commit 3e230e09dd
2 changed files with 90 additions and 59 deletions

118
cli.zig
View File

@ -4,19 +4,20 @@ const mem = std.mem;
const process = std.process; const process = std.process;
const ArrayList = std.ArrayList; const ArrayList = std.ArrayList;
const StringHashMap = std.StringHashMap; const StringHashMap = std.StringHashMap;
const out = io.getStdOut().writer();
const util = @import("./util.zig"); const util = @import("./util.zig");
pub const Option = struct { pub const Option = struct {
short: []const u8, short: []const u8,
long: []const u8, long: []const u8,
optional: bool, optional: bool = true,
global: bool, global: bool = false,
description: []const u8, description: []const u8,
value_string: []const u8 = "", value_string: []const u8 = "",
value_int: i32 = 0, value_int: i32 = 0,
value_float: f32 = 0.0, value_float: f32 = 0.0,
value_bool: bool = false, value_bool: bool = false,
value_type: ValueType, value_type: ValueType = .BOOL,
const ValueType = enum { const ValueType = enum {
STRING, STRING,
@ -26,6 +27,20 @@ pub const Option = struct {
}; };
}; };
const help_option = Option{
.short = "h",
.long = "help",
.global = true,
.description = "Print help",
};
const version_option = Option{
.short = "v",
.long = "version",
.global = true,
.description = "Print version",
};
pub const Command = struct { pub const Command = struct {
const Commands = StringHashMap(Command); const Commands = StringHashMap(Command);
const Options = StringHashMap(Option); const Options = StringHashMap(Option);
@ -36,7 +51,7 @@ pub const Command = struct {
long_description: []const u8, long_description: []const u8,
usage: []const u8, usage: []const u8,
version: []const u8 = "0.0.1", version: []const u8 = "0.0.1",
run: *const fn (cmd: *Command) void = undefined, run: ?*const fn (cmd: *Command) anyerror!void,
}; };
subcommands: Commands, subcommands: Commands,
@ -46,10 +61,13 @@ pub const Command = struct {
allocator: mem.Allocator, allocator: mem.Allocator,
config: CommandConfig, config: CommandConfig,
pub fn init(allocator: mem.Allocator, args: CommandConfig) Command { pub fn init(allocator: mem.Allocator, args: CommandConfig) !Command {
var options = Options.init(allocator);
try options.put(help_option.long, help_option);
try options.put(version_option.long, version_option);
return Command{ return Command{
.subcommands = Commands.init(allocator), .subcommands = Commands.init(allocator),
.options = Options.init(allocator), .options = options,
.arguments = Args.init(allocator), .arguments = Args.init(allocator),
.cmd_args = Args.init(allocator), .cmd_args = Args.init(allocator),
.allocator = allocator, .allocator = allocator,
@ -69,6 +87,13 @@ pub const Command = struct {
} }
pub fn addCommand(self: *Command, command: Command) !void { pub fn addCommand(self: *Command, command: Command) !void {
var option_iterator = self.options.iterator();
while (option_iterator.next()) |entry| {
const option = entry.value_ptr.*;
if (option.global) {
try @constCast(&command).addOption(option);
}
}
try self.subcommands.put(command.config.name, command); try self.subcommands.put(command.config.name, command);
} }
@ -80,68 +105,70 @@ pub const Command = struct {
else else
self.config.name; self.config.name;
var builder = ArrayList(u8).init(self.allocator); try out.print("{s}\n\nUsage: {s}", .{ description, self.config.usage });
defer builder.deinit(); if (self.subcommands.count() > 0) {
try builder.appendSlice(description); try out.print(" [COMMAND]", .{});
try builder.appendSlice("\n\n"); }
try builder.appendSlice("Usage: "); try out.print(" [OPTIONS] [ARGUMENTS]\n\n", .{});
try builder.appendSlice(self.config.usage);
try builder.appendSlice("\n\n");
if (self.subcommands.count() > 0) { if (self.subcommands.count() > 0) {
try builder.appendSlice("Subcommands: \n"); try out.print("Commands: \n\n", .{});
var entry_iterator = self.subcommands.iterator(); var entry_iterator = self.subcommands.iterator();
while (entry_iterator.next()) |subcommand| { while (entry_iterator.next()) |subcommand| {
try builder.appendSlice(" "); try out.print(" {s: <20}{s}\n", .{ subcommand.key_ptr.*, subcommand.value_ptr.*.config.short_description });
try builder.appendSlice(subcommand.key_ptr.*);
try builder.appendSlice("\t\t\t");
try builder.appendSlice(subcommand.value_ptr.*.config.short_description);
try builder.appendSlice("\n");
} }
try builder.appendSlice("\n"); try out.print("\n", .{});
} }
try out.print("Options: \n\n", .{});
if (self.options.count() > 0) { if (self.options.count() > 0) {
try builder.appendSlice("Options: \n");
var entry_iterator = self.options.iterator(); var entry_iterator = self.options.iterator();
while (entry_iterator.next()) |entry| { while (entry_iterator.next()) |entry| {
const option = entry.value_ptr.*; const option = entry.value_ptr.*;
try builder.appendSlice(" -"); const flags = try mem.concat(self.allocator, u8, &[_][]const u8{ "-", option.short, ", --", option.long });
try builder.appendSlice(option.short); try out.print(" {s: <20}{s}", .{ flags, option.description });
try builder.appendSlice(", --");
try builder.appendSlice(option.long);
try builder.appendSlice("\t\t");
try builder.appendSlice(option.description);
if (!option.optional) { if (!option.optional) {
try builder.appendSlice(" (required)"); try out.print(" (required)\n", .{});
} } else {
try builder.appendSlice("\n"); try out.print("\n", .{});
}
} }
} }
_ = try io.getStdOut().write(builder.items);
} }
pub fn printVersion(self: *Command) !void { pub fn printVersion(self: *Command) !void {
_ = try io.getStdOut().write(try mem.concat(self.allocator, u8, &[_][]const u8{ self.config.version, "\n" })); _ = try io.getStdOut().write(try mem.concat(self.allocator, u8, &[_][]const u8{ self.config.version, "\n" }));
} }
fn defaultRun(self: *Command) !void {
if (self.options.get("help").?.value_bool) {
try self.help();
} else if (self.options.get("version").?.value_bool) {
try self.printVersion();
} else if (self.config.run) |run| {
try run(self);
}
}
fn parse(self: *Command) !void { fn parse(self: *Command) !void {
var i: usize = 0; var i: usize = 1;
var current_command = self.*; var current_command = self.*;
var current_option: Option = undefined; var current_option: Option = undefined;
var parsing_option = false; var parsing_option = false;
var subcommand_parse_over = false;
var option_parse_over = false;
parse_while_blk: while (i < self.cmd_args.items.len) { parse_while_blk: while (i < self.cmd_args.items.len) {
const current_arg = self.cmd_args.items[i]; const current_arg = self.cmd_args.items[i];
if (!parsing_option) { if (!subcommand_parse_over) {
if (current_command.subcommands.get(current_arg)) |subcommand| { if (current_command.subcommands.get(current_arg)) |subcommand| {
current_command = subcommand; current_command = subcommand;
i += 1; i += 1;
continue :parse_while_blk; continue :parse_while_blk;
} }
subcommand_parse_over = true;
} }
var entry_iterator = current_command.options.iterator(); if (!option_parse_over) {
while (entry_iterator.next()) |entry| {
const option = entry.value_ptr.*;
if (parsing_option) { if (parsing_option) {
switch (current_option.value_type) { switch (current_option.value_type) {
.STRING => current_option.value_string = current_arg, .STRING => current_option.value_string = current_arg,
@ -149,28 +176,34 @@ pub const Command = struct {
.FLOAT => current_option.value_float = util.strToFloat(current_arg.ptr), .FLOAT => current_option.value_float = util.strToFloat(current_arg.ptr),
else => {}, else => {},
} }
try self.options.put(entry.key_ptr.*, current_option); try current_command.options.put(current_option.long, current_option);
parsing_option = false; parsing_option = false;
i += 1; i += 1;
continue :parse_while_blk; continue :parse_while_blk;
} else if (mem.eql(u8, current_arg, try mem.concat(self.allocator, u8, &[_][]const u8{ "-", option.short })) or }
mem.eql(u8, current_arg, try mem.concat(self.allocator, u8, &[_][]const u8{ "--", option.long }))) var entry_iterator = current_command.options.iterator();
while (entry_iterator.next()) |entry| {
const option = entry.value_ptr.*;
if (mem.eql(u8, current_arg, try mem.concat(current_command.allocator, u8, &[_][]const u8{ "-", option.short })) or
mem.eql(u8, current_arg, try mem.concat(current_command.allocator, u8, &[_][]const u8{ "--", option.long })))
{ {
current_option = option; current_option = option;
parsing_option = true; parsing_option = true;
if (current_option.value_type == .BOOL) { if (current_option.value_type == .BOOL) {
current_option.value_bool = true; current_option.value_bool = true;
try self.options.put(entry.key_ptr.*, current_option); try current_command.options.put(entry.key_ptr.*, current_option);
parsing_option = false; parsing_option = false;
} }
i += 1; i += 1;
continue :parse_while_blk; continue :parse_while_blk;
} }
} }
try self.arguments.append(current_arg); option_parse_over = true;
}
try current_command.arguments.append(current_arg);
i += 1; i += 1;
} }
current_command.config.run(&current_command); try current_command.defaultRun();
} }
pub fn execute(self: *Command) !void { pub fn execute(self: *Command) !void {
@ -178,6 +211,7 @@ pub const Command = struct {
while (arg_iterator.next()) |arg| { while (arg_iterator.next()) |arg| {
try self.cmd_args.append(arg); try self.cmd_args.append(arg);
} }
self.cmd_args.items[0] = self.config.name;
try self.parse(); try self.parse();
} }

View File

@ -4,12 +4,12 @@ const cli = @import("cli");
const Command = cli.Command; const Command = cli.Command;
const Option = cli.Option; const Option = cli.Option;
fn rootRun(cmd: *Command) void { fn rootRun(cmd: *Command) anyerror!void {
const e = cmd.getIntOption("example"); const e = cmd.getIntOption("example");
std.debug.print("{d}\n", .{e}); std.debug.print("{d}\n", .{e});
} }
fn subcommandRun(cmd: *Command) void { fn subcommandRun(cmd: *Command) anyerror!void {
std.debug.print("{s}\n", .{cmd.config.name}); std.debug.print("{s}\n", .{cmd.config.name});
} }
@ -23,24 +23,21 @@ pub fn main() !void {
.name = "example", .name = "example",
.run = rootRun, .run = rootRun,
}; };
var root_command = Command.init(allocator, config); var root_command = try Command.init(allocator, config);
try root_command.addCommand(Command.init(allocator, .{ const subcommand = try Command.init(allocator, .{
.short_description = "SubExample", .short_description = "SubExample",
.long_description = "Subcommand example", .long_description = "Subcommand example",
.usage = "subexample", .usage = "subexample",
.name = "subexample", .name = "subexample",
.run = subcommandRun, .run = subcommandRun,
})); });
try root_command.addCommand(subcommand);
try root_command.addOption(Option{ try root_command.addOption(Option{
.short = "e", .short = "e",
.long = "example", .long = "example",
.description = "example option", .description = "example option",
.optional = false,
.global = false,
.value_type = .INT, .value_type = .INT,
.value_int = 3, .value_int = 3,
}); });
try root_command.execute(); try root_command.execute();
try root_command.help();
try root_command.printVersion();
} }