200 lines
7.1 KiB
Zig
200 lines
7.1 KiB
Zig
const std = @import("std");
|
|
const io = std.io;
|
|
const mem = std.mem;
|
|
const process = std.process;
|
|
const ArrayList = std.ArrayList;
|
|
const StringHashMap = std.StringHashMap;
|
|
const util = @import("./util.zig");
|
|
|
|
pub const Option = struct {
|
|
short: []const u8,
|
|
long: []const u8,
|
|
optional: bool,
|
|
global: bool,
|
|
description: []const u8,
|
|
value_string: []const u8 = "",
|
|
value_int: i32 = 0,
|
|
value_float: f32 = 0.0,
|
|
value_bool: bool = false,
|
|
value_type: ValueType,
|
|
|
|
const ValueType = enum {
|
|
STRING,
|
|
BOOL,
|
|
INT,
|
|
FLOAT,
|
|
};
|
|
};
|
|
|
|
pub const Command = struct {
|
|
const Commands = StringHashMap(Command);
|
|
const Options = StringHashMap(Option);
|
|
const Args = ArrayList([]const u8);
|
|
pub const CommandConfig = struct {
|
|
name: []const u8,
|
|
short_description: []const u8,
|
|
long_description: []const u8,
|
|
usage: []const u8,
|
|
version: []const u8 = "0.0.1",
|
|
run: *const fn (cmd: *Command) void = undefined,
|
|
};
|
|
|
|
subcommands: Commands,
|
|
options: Options,
|
|
arguments: Args,
|
|
cmd_args: Args,
|
|
allocator: mem.Allocator,
|
|
config: CommandConfig,
|
|
|
|
pub fn init(allocator: mem.Allocator, args: CommandConfig) Command {
|
|
return Command{
|
|
.subcommands = Commands.init(allocator),
|
|
.options = Options.init(allocator),
|
|
.arguments = Args.init(allocator),
|
|
.cmd_args = Args.init(allocator),
|
|
.allocator = allocator,
|
|
.config = args,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Command) void {
|
|
self.subcommands.deinit();
|
|
self.options.deinit();
|
|
self.arguments.deinit();
|
|
self.cmd_args.deinit();
|
|
}
|
|
|
|
pub fn addOption(self: *Command, option: Option) !void {
|
|
try self.options.put(option.long, option);
|
|
}
|
|
|
|
pub fn addCommand(self: *Command, command: Command) !void {
|
|
try self.subcommands.put(command.config.name, command);
|
|
}
|
|
|
|
pub fn help(self: *Command) !void {
|
|
const description = if (self.config.long_description.len > 0)
|
|
self.config.long_description
|
|
else if (self.config.short_description.len > 0)
|
|
self.config.short_description
|
|
else
|
|
self.config.name;
|
|
|
|
var builder = ArrayList(u8).init(self.allocator);
|
|
defer builder.deinit();
|
|
try builder.appendSlice(description);
|
|
try builder.appendSlice("\n\n");
|
|
try builder.appendSlice("Usage: ");
|
|
try builder.appendSlice(self.config.usage);
|
|
try builder.appendSlice("\n\n");
|
|
|
|
if (self.subcommands.count() > 0) {
|
|
try builder.appendSlice("Subcommands: \n");
|
|
var entry_iterator = self.subcommands.iterator();
|
|
while (entry_iterator.next()) |subcommand| {
|
|
try builder.appendSlice(" ");
|
|
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");
|
|
}
|
|
|
|
if (self.options.count() > 0) {
|
|
try builder.appendSlice("Options: \n");
|
|
var entry_iterator = self.options.iterator();
|
|
while (entry_iterator.next()) |entry| {
|
|
const option = entry.value_ptr.*;
|
|
try builder.appendSlice(" -");
|
|
try builder.appendSlice(option.short);
|
|
try builder.appendSlice(", --");
|
|
try builder.appendSlice(option.long);
|
|
try builder.appendSlice("\t\t");
|
|
try builder.appendSlice(option.description);
|
|
if (!option.optional) {
|
|
try builder.appendSlice(" (required)");
|
|
}
|
|
try builder.appendSlice("\n");
|
|
}
|
|
}
|
|
_ = try io.getStdOut().write(builder.items);
|
|
}
|
|
|
|
pub fn printVersion(self: *Command) !void {
|
|
_ = try io.getStdOut().write(try mem.concat(self.allocator, u8, &[_][]const u8{ self.config.version, "\n" }));
|
|
}
|
|
|
|
fn parse(self: *Command) !void {
|
|
var i: usize = 0;
|
|
var current_command = self.*;
|
|
var current_option: Option = undefined;
|
|
var parsing_option = false;
|
|
parse_while_blk: while (i < self.cmd_args.items.len) {
|
|
const current_arg = self.cmd_args.items[i];
|
|
if (!parsing_option) {
|
|
if (current_command.subcommands.get(current_arg)) |subcommand| {
|
|
current_command = subcommand;
|
|
i += 1;
|
|
continue :parse_while_blk;
|
|
}
|
|
}
|
|
var entry_iterator = current_command.options.iterator();
|
|
while (entry_iterator.next()) |entry| {
|
|
const option = entry.value_ptr.*;
|
|
if (parsing_option) {
|
|
switch (current_option.value_type) {
|
|
.STRING => current_option.value_string = current_arg,
|
|
.INT => current_option.value_int = util.strToInt(current_arg.ptr),
|
|
.FLOAT => current_option.value_float = util.strToFloat(current_arg.ptr),
|
|
else => {},
|
|
}
|
|
try self.options.put(entry.key_ptr.*, current_option);
|
|
parsing_option = false;
|
|
i += 1;
|
|
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 })))
|
|
{
|
|
current_option = option;
|
|
parsing_option = true;
|
|
if (current_option.value_type == .BOOL) {
|
|
current_option.value_bool = true;
|
|
try self.options.put(entry.key_ptr.*, current_option);
|
|
parsing_option = false;
|
|
}
|
|
i += 1;
|
|
continue :parse_while_blk;
|
|
}
|
|
}
|
|
try self.arguments.append(current_arg);
|
|
i += 1;
|
|
}
|
|
current_command.config.run(¤t_command);
|
|
}
|
|
|
|
pub fn execute(self: *Command) !void {
|
|
var arg_iterator = process.args();
|
|
while (arg_iterator.next()) |arg| {
|
|
try self.cmd_args.append(arg);
|
|
}
|
|
try self.parse();
|
|
}
|
|
|
|
pub fn getBoolOption(self: *Command, option_name: []const u8) bool {
|
|
return if (self.options.get(option_name)) |option| option.value_bool else false;
|
|
}
|
|
|
|
pub fn getStringOption(self: *Command, option_name: []const u8) []const u8 {
|
|
return if (self.options.get(option_name)) |option| option.value_string else "";
|
|
}
|
|
|
|
pub fn getIntOption(self: *Command, option_name: []const u8) i32 {
|
|
return if (self.options.get(option_name)) |option| option.value_int else 0;
|
|
}
|
|
|
|
pub fn getFloatOption(self: *Command, option_name: []const u8) f32 {
|
|
return if (self.options.get(option_name)) |option| option.value_float else 0.0;
|
|
}
|
|
};
|