Skip to content

Commit

Permalink
feat: Implement new settings generator
Browse files Browse the repository at this point in the history
  • Loading branch information
PrajwalCH committed Oct 25, 2022
1 parent 10ad491 commit 59e9c24
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 87 deletions.
61 changes: 26 additions & 35 deletions src/Arg.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
const Arg = @This();
const std = @import("std");
const ArgsContext = @import("parser/ArgsContext.zig");
const MakeSettings = @import("settings.zig").MakeSettings;

const Settings = struct {
takes_value: bool = false,
takes_multiple_values: bool = false,
allow_empty_value: bool = false,
};
const Settings = MakeSettings(&[_][]const u8{
"takes_value",
"takes_multiple_values",
"allow_empty_value",
});

name: []const u8,
short_name: ?u8,
Expand Down Expand Up @@ -57,56 +58,34 @@ pub fn setDescription(self: *Arg, description: []const u8) void {
}

/// Sets the minimum number of values required to provide for an argument.
/// Implicitly sets the `Arg.takesValue(true)`
/// Implicitly applies the `.takes_value` setting
pub fn minValues(self: *Arg, num: usize) void {
if (num >= 1) {
self.min_values = num;
self.takesValue(true);
self.applySetting(.takes_value);
}
}

/// Sets the maximum number of values an argument can take.
/// Implicitly sets the `Arg.takesValue(true)`
/// Implicitly applies the `.takes_value` setting
pub fn maxValues(self: *Arg, num: usize) void {
self.max_values = num;
self.takesValue(true);
self.applySetting(.takes_value);
}

/// Sets the allowed values for an argument.
/// Value outside of allowed values will be consider as error.
/// Implicitly sets the `Arg.takesValue(true)`
/// Implicitly applies the `.takes_value` setting
pub fn allowedValues(self: *Arg, values: []const []const u8) void {
self.allowed_values = values;
self.takesValue(true);
self.applySetting(.takes_value);
}

/// Sets separator between the values of an argument.
/// Implicitly sets the `Arg.takesValue(true)`
/// Implicitly applies the `.takes_value` setting
pub fn valuesDelimiter(self: *Arg, delimiter: []const u8) void {
self.values_delimiter = delimiter;
self.takesValue(true);
}

/// Specifies that an argument will takes a value
pub fn takesValue(self: *Arg, b: bool) void {
self.settings.takes_value = b;
}

/// Specifies that the argument will takes an unknown number of values.
/// You can use `Arg.maxValues(n)` to limit the number of values.
///
/// Note:
/// Values will continue to be consume until one of the following condition wiil satisfies:
/// 1. If another flag is found
/// 2. If parser reaches the end of raw argument
/// 3. If parser reaches the maximum number of values. Requires to explicitly set `Arg.maxValues(n)`
pub fn takesMultipleValues(self: *Arg, b: bool) void {
self.settings.takes_multiple_values = b;
}

/// Specifies wether an argument can take a empty value or not
pub fn allowedEmptyValue(self: *Arg, b: bool) void {
self.settings.allow_empty_value = b;
self.applySetting(.takes_value);
}

pub fn verifyValueInAllowedValues(self: *const Arg, value_to_check: []const u8) bool {
Expand All @@ -120,6 +99,18 @@ pub fn verifyValueInAllowedValues(self: *const Arg, value_to_check: []const u8)
}
}

pub fn applySetting(self: *Arg, option: Settings.Options) void {
return self.settings.apply(option);
}

pub fn removeSetting(self: *Arg, option: Settings.Options) void {
return self.settings.remove(option);
}

pub fn isSettingApplied(self: *const Arg, option: Settings.Options) bool {
return self.settings.isApplied(option);
}

test "emit methods docs" {
std.testing.refAllDecls(@This());
}
43 changes: 20 additions & 23 deletions src/Command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ const Command = @This();
const std = @import("std");
const Arg = @import("Arg.zig");
const Help = @import("Help.zig");
const MakeSettings = @import("settings.zig").MakeSettings;

const mem = std.mem;
const ArrayList = std.ArrayListUnmanaged;
const Allocator = mem.Allocator;

const Setting = struct {
takes_value: bool = false,
arg_required: bool = false,
subcommand_required: bool = false,
};
const Settings = MakeSettings(&[_][]const u8{
"takes_value",
"arg_required",
"subcommand_required",
});

allocator: Allocator,
name: []const u8,
description: ?[]const u8 = null,
args: ArrayList(Arg) = .{},
options: ArrayList(Arg) = .{},
subcommands: ArrayList(Command) = .{},
setting: Setting = .{},
settings: Settings = .{},

/// Creates a new instance of it
pub fn new(allocator: Allocator, name: []const u8) Command {
Expand Down Expand Up @@ -81,22 +81,7 @@ pub fn takesNValues(self: *Command, arg_name: []const u8, n: usize) !void {
if (n > 1) arg.valuesDelimiter(",");

try self.addArg(arg);
self.takesValue(true);
}

/// Specifies that the command takes value. Default to 'false`
pub fn takesValue(self: *Command, b: bool) void {
self.setting.takes_value = b;
}

/// Specifies that argument is required to provide. Default to `false`
pub fn argRequired(self: *Command, boolean: bool) void {
self.setting.arg_required = boolean;
}

/// Specifies that sub-command is required to provide. Default to `false`
pub fn subcommandRequired(self: *Command, boolean: bool) void {
self.setting.subcommand_required = boolean;
self.applySetting(.takes_value);
}

pub fn countArgs(self: *const Command) usize {
Expand Down Expand Up @@ -145,6 +130,18 @@ pub fn findSubcommand(self: *const Command, provided_subcmd: []const u8) ?*const
return null;
}

pub fn applySetting(self: *Command, option: Settings.Options) void {
return self.settings.apply(option);
}

pub fn removeSetting(self: *Command, option: Settings.Options) void {
return self.settings.remove(option);
}

pub fn isSettingApplied(self: *const Command, option: Settings.Options) bool {
return self.settings.isApplied(option);
}

pub fn help(self: *const Command) Help {
return Help.init(self);
}
Expand Down
27 changes: 14 additions & 13 deletions src/Help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ const Help = @This();

const std = @import("std");
const Command = @import("Command.zig");
const MakeSettings = @import("settings.zig").MakeSettings;
const Braces = std.meta.Tuple(&[2]type{ u8, u8 });

pub const Options = struct {
include_args: bool = false,
include_subcmds: bool = false,
include_flags: bool = false,
};
pub const Options = MakeSettings(&[_][]const u8{
"include_args",
"include_subcmds",
"include_flags",
});

cmd: *const Command,
options: Options = .{},
Expand Down Expand Up @@ -45,7 +46,7 @@ pub fn writeAll(self: *Help) !void {
try self.writeCommands(writer);
try self.writeOptions(writer);

if (self.options.include_subcmds) {
if (self.options.isApplied(.include_subcmds)) {
try writeNewLine(writer);
try writer.print(
"Run '{s} <command> -h' or '{s} <command> --help' to get help for specific command",
Expand All @@ -65,8 +66,8 @@ fn writeHeader(self: *Help, writer: anytype) !void {
try writer.print("Usage: {s} ", .{self.cmd.name});

if (self.cmd.countArgs() >= 1) {
self.options.include_flags = true;
const braces = getBraces(self.cmd.setting.arg_required);
self.options.apply(.include_args);
const braces = getBraces(self.cmd.isSettingApplied(.arg_required));

for (self.cmd.args.items) |arg| {
try writer.print("{c}{s}{c} ", .{ braces[0], arg.name, braces[1] });
Expand All @@ -76,8 +77,8 @@ fn writeHeader(self: *Help, writer: anytype) !void {
if (self.cmd.countOptions() >= 1) try writer.writeAll("[OPTIONS] ");

if (self.cmd.countSubcommands() >= 1) {
self.options.include_subcmds = true;
const braces = getBraces(self.cmd.setting.subcommand_required);
self.options.apply(.include_subcmds);
const braces = getBraces(self.cmd.isSettingApplied(.subcommand_required));

try writer.print("{c}COMMAND{c}", .{ braces[0], braces[1] });
try writeNewLine(writer);
Expand All @@ -90,7 +91,7 @@ fn getBraces(required: bool) Braces {
}

fn writeCommands(self: *Help, writer: anytype) !void {
if (!(self.options.include_subcmds)) return;
if (!(self.options.isApplied(.include_subcmds))) return;

try writer.writeAll("Commands:");
try writeNewLine(writer);
Expand All @@ -104,7 +105,7 @@ fn writeCommands(self: *Help, writer: anytype) !void {
}

fn writeOptions(self: *Help, writer: anytype) !void {
if (self.options.include_flags) {
if (self.options.isApplied(.include_flags)) {
try writer.writeAll("Options:");
try writeNewLine(writer);

Expand All @@ -116,7 +117,7 @@ fn writeOptions(self: *Help, writer: anytype) !void {
if (arg.long_name) |long_name|
try writer.print(" --{s} ", .{long_name});

if (arg.settings.takes_value) {
if (arg.isSettingApplied(.takes_value)) {
// TODO: Add new `Arg.placeholderName()` to display proper placeholder

// Required options: <A | B | C>
Expand Down
11 changes: 5 additions & 6 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ test "arg required error" {
};

var app = try initAppArgs(allocator);
app.rootCommand().argRequired(true);
app.rootCommand().applySetting(.arg_required);
defer app.deinit();

try testing.expectError(error.CommandArgumentNotProvided, app.parseFrom(argv));
Expand All @@ -51,7 +51,7 @@ test "subcommand required error" {
};

var app = try initAppArgs(allocator);
app.rootCommand().subcommandRequired(true);
app.rootCommand().applySetting(.subcommand_required);
defer app.deinit();

try testing.expectError(error.CommandSubcommandNotProvided, app.parseFrom(argv));
Expand Down Expand Up @@ -112,11 +112,10 @@ test "arg.takes_multiple_values" {

var app = try initAppArgs(allocator);
defer app.deinit();
app.rootCommand().takesValue(true);
app.rootCommand().applySetting(.takes_value);

var files = Arg.new("files");
//files.takesValue(true);
files.takesMultipleValues(true);
files.applySetting(.takes_multiple_values);

try app.rootCommand().addArg(files);
var args = try app.parseFrom(argv);
Expand All @@ -131,7 +130,7 @@ test "using displayHelp and displaySubcommandHelp help api" {

var app = try initAppArgs(allocator);
defer app.deinit();
app.rootCommand().takesValue(false);
app.rootCommand().removeSetting(.takes_value);

var subcmd = app.createCommand("subcmd", null);
try subcmd.addArg(flag.boolean("bool", null, null));
Expand Down
20 changes: 10 additions & 10 deletions src/parser/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub fn init(
.err_builder = ErrorBuilder.init(),
.cmd = command,
.cmd_args_idx = 0,
.consume_cmd_args = (command.setting.takes_value and command.countArgs() >= 1),
.consume_cmd_args = (command.isSettingApplied(.takes_value) and command.countArgs() >= 1),
};
}

Expand Down Expand Up @@ -162,7 +162,7 @@ pub fn parse(self: *Parser) Error!ArgsContext {
}

if (!(self.args_ctx.isPresent("help"))) {
if (self.cmd.setting.subcommand_required and self.args_ctx.subcommand == null) {
if (self.cmd.isSettingApplied(.subcommand_required) and self.args_ctx.subcommand == null) {
self.err_builder.setErr(Error.CommandSubcommandNotProvided);
return self.err_builder.err;
}
Expand All @@ -179,7 +179,7 @@ fn consumeCommandArg(self: *Parser, token: *const Token) Error!void {

// Looks like we found a option
if (token.tag != .some_argument) {
if (self.cmd.setting.arg_required and (self.args_ctx.args.count() == 0)) {
if (self.cmd.isSettingApplied(.arg_required) and (self.args_ctx.args.count() == 0)) {
self.err_builder.setErr(Error.CommandArgumentNotProvided);
return self.err_builder.err;
} else {
Expand Down Expand Up @@ -221,7 +221,7 @@ fn parseShortOption(self: *Parser, token: *const Token) InternalError!void {
};
self.err_builder.setArg(arg);

if (!(arg.settings.takes_value)) {
if (!(arg.isSettingApplied(.takes_value))) {
if (short_option.hasValue()) {
return Error.UnneededAttachedValue;
} else if (short_option.hasEmptyValue()) {
Expand Down Expand Up @@ -256,7 +256,7 @@ fn parseLongOption(self: *Parser, token: *const Token) InternalError!void {
};
self.err_builder.setArg(arg);

if (!(arg.settings.takes_value)) {
if (!(arg.isSettingApplied(.takes_value))) {
if (option_tuple[1] != null) {
return Error.UnneededAttachedValue;
} else {
Expand Down Expand Up @@ -356,7 +356,7 @@ fn processValue(
const max_eqls_one = (has_max_num and (arg.max_values.? == 1));

// If maximum number and takes_multiple_values is not set we are not looking for more values
if ((!has_max_num or max_eqls_one) and !(arg.settings.takes_multiple_values)) {
if ((!has_max_num or max_eqls_one) and !(arg.isSettingApplied(.takes_multiple_values))) {
// If values contains only one value, we can be sure that the minimum number of values is set to 1
// therefore return it as a single value instead
if (values.items.len == 1) {
Expand All @@ -366,7 +366,7 @@ fn processValue(
return self.args_ctx.putMatchedArg(arg, .{ .many = values });
}
}
if (arg.settings.takes_multiple_values) {
if (arg.isSettingApplied(.takes_multiple_values)) {
if (!has_max_num) {
try self.consumeValuesTillNextOption(arg, &values);
return self.args_ctx.putMatchedArg(arg, .{ .many = values });
Expand Down Expand Up @@ -410,7 +410,7 @@ fn verifyAndAppendValue(
) InternalError!void {
self.err_builder.setProvidedArg(value);

if ((value.len == 0) and !(arg.settings.allow_empty_value))
if ((value.len == 0) and !(arg.isSettingApplied(.allow_empty_value)))
return InternalError.EmptyArgValueNotAllowed;
if (!(arg.verifyValueInAllowedValues(value)))
return InternalError.ProvidedValueIsNotValidOption;
Expand All @@ -425,14 +425,14 @@ fn parseSubCommand(self: *Parser, provided_subcmd: []const u8) Error!MatchedSubC
};

// zig fmt: off
if (valid_subcmd.setting.takes_value
if (valid_subcmd.isSettingApplied(.takes_value)
or valid_subcmd.countArgs() >= 1
or valid_subcmd.countOptions() >= 1
or valid_subcmd.countSubcommands() >= 1) {
// zig fmt: on
const help = valid_subcmd.help();
const subcmd_argv = self.tokenizer.restArg() orelse {
if (!(valid_subcmd.setting.arg_required)) {
if (!(valid_subcmd.isSettingApplied(.arg_required))) {
return MatchedSubCommand.initWithArg(
valid_subcmd.name,
ArgsContext.init(self.allocator),
Expand Down
Loading

0 comments on commit 59e9c24

Please sign in to comment.