[FIX] fix bugs of parsing arguments
This commit is contained in:
parent
04d51e79eb
commit
3e230e09dd
118
cli.zig
118
cli.zig
|
|
@ -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(¤t_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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
15
demo.zig
15
demo.zig
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue