Skip to content

Commit

Permalink
Compiles with value refs
Browse files Browse the repository at this point in the history
  • Loading branch information
sam701 committed Oct 12, 2023
1 parent 07b458f commit e98d359
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 142 deletions.
12 changes: 7 additions & 5 deletions example/short.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ const cli = @import("zig-cli");
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();

var config = struct {
host: []const u8 = "localhost",
port: u16 = undefined,
}{};
var host = cli.Option{
.long_name = "host",
.help = "host to listen on",
.value = cli.OptionValue{ .string = "localhost" },
.value_ref = cli.mkRef(&config.host),
};
var port = cli.Option{
.long_name = "port",
.help = "port to bind to",
.required = true,
.value = cli.OptionValue{ .int = null },
.value_ref = cli.mkRef(&config.port),
};
var app = &cli.App{
.name = "short",
Expand All @@ -26,7 +30,5 @@ pub fn main() !void {
}

fn run_server(_: []const []const u8) !void {
var h = host.value.string.?;
var p = port.value.int.?;
std.log.debug("server is listening on {s}:{}", .{ h, p });
std.log.debug("server is listening on {s}:{}", .{ config.host, config.port });
}
82 changes: 34 additions & 48 deletions example/simple.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,43 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();

var config = struct {
host: []const u8 = "localhost",
port: u16 = 3000,
base_value: f64 = undefined,
expose_metrics: bool = false,
testar: []i32 = undefined,
ip: []const u8 = undefined,
int: i32 = undefined,
bool: bool = false,
float: f64 = 0.34,
name: []const u8 = undefined,
}{};

var host_option = cli.Option{
.long_name = "host",
.help = "host to listen on",
.short_alias = 'h',
.value = cli.OptionValue{ .string = null },
.value_ref = cli.valueRef(&config.host),
var ip_option = cli.Option{
.long_name = "ip",
.help = "this is the IP address",
.short_alias = 'i',
.value_ref = cli.mkRef(&config.ip),
.required = true,
.value_name = "IP",
};
var port_option = cli.Option{
.long_name = "port",
.help = "post to bind to",
.value = cli.OptionValue{ .int = null },
.value_ref = cli.valueRef(&config.port),
var int_option = cli.Option{
.long_name = "int",
.help = "this is an int",
.value_ref = cli.mkRef(&config.int),
};
var ar_option = cli.Option{
.long_name = "array",
.help = "slice test",
.value = cli.OptionValue{ .int = null },
.value_ref = cli.valueRef(&config.testar),
var bool_option = cli.Option{
.long_name = "bool",
.short_alias = 'b',
.help = "this is a bool",
.value_ref = cli.mkRef(&config.bool),
};
var expose_metrics_option = cli.Option{
.long_name = "expose-metrics",
.short_alias = 'm',
.help = "if the metrics should be exposed",
.value = cli.OptionValue{ .bool = false },
};
var base_value_option = cli.Option{
.long_name = "base-value",
.help = "base value",
.value = cli.OptionValue{ .float = 0.34 },
var float_option = cli.Option{
.long_name = "float",
.help = "this is a float",
.value_ref = cli.mkRef(&config.float),
};

var name_option = cli.Option{
.long_name = "long_name",
.help = "long_name help",
.value_ref = cli.mkRef(&config.name),
};
var app = &cli.App{
.name = "simple",
.description = "This a simple CLI app\nEnjoy!",
Expand All @@ -59,10 +56,10 @@ var app = &cli.App{
\\And this is line 3.
,
.options = &.{
&host_option,
&port_option,
&expose_metrics_option,
&base_value_option,
&ip_option,
&int_option,
&bool_option,
&float_option,
},
.subcommands = &.{
&cli.Command{
Expand All @@ -75,21 +72,10 @@ var app = &cli.App{
};

pub fn main() anyerror!void {
try port_option.value_ref.?.put("356", allocator);
std.log.debug("port value = {}", .{config.port});

try host_option.value_ref.?.put("awesome.com", allocator);
std.log.debug("host value = {s}", .{config.host});

try ar_option.value_ref.?.put("45", allocator);
try ar_option.value_ref.?.put("94", allocator);
try ar_option.value_ref.?.put("23456789", allocator);
try ar_option.value_ref.?.finalize(allocator);
std.log.debug("testar value = {any}", .{config.testar});
return cli.run(app, allocator);
}

fn run_sub2(args: []const []const u8) anyerror!void {
var ip = host_option.value.string.?;
std.log.debug("running sub2: ip={s}, bool={any}, float={any} arg_count={any}", .{ ip, expose_metrics_option.value.bool, base_value_option.value.float, args.len });
const c = &config;
std.log.debug("running sub2: ip={s}, bool={any}, float={any} arg_count={any}", .{ c.ip, c.bool, c.float, args.len });
}
12 changes: 1 addition & 11 deletions src/command.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const std = @import("std");
const vref = @import("./value_ref.zig");
pub const ValueRef = vref.ValueRef;
pub const valueRef = vref.valueRef;

pub const App = struct {
name: []const u8,
Expand Down Expand Up @@ -42,20 +41,11 @@ pub const Command = struct {

pub const Action = *const fn (args: []const []const u8) anyerror!void;

pub const OptionValue = union(enum) {
bool: bool,
string: ?[]const u8,
int: ?i64,
float: ?f64,
string_list: ?[]const []const u8,
};

pub const Option = struct {
long_name: []const u8,
short_alias: ?u8 = null,
help: []const u8,
required: bool = false,
value: OptionValue,
value_ref: ?ValueRef = null, // TODO: remove ?
value_ref: ValueRef,
value_name: []const u8 = "VALUE",
};
2 changes: 1 addition & 1 deletion src/help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const HelpPrinter = struct {
self.printer.format("--{s}", .{option.long_name});
self.printer.printColor(color_clear);
var width = option.long_name.len;
if (option.value != .bool) {
if (!option.value_ref.value_data.is_bool) {
self.printer.printColor(self.help_config.color_option);
self.printer.format(" <{s}>", .{option.value_name});
self.printer.printColor(color_clear);
Expand Down
1 change: 1 addition & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub usingnamespace @import("./command.zig");
const parser = @import("./parser.zig");

pub const mkRef = @import("./value_ref.zig").mkRef;
pub const run = parser.run;
106 changes: 32 additions & 74 deletions src/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const command = @import("command.zig");
const help = @import("./help.zig");
const argp = @import("./arg.zig");
const Printer = @import("./Printer.zig");
const mkRef = @import("./value_ref.zig").mkRef;

pub const ParseResult = struct {
action: command.Action,
Expand All @@ -22,16 +23,15 @@ pub fn run(app: *const command.App, alloc: Allocator) anyerror!void {
return result.action(result.args);
}

var help_option_set: bool = false;

var help_option = command.Option{
.long_name = "help",
.help = "Show this help output.",
.short_alias = 'h',
.value = command.OptionValue{ .bool = false },
.value_ref = mkRef(&help_option_set),
};

const ValueList = std.ArrayList([]const u8);
const ValueListMap = std.AutoHashMap(*command.Option, ValueList);

pub fn Parser(comptime Iterator: type) type {
return struct {
const Self = @This();
Expand All @@ -41,7 +41,6 @@ pub fn Parser(comptime Iterator: type) type {
app: *const command.App,
command_path: std.ArrayList(*const command.Command),
captured_arguments: std.ArrayList([]const u8),
value_lists: ?ValueListMap,

pub fn init(app: *const command.App, it: Iterator, alloc: Allocator) !Self {
return Self{
Expand All @@ -50,7 +49,6 @@ pub fn Parser(comptime Iterator: type) type {
.app = app,
.command_path = try std.ArrayList(*const command.Command).initCapacity(alloc, 16),
.captured_arguments = try std.ArrayList([]const u8).initCapacity(alloc, 16),
.value_lists = null,
};
}

Expand Down Expand Up @@ -95,13 +93,12 @@ pub fn Parser(comptime Iterator: type) type {
self.ensure_all_required_set(self.current_command());
var args = try self.captured_arguments.toOwnedSlice();

if (self.value_lists) |vl| {
var it = vl.iterator();
while (it.next()) |entry| {
var option: *command.Option = entry.key_ptr.*;
option.value.string_list = try entry.value_ptr.toOwnedSlice();
for (self.command_path.items) |cmd| {
if (cmd.options) |options| {
for (options) |opt| {
try opt.value_ref.finalize(self.alloc);
}
}
self.value_lists.?.deinit();
}

if (self.current_command().action) |action| {
Expand Down Expand Up @@ -136,12 +133,12 @@ pub fn Parser(comptime Iterator: type) type {
return self.arg_iterator.next();
}

fn process_option(self: *Self, option: *const argp.OptionInterpretation) !void {
var opt: *command.Option = switch (option.option_type) {
.long => self.find_option_by_name(self.current_command(), option.name),
fn process_option(self: *Self, option_interpretation: *const argp.OptionInterpretation) !void {
var opt: *command.Option = switch (option_interpretation.option_type) {
.long => self.find_option_by_name(self.current_command(), option_interpretation.name),
.short => a: {
self.set_boolean_options(self.current_command(), option.name[0 .. option.name.len - 1]);
break :a self.find_option_by_alias(self.current_command(), option.name[option.name.len - 1]);
self.set_concatenated_boolean_options(self.current_command(), option_interpretation.name[0 .. option_interpretation.name.len - 1]);
break :a self.find_option_by_alias(self.current_command(), option_interpretation.name[option_interpretation.name.len - 1]);
},
};

Expand All @@ -150,49 +147,18 @@ pub fn Parser(comptime Iterator: type) type {
std.os.exit(0);
}

switch (opt.value) {
.bool => opt.value = command.OptionValue{ .bool = true },
else => {
const arg = option.value orelse self.next_arg() orelse {
self.fail("missing argument for {s}", .{opt.long_name});
unreachable;
};
try self.parse_and_set_option_value(arg, opt);
},
}
}

fn parse_and_set_option_value(self: *Self, text: []const u8, option: *command.Option) !void {
switch (option.value) {
.bool => unreachable,
.string => option.value = command.OptionValue{ .string = text },
.int => {
if (std.fmt.parseInt(i64, text, 10)) |iv| {
option.value = command.OptionValue{ .int = iv };
} else |_| {
self.fail("option({s}): cannot parse int value", .{option.long_name});
unreachable;
}
},
.float => {
if (std.fmt.parseFloat(f64, text)) |fv| {
option.value = command.OptionValue{ .float = fv };
} else |_| {
self.fail("option({s}): cannot parse float value", .{option.long_name});
unreachable;
}
},
.string_list => {
if (self.value_lists == null) {
self.value_lists = ValueListMap.init(self.alloc);
}

var res = try self.value_lists.?.getOrPut(option);
if (!res.found_existing) {
res.value_ptr.* = try ValueList.initCapacity(self.alloc, 16);
}
try res.value_ptr.append(text);
},
if (opt.value_ref.value_data.is_bool) {
try opt.value_ref.put("true", self.alloc);
// TODO: bool argument can be explicitly passed as a value
} else {
const arg = option_interpretation.value orelse self.next_arg() orelse {
self.fail("missing argument for {s}", .{opt.long_name});
unreachable;
};
opt.value_ref.put(arg, self.alloc) catch |err| {
self.fail("option({s}): cannot parse {s} value: {s}", .{ opt.long_name, opt.value_ref.value_data.type_name, @errorName(err) });
unreachable;
};
}
}

Expand Down Expand Up @@ -250,11 +216,12 @@ pub fn Parser(comptime Iterator: type) type {
}
}

fn set_boolean_options(self: *const Self, cmd: *const command.Command, options: []const u8) void {
/// Set boolean options provided like `-acde`
fn set_concatenated_boolean_options(self: *const Self, cmd: *const command.Command, options: []const u8) void {
for (options) |alias| {
var opt = self.find_option_by_alias(cmd, alias);
if (opt.value == command.OptionValue.bool) {
opt.value.bool = true;
if (opt.value_ref.value_data.is_bool) {
opt.value_ref.put("true", self.alloc) catch unreachable;
} else {
self.fail("'-{c}' is not a boolean option", .{alias});
}
Expand All @@ -264,17 +231,8 @@ pub fn Parser(comptime Iterator: type) type {
fn ensure_all_required_set(self: *const Self, cmd: *const command.Command) void {
if (cmd.options) |list| {
for (list) |option| {
if (option.required) {
var not_set = switch (option.value) {
.bool => false,
.string => |x| x == null,
.int => |x| x == null,
.float => |x| x == null,
.string_list => |x| x == null,
};
if (not_set) {
self.fail("missing required option '{s}'", .{option.long_name});
}
if (option.required and option.value_ref.element_count == 0) {
self.fail("missing required option '{s}'", .{option.long_name});
}
}
}
Expand Down
Loading

0 comments on commit e98d359

Please sign in to comment.