Skip to content

Commit

Permalink
Implemented Argument Tokenization
Browse files Browse the repository at this point in the history
- Added `cova.tokenize_args()` and `cova.TokenizeConfig`.
- Fixed small bug in `Command`.
- Closes #22
  • Loading branch information
00JCIV00 committed Sep 19, 2023
1 parent 243de80 commit 89a6971
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 3 deletions.
5 changes: 5 additions & 0 deletions examples/covademo.zig
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,9 @@ pub fn main() !void {
try fn_cmd.callAs(demoFn, null, void);
}

// Tokenization Example
const arg_str = "cova struct-cmd --multi-str \"demo str\" -m 'a \"quoted string\"' -m \"A string using an 'apostrophe'\" -m (quick parans test) 50";
const args = try cova.tokenizeArgs(arg_str, alloc, .{ .groupers_open = "\"'(", .groupers_close = "\"')" });
defer alloc.free(args);
log.debug("Tokenized Args:\n{s}", .{ args });
}
2 changes: 1 addition & 1 deletion src/Command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ pub fn Custom(comptime config: Config) type {
var cmd_name_buf: [@typeName(From_T).len]u8 = undefined;
const cmd_name = if (from_config.cmd_name.len > 0) from_config.cmd_name else cmdName: {
if (!from_config.convert_syntax) break :cmdName @typeName(From_T) else {
_ = mem.replace(u8, @typeName(From_T), "_", "-", cmd_name_buf);
_ = mem.replace(u8, @typeName(From_T), "_", "-", cmd_name_buf[0..]);
break :cmdName cmd_name_buf[0..];
}
};
Expand Down
1 change: 1 addition & 0 deletions src/Value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ pub fn Custom(comptime config: Config) type {
if (ary_info == .Optional) break :aryType ary_info.Optional.child
else break :aryType comp_info.Array.child;
},
// TODO: Check if Pointer is a String.
.Bool, .Int, .Float, .Pointer => From_T,
else => {
if (!from_config.ignore_incompatible) @compileError("The comp '" ++ comp_name ++ "' of type '" ++ @typeName(From_T) ++ "' is incompatible.")
Expand Down
69 changes: 67 additions & 2 deletions src/cova.zig
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ pub fn parseArgs(
}
// - Long Options
else if (mem.eql(u8, arg[0..long_pf.len], long_pf)) {
if (arg.len == long_pf.len) {
if (arg.len == long_pf.len and parse_config.enable_opt_termination) {
opt_term = true;
continue;
}
Expand Down Expand Up @@ -414,7 +414,7 @@ fn parseOpt(args: *ArgIteratorGeneric, comptime OptionType: type, opt: *const Op
try opt.val.set(set_arg);
}

/// React to Parsing Errors
/// React to Parsing Errors with the given Reaction (`reaction`) based on the provided Argument (`arg`) to the provided Writer (`writer`).
fn errReaction(reaction: ParseConfig.ParseErrorReaction, arg: anytype, writer: anytype) !void {
return switch(reaction) {
.Usage => arg.usage(writer),
Expand All @@ -423,6 +423,62 @@ fn errReaction(reaction: ParseConfig.ParseErrorReaction, arg: anytype, writer: a
};
}

pub const TokenizeConfig = struct{
/// Delimiter Characters
delimiters: []const u8 = " ",
/// Grouping Open Characters
/// Note, these Characters must line up with `groupers_close` in pairs.
groupers_open: []const u8 = "\"'",
/// Grouping Close Characters
groupers_close: []const u8 = "\"'",
};

/// Tokenize an Argument String (`arg_str`) into a slice of Strings using the provided Allocator (`alloc`) and TokenizeConfig (`token_config`).
/// This handles basic quoting using single or double quotes (`'` or `"`) with no support for escape sequences.
pub fn tokenizeArgs(arg_str: []const u8, alloc: mem.Allocator, token_config: TokenizeConfig) ![]const []const u8 {
var start: usize = 0;
var end: usize = 0;
var quote_char: ?u8 = null;
var args_list = std.ArrayList([]const u8).init(alloc);

if (token_config.groupers_open.len != token_config.groupers_close.len) {
log.err("The length `token_config.groupers_open` must match that of `token_config.groupers_close`. These should be open/close pairs.", .{});
return error.UnbalancedGrouperPairs;
}

for (arg_str, 0..) |char, idx| {
if (mem.indexOfScalar(u8, token_config.delimiters, char) != null and quote_char == null) {
end = idx;
if (start == end) {
start += 1;
continue;
}
try args_list.append(try alloc.dupe(u8, arg_str[start..end]));
start = end + 1;
}
else if (quote_char == null and mem.indexOfScalar(u8, token_config.groupers_open, char) != null) {
if (mem.indexOfScalar(u8, token_config.groupers_open, char)) |close_idx| {
quote_char = token_config.groupers_close[close_idx];
start = idx + 1;
}
}
else if (quote_char) |q_char| {
if (char == q_char) {
end = idx;
try args_list.append(try alloc.dupe(u8, arg_str[start..end]));
start = end + 1;
quote_char = null;
}
}
else if (idx == arg_str.len - 1) {
end = arg_str.len;
try args_list.append(try alloc.dupe(u8, arg_str[start..end]));
}
}

return try args_list.toOwnedSlice();
}


// TESTING
const TestCommand = Command.Custom(.{
Expand Down Expand Up @@ -563,6 +619,15 @@ const TestCmdFromStruct = struct {
};
const test_setup_cmd_from_struct = TestCommand.from(TestCmdFromStruct, .{});

test "tokenize args" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
const arg_str = "cova struct-cmd --multi-str \"demo str\" -m 'a \"quoted string\"' -m \"A string using an 'apostrophe'\" 50";
const test_args = try tokenizeArgs(arg_str, alloc, .{});
const expect_args = [_][]const u8{ "cova", "struct-cmd", "--multi-str", "demo str", "-m", "a \"quoted string\"", "-m", "A string using an 'apostrophe'", "50" };
for (test_args, expect_args[0..]) |t_arg, e_arg| try testing.expectEqualStrings(t_arg, e_arg);
}

test "command setup" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
Expand Down

0 comments on commit 89a6971

Please sign in to comment.