Skip to content

Commit

Permalink
Merge branch 'main' into 0.9
Browse files Browse the repository at this point in the history
  • Loading branch information
sam701 committed Jan 13, 2024
2 parents dfc2f01 + 50201f0 commit 4392088
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 61 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
- run: zig fmt --check *.zig src/*.zig
- run: zig build
- run: zig build test
- run: cd examples/standalone && zig build
validate_and_test_windows:
runs-on: windows-latest
steps:
Expand All @@ -27,3 +28,4 @@ jobs:
version: master
- run: zig build
- run: zig build test
- run: cd examples/standalone && zig build
20 changes: 14 additions & 6 deletions examples/short.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");
const cli = @import("zig-cli");

// Define a configuration structure with default values.
var config = struct {
host: []const u8 = "localhost",
port: u16 = undefined,
Expand All @@ -9,21 +10,26 @@ var config = struct {
pub fn main() !void {
var r = try cli.AppRunner.init(std.heap.page_allocator);

// Create an App with a command named "short" that takes host and port options.
const app = cli.App{
.command = cli.Command{
.name = "short",
.options = &.{
// Define an Option for the "host" command-line argument.
.{
.long_name = "host",
.help = "host to listen on",
.value_ref = r.mkRef(&config.host),
},

// Define an Option for the "port" command-line argument.
.{
.long_name = "port",
.help = "port to bind to",
.required = true,
.value_ref = r.mkRef(&config.port),
},
.{
.long_name = "host",
.help = "host to listen on",
.value_ref = r.mkRef(&config.host),
},

},
.target = cli.CommandTarget{
.action = cli.CommandAction{ .exec = run_server },
Expand All @@ -33,6 +39,8 @@ pub fn main() !void {
return r.run(&app);
}

// Action function to execute when the "short" command is invoked.
fn run_server() !void {
std.log.debug("server is listening on {s}:{}", .{ config.host, config.port });
// Log a debug message indicating the server is listening on the specified host and port.
std.log.debug("server is listening on {s}:{d}", .{ config.host, config.port });
}
2 changes: 1 addition & 1 deletion examples/standalone/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn build(b: *std.Build) void {
.target = target,
.optimize = optimize,
});
exe.addModule("zig-cli", zigcli_mod);
exe.root_module.addImport("zig-cli", zigcli_mod);

// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
Expand Down
4 changes: 2 additions & 2 deletions examples/standalone/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
.dependencies = .{
.@"zig-cli" = .{
// URL pattern: https://github.com/sam701/zig-cli/archive/<commit hash>.tar.gz
.url = "https://github.com/sam701/zig-cli/archive/ddf49596a9225c91e6cd9c28904aec598d5becf0.tar.gz",
.hash = "1220787d26a153a3755d55c8b65af53f7b1c4d79add4a47e2898dc776f3a16f1ce89",
.url = "https://github.com/sam701/zig-cli/archive/aa3de8e548a3d68c63e299dae3a97bc0a669dfa3.tar.gz",
.hash = "1220b1cc7c256080eb3229d95a8519d6a33035dc83558a8ca1ed6e407008a38fe2c2",
},
},
}
32 changes: 18 additions & 14 deletions src/arg.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,38 @@ pub const ArgumentInterpretation = union(enum) {
};

pub fn interpret(arg: []const u8) error{MissingOptionValue}!ArgumentInterpretation {
if (arg.len == 0) return ArgumentInterpretation{ .other = arg };
if (arg.len == 0) return .{ .other = arg };

if (arg[0] == '-') {
if (arg.len == 1) return ArgumentInterpretation{ .other = arg };
if (arg.len == 1) return .{ .other = arg };

var name = arg[1..];
var option_type = OptionType.short;
var option_type: OptionType = .short;
if (arg[1] == '-') {
if (arg.len == 2) return ArgumentInterpretation.double_dash;
if (arg.len == 2) return .double_dash;
name = arg[2..];
option_type = .long;
}

if (std.mem.indexOfScalar(u8, name, '=')) |ix| {
if (name.len < ix + 2) return error.MissingOptionValue;
return ArgumentInterpretation{ .option = OptionInterpretation{
.option_type = option_type,
.name = name[0..ix],
.value = name[ix + 1 ..],
} };
return .{
.option = .{
.option_type = option_type,
.name = name[0..ix],
.value = name[ix + 1 ..],
},
};
} else {
return ArgumentInterpretation{ .option = OptionInterpretation{
.option_type = option_type,
.name = name,
} };
return .{
.option = .{
.option_type = option_type,
.name = name,
},
};
}
} else {
return ArgumentInterpretation{ .other = arg };
return .{ .other = arg };
}
}

Expand Down
48 changes: 44 additions & 4 deletions src/command.zig
Original file line number Diff line number Diff line change
@@ -1,73 +1,113 @@
const std = @import("std");
pub const ValueRef = @import("./value_ref.zig").ValueRef;

/// Main structure for the application.
pub const App = struct {
/// Main command configuration.
command: Command,
/// Optional version information.
version: ?[]const u8 = null,
/// Optional author information.
author: ?[]const u8 = 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.
/// 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 display configuration.
help_config: HelpConfig = HelpConfig{},
};

/// Enumeration for color usage in help display.
pub const ColorUsage = enum {
always,
never,
auto,
};

/// Configuration for help display.
pub const HelpConfig = struct {
/// Color usage setting.
color_usage: ColorUsage = .auto,
/// Color for the application name in help.
color_app_name: []const u8 = "33;1",
/// Color for section headers in help.
color_section: []const u8 = "33;1",
/// Color for option names in help.
color_option: []const u8 = "32",
/// Color for error messages in help.
color_error: []const u8 = "31;1",
};

/// Structure representing a command.
pub const Command = struct {
/// Name of the command.
name: []const u8,
/// Description of the command.
description: ?Description = null,
/// List of options for the command.
options: ?[]const Option = null,
/// Target of the command (subcommands or action).
target: CommandTarget,
};

/// Structure representing a description.
pub const Description = struct {
/// One-line description.
one_line: []const u8,
/// Detailed description (optional).
detailed: ?[]const u8 = null,
};

/// Union for different command targets.
pub const CommandTarget = union(enum) {
/// Subcommands of the command.
subcommands: []const Command,
/// Action to execute for the command.
action: CommandAction,
};

/// Structure representing a command action.
pub const CommandAction = struct {
/// Positional arguments for the action.
positional_args: ?PositionalArgs = null,
/// Function to execute for the action.
exec: ExecFn,
};

/// Function pointer type for command execution.
pub const ExecFn = *const fn () anyerror!void;

/// Structure representing an option.
pub const Option = struct {
/// Long name of the option.
long_name: []const u8,
/// Short alias for the option.
short_alias: ?u8 = null,
/// Help description for the option.
help: []const u8,
/// Whether the option is required or not.
required: bool = false,
/// Reference to the value of the option.
value_ref: *ValueRef,
/// Name of the value for the option.
value_name: []const u8 = "VALUE",
/// Environment variable name for the option.
envvar: ?[]const u8 = null,
};

/// Structure representing positional arguments for an action.
pub const PositionalArgs = struct {
/// Required positional arguments.
required: ?[]const PositionalArg = null,
/// Optional positional arguments. There always follow the required ones.
optional: ?[]const PositionalArg = null,
};

/// Structure representing a positional argument.
pub const PositionalArg = struct {
/// Name of the positional argument.
name: []const u8,
/// Help description for the positional argument.
help: ?[]const u8 = null,
/// Reference to the value of the positional argument.
value_ref: *ValueRef,
};
34 changes: 15 additions & 19 deletions src/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub fn Parser(comptime Iterator: type) type {
error_data: ?ErrorData = null,

pub fn init(app: *const command.App, it: Iterator, alloc: Allocator) !Self {
return Self{
return .{
.alloc = alloc,
.arg_iterator = it,
.app = app,
Expand Down Expand Up @@ -166,7 +166,7 @@ pub fn Parser(comptime Iterator: type) type {
};
return err;
};
if (posArgRef.value_type == vref.ValueType.single) {
if (posArgRef.value_type == .single) {
self.position_argument_ix += 1;
}
} else {
Expand Down Expand Up @@ -199,11 +199,7 @@ pub fn Parser(comptime Iterator: type) type {
defer self.alloc.free(envvar_name);
@memcpy(envvar_name[0..prefix.len], prefix);
for (envvar_name[prefix.len..], opt.long_name) |*dest, name_char| {
if (name_char == '-') {
dest.* = '_';
} else {
dest.* = std.ascii.toUpper(name_char);
}
dest.* = if (name_char == '-') '_' else std.ascii.toUpper(name_char);
}

if (std.process.getEnvVarOwned(self.alloc, envvar_name)) |value| {
Expand All @@ -226,12 +222,9 @@ pub fn Parser(comptime Iterator: type) type {
var args_only = false;
try switch (int.*) {
.option => |opt| self.process_option(&opt),
.double_dash => {
args_only = true;
},
.double_dash => args_only = true,
.other => |some_name| {
const cmd = self.current_command();
switch (cmd.target) {
switch (self.current_command().target) {
.subcommands => |cmds| {
for (cmds) |*sc| {
if (std.mem.eql(u8, sc.name, some_name)) {
Expand All @@ -242,9 +235,7 @@ pub fn Parser(comptime Iterator: type) type {
self.error_data = ErrorData{ .provided_string = some_name };
return error.UnknownSubcommand;
},
.action => {
try self.handlePositionalArgument(some_name);
},
.action => try self.handlePositionalArgument(some_name),
}
},
};
Expand All @@ -268,8 +259,14 @@ pub fn Parser(comptime Iterator: type) type {
var opt: *const command.Option = switch (option_interpretation.option_type) {
.long => try self.find_option_by_name(option_interpretation.name),
.short => a: {
try self.set_concatenated_boolean_options(self.current_command(), option_interpretation.name[0 .. option_interpretation.name.len - 1]);
break :a try self.find_option_by_alias(self.current_command(), option_interpretation.name[option_interpretation.name.len - 1]);
try self.set_concatenated_boolean_options(
self.current_command(),
option_interpretation.name[0 .. option_interpretation.name.len - 1],
);
break :a try self.find_option_by_alias(
self.current_command(),
option_interpretation.name[option_interpretation.name.len - 1],
);
},
};

Expand All @@ -288,8 +285,7 @@ pub fn Parser(comptime Iterator: type) type {
return;
}

const following_arg = self.nextArg();
if (following_arg) |arg| {
if (self.nextArg()) |arg| {
if (arg.len > 0 and arg[0] != '-') {
var lw = try self.alloc.alloc(u8, arg.len);
defer self.alloc.free(lw);
Expand Down
9 changes: 2 additions & 7 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ const StringSliceIterator = struct {

pub fn next(self: *StringSliceIterator) ?[]const u8 {
defer self.index += 1;

if (self.index < self.items.len) {
return self.items[self.index];
} else {
return null;
}
return if (self.index < self.items.len) self.items[self.index] else null;
}
};

Expand All @@ -45,7 +40,7 @@ fn dummy_action() !void {}
fn runOptionsPArgs(input: []const []const u8, options: []const command.Option, pargs: ?[]const command.PositionalArg) !void {
const pa = if (pargs) |p| command.PositionalArgs{ .required = p } else null;
const app = command.App{
.command = command.Command{
.command = .{
.name = "cmd",
.description = command.Description{ .one_line = "short help" },
.options = options,
Expand Down
Loading

0 comments on commit 4392088

Please sign in to comment.