summaryrefslogtreecommitdiff
path: root/src/cmd/lib/flags.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/lib/flags.zig')
-rw-r--r--src/cmd/lib/flags.zig197
1 files changed, 197 insertions, 0 deletions
diff --git a/src/cmd/lib/flags.zig b/src/cmd/lib/flags.zig
new file mode 100644
index 0000000..637ea83
--- /dev/null
+++ b/src/cmd/lib/flags.zig
@@ -0,0 +1,197 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const ArrayList = std.ArrayList;
+const HashMap = std.HashMap;
+
+pub const FlagType = enum {
+ Boolean,
+ String,
+ Number,
+};
+
+pub const FlagDef = struct {
+ name: []const u8,
+ aliases: []const []const u8,
+ flag_type: FlagType,
+ description: []const u8,
+ default_value: ?[]const u8 = null,
+};
+
+pub const FlagValue = union(FlagType) {
+ Boolean: bool,
+ String: []const u8,
+ Number: i32,
+};
+
+pub const ParsedFlags = struct {
+ values: HashMap([]const u8, FlagValue, StringContext, std.hash_map.default_max_load_percentage),
+ allocator: Allocator,
+
+ const StringContext = struct {
+ pub fn hash(self: @This(), s: []const u8) u64 {
+ _ = self;
+ return std.hash_map.hashString(s);
+ }
+ pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
+ _ = self;
+ return std.mem.eql(u8, a, b);
+ }
+ };
+
+ pub fn init(allocator: Allocator) ParsedFlags {
+ return ParsedFlags{
+ .values = HashMap([]const u8, FlagValue, StringContext, std.hash_map.default_max_load_percentage).init(allocator),
+ .allocator = allocator,
+ };
+ }
+
+ pub fn deinit(self: *ParsedFlags) void {
+ self.values.deinit();
+ }
+
+ pub fn getBool(self: *const ParsedFlags, name: []const u8) bool {
+ if (self.values.get(name)) |value| {
+ return switch (value) {
+ .Boolean => |b| b,
+ else => false,
+ };
+ }
+ return false;
+ }
+
+ pub fn getString(self: *const ParsedFlags, name: []const u8) ?[]const u8 {
+ if (self.values.get(name)) |value| {
+ return switch (value) {
+ .String => |s| s,
+ else => null,
+ };
+ }
+ return null;
+ }
+
+ pub fn getNumber(self: *const ParsedFlags, name: []const u8) ?i32 {
+ if (self.values.get(name)) |value| {
+ return switch (value) {
+ .Number => |n| n,
+ else => null,
+ };
+ }
+ return null;
+ }
+
+ pub fn setValue(self: *ParsedFlags, name: []const u8, value: FlagValue) !void {
+ try self.values.put(name, value);
+ }
+};
+
+pub const CommandFlags = struct {
+ flags: []const FlagDef,
+
+ pub fn parse(self: *const CommandFlags, args: []const []const u8, allocator: Allocator) !ParsedFlags {
+ var parsed = ParsedFlags.init(allocator);
+
+ for (args) |arg| {
+ if (arg.len == 0 or arg[0] != '/') continue;
+
+ const flag_text = arg[1..];
+ if (flag_text.len == 0) continue;
+
+ var flag_name: []const u8 = undefined;
+ var flag_value: ?[]const u8 = null;
+
+ if (std.mem.indexOf(u8, flag_text, ":")) |colon_pos| {
+ flag_name = flag_text[0..colon_pos];
+ flag_value = flag_text[colon_pos + 1 ..];
+ } else {
+ flag_name = flag_text;
+ }
+
+ const flag_def = self.findFlag(flag_name) orelse {
+ return error.UnknownFlag;
+ };
+
+ const value = switch (flag_def.flag_type) {
+ .Boolean => FlagValue{ .Boolean = true },
+ .String => blk: {
+ const str_value = flag_value orelse flag_def.default_value orelse {
+ return error.MissingFlagValue;
+ };
+ break :blk FlagValue{ .String = try allocator.dupe(u8, str_value) };
+ },
+ .Number => blk: {
+ const str_value = flag_value orelse flag_def.default_value orelse {
+ return error.MissingFlagValue;
+ };
+ const num_value = std.fmt.parseInt(i32, str_value, 10) catch {
+ return error.InvalidNumber;
+ };
+ break :blk FlagValue{ .Number = num_value };
+ },
+ };
+
+ try parsed.setValue(flag_def.name, value);
+ }
+
+ return parsed;
+ }
+
+ fn findFlag(self: *const CommandFlags, name: []const u8) ?*const FlagDef {
+ for (self.flags) |*flag_def| {
+ if (std.mem.eql(u8, flag_def.name, name)) {
+ return flag_def;
+ }
+
+ for (flag_def.aliases) |alias| {
+ if (alias.len > 0 and alias[0] == '/') {
+ if (std.mem.eql(u8, alias[1..], name)) {
+ return flag_def;
+ }
+ } else if (std.mem.eql(u8, alias, name)) {
+ return flag_def;
+ }
+ }
+ }
+ return null;
+ }
+
+ pub fn getHelp(self: *const CommandFlags, command_name: []const u8, allocator: Allocator) ![]const u8 {
+ var help = ArrayList(u8).init(allocator);
+ defer help.deinit();
+
+ try help.appendSlice(command_name);
+ try help.appendSlice(" - Available flags:\n\n");
+
+ for (self.flags) |flag_def| {
+ try help.appendSlice(" /");
+ try help.appendSlice(flag_def.name);
+
+ if (flag_def.aliases.len > 0) {
+ try help.appendSlice(" (");
+ for (flag_def.aliases, 0..) |alias, i| {
+ if (i > 0) try help.appendSlice(", ");
+ try help.appendSlice(alias);
+ }
+ try help.appendSlice(")");
+ }
+
+ switch (flag_def.flag_type) {
+ .String => try help.appendSlice(":value"),
+ .Number => try help.appendSlice(":number"),
+ .Boolean => {},
+ }
+
+ try help.appendSlice(" - ");
+ try help.appendSlice(flag_def.description);
+ try help.appendSlice("\n");
+ }
+
+ return help.toOwnedSlice();
+ }
+};
+
+pub const FlagParseError = error{
+ UnknownFlag,
+ MissingFlagValue,
+ InvalidNumber,
+ OutOfMemory,
+};