Skip to content

Commit

Permalink
Enable take option values from environment variables.
Browse files Browse the repository at this point in the history
Closes #12
  • Loading branch information
sam701 committed Oct 14, 2023
1 parent 8e686c6 commit 24651ab
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Inspired by [urfave/cli](https://github.com/urfave/cli) Go package.
* concatenated short options: `-a -b -c` equals `-abc`
* subcommands: `command1 -option1 subcommand2 -option2`
* multiple option values: `--opt val1 --opt val2 --opt val3`
* options value can be read from environment variables with a configured prefix
* positional arguments can be mixed with options: `--opt1 val1 arg1 -v`
* stops option parsing after `--`: `command -- --abc` will consider `--abc` as a positional argument to `command`.
* errors on missing required options: `ERROR: option 'ip' is required`
Expand Down
5 changes: 5 additions & 0 deletions src/command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ pub const App = struct {
subcommands: ?[]const *const Command = null,
action: ?Action = null,

/// If set all options can be set by providing an environment variable.
/// For example an option with a long name `hello_world` can be set by setting `<prefix in upper case>_HELLO_WORLD` environment variable.
option_envvar_prefix: ?[]const u8 = null,

help_config: HelpConfig = HelpConfig{},
};

Expand Down Expand Up @@ -48,4 +52,5 @@ pub const Option = struct {
required: bool = false,
value_ref: ValueRef,
value_name: []const u8 = "VALUE",
envvar: ?[]const u8 = null,
};
39 changes: 36 additions & 3 deletions src/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,18 @@ pub fn Parser(comptime Iterator: type) type {
}

fn finalize(self: *Self) !ParseResult {
self.ensure_all_required_set(self.current_command());
var args = try self.captured_arguments.toOwnedSlice();

for (self.command_path.items) |cmd| {
if (cmd.options) |options| {
for (options) |opt| {
try self.set_option_value_from_envvar(opt);
try opt.value_ref.finalize(self.alloc);
}
}
}

self.ensure_all_required_set(self.current_command());
var args = try self.captured_arguments.toOwnedSlice();

if (self.current_command().action) |action| {
return ParseResult{ .action = action, .args = args };
} else {
Expand All @@ -109,6 +110,38 @@ pub fn Parser(comptime Iterator: type) type {
}
}

fn set_option_value_from_envvar(self: *const Self, opt: *command.Option) !void {
if (opt.value_ref.element_count > 0) return;

if (opt.envvar) |envvar_name| {
if (std.os.getenv(envvar_name)) |value| {
opt.value_ref.put(value, self.alloc) catch |err| {
self.fail("envvar({s}): cannot parse {s} value '{s}': {s}", .{ envvar_name, opt.value_ref.value_data.type_name, value, @errorName(err) });
unreachable;
};
}
} else if (self.app.option_envvar_prefix) |prefix| {
var envvar_name = try self.alloc.alloc(u8, opt.long_name.len + prefix.len + 1);
defer self.alloc.free(envvar_name);
@memcpy(envvar_name[0..prefix.len], prefix);
envvar_name[prefix.len] = '_';
for (envvar_name[prefix.len + 1 ..], opt.long_name) |*dest, name_char| {
if (name_char == '-') {
dest.* = '_';
} else {
dest.* = std.ascii.toUpper(name_char);
}
}

if (std.os.getenv(envvar_name)) |value| {
opt.value_ref.put(value, self.alloc) catch |err| {
self.fail("envvar({s}): cannot parse {s} value '{s}': {s}", .{ envvar_name, opt.value_ref.value_data.type_name, value, @errorName(err) });
unreachable;
};
}
}
}

fn process_interpretation(self: *Self, int: *const argp.ArgumentInterpretation) !bool {
var args_only = false;
try switch (int.*) {
Expand Down

0 comments on commit 24651ab

Please sign in to comment.