Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ env:
jobs:
build-test:
strategy:
fail-fast: false
matrix:
target:
[
Expand Down Expand Up @@ -58,6 +59,7 @@ jobs:
run: zig build test -Dtarget=${{ matrix.target }}

- name: Generate filename
if: always()
shell: bash
run: |
REPO_NAME=$(echo "${{ github.repository }}" | awk -F/ '{print $2}')
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ For arguments, T must be the smallest parsable unit: `[]const u8` -> T
- Only supports base types of `.int`, `.float`, and `.bool`
- `@Vector{1,1}`: `[\(\[\{][ ]*1[ ]*[;:,][ ]*1[ ]*[\)\]\}]`
- `@Vector{true,false}`: `[\(\[\{][ ]*y[ ]*[;:,][ ]*no[ ]*[\)\]\}]`
- `std.fs.File/Dir`

If type T has no associated default parser or `parse` method, you can specify a custom parser (`.parseFn`) for the parameter. Obviously, single-option parameters cannot have parsers as it would be meaningless.

Expand All @@ -212,6 +213,8 @@ Options and arguments can be configured with default values (`.default`). Once c

> Single options, options with a single argument of optional type, or single positional arguments of optional type are always optional.

Default values must be determined at comptime. For `argOpt`, if the value cannot be determined at comptime (e.g., `std.fs.cwd()` at `Windows`), you can configure the default input (`.rawDefault`), which will determine the default value in the perser.

#### Value Ranges

Value ranges (`.ranges`, `.choices`) can be configured for arguments, which are validated after parsing.
Expand Down
3 changes: 3 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ pub fn getString(self: Self) *const [stringify(self, "fname").count():0]u8 {
- 仅支持基类型为 `.int`, `.float` 和 `.bool` 的
- `@Vector{1,1}`:`[\(\[\{][ ]*1[ ]*[;:,][ ]*1[ ]*[\)\]\}]`
- `@Vector{true,false}`:`[\(\[\{][ ]*y[ ]*[;:,][ ]*no[ ]*[\)\]\}]`
- `std.fs.File/Dir`

如果 T 不存在相关联的默认解析器或`parse`方法,可以为参数自定义解析器(`.parseFn`)。显然,无法为单选项配置解析器,因为这是无意义的。

Expand All @@ -211,6 +212,8 @@ pub fn getString(self: Self) *const [stringify(self, "fname").count():0]u8 {

> 单选项、具有可选类型的带单参数选项或具有可选类型的单位置参数,总是可选的。

默认值需要在编译期确定。对于带参数选项(`argOpt`),如果无法在编译期确定值(比如在`Windows`上的`std.fs.cwd()`),那么可以配置默认输入(`.rawDefault`),这将在解析器中完成默认值的确定。

#### 取值范围

可以为参数配置值取值范围(`.ranges`, `.choices`),这将在解析后执行有效性检查。
Expand Down
144 changes: 80 additions & 64 deletions src/command/AFormatter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ pub fn usage(self: Self, w: anytype) !void {
const meta = self.arg.meta;
var is_first = true;

if (meta.default != null) {
try w.print("{}[", .{sRec.apply(sConfig.usage.optional)});
if (meta.default != null or meta.rawDefault != null or ztype.Type.isSlice(self.arg.T)) {
try w.print("{}", .{sRec.apply(sConfig.usage.optional)});
if (!ztype.Type.isSlice(self.arg.T)) {
try w.writeByte('[');
}
}
if ((meta.short.len > 0 or meta.long.len > 0) and meta.argName != null) {
try w.print("{}", .{sRec.apply(sConfig.usage.optarg)});
Expand All @@ -56,7 +59,7 @@ pub fn usage(self: Self, w: anytype) !void {
if (meta.argName) |s| {
if (!is_first) try w.writeByte(' ');
try w.print("{}", .{sRec.apply(sConfig.usage.argument)});
if (!is_first or meta.default == null) {
if (!is_first or (meta.default == null and meta.rawDefault == null)) {
try w.writeByte('{');
}
switch (@typeInfo(self.arg.T)) {
Expand All @@ -65,12 +68,15 @@ pub fn usage(self: Self, w: anytype) !void {
else => {},
}
try w.writeAll(s);
if (!is_first or meta.default == null) {
if (!is_first or (meta.default == null and meta.rawDefault == null)) {
try w.writeByte('}');
}
}
if (meta.default != null) {
try w.print("{}]", .{sRec.restore(sConfig.usage.optional)});
if (meta.default != null or meta.rawDefault != null or ztype.Type.isSlice(self.arg.T)) {
try w.print("{}", .{sRec.restore(sConfig.usage.optional)});
if (!ztype.Type.isSlice(self.arg.T)) {
try w.writeByte(']');
}
}
try w.print("{}", .{sRec.reset()});
if (self.arg.class == .opt and self.arg.T != bool or self.arg.class == .optArg and ztype.Type.isSlice(self.arg.T)) {
Expand Down Expand Up @@ -137,17 +143,18 @@ pub fn help(self: Self, w: anytype) !void {
try w.writeAll(" " ** fConfig.indent);
try self.usage1(w);

if (meta.help != null or meta.default != null) {
if (meta.help != null or meta.default != null or meta.rawDefault != null) {
try self.indent(w, &is_firstline);
if (meta.help) |s| {
try w.writeAll(s);
}
if (meta.default) |_| {
if (meta.default != null or meta.rawDefault != null) {
if (meta.help != null) try w.writeByte(' ');
try w.print("{}(default is {}{}{}){}", .{
try w.print("{}({s} is {}{}{}){}", .{
sRec.apply(sConfig.help.default),
if (meta.default != null) "default" else "default input",
sRec.apply(sConfig.help.defaultValue),
any(self.arg._toField().defaultValue().?, .{}),
any(if (meta.default != null) self.arg._toField().defaultValue().? else meta.rawDefault.?, .{}),
sRec.restore(sConfig.help.default),
sRec.reset(),
});
Expand All @@ -156,7 +163,7 @@ pub fn help(self: Self, w: anytype) !void {

if (meta.ranges != null or meta.choices != null) {
try self.indent(w, &is_firstline);
try w.print("{}possible values: {}", .{
try w.print("{}possible: {}", .{
sRec.apply(sConfig.help.possible),
sRec.apply(sConfig.help.possibleValue),
});
Expand Down Expand Up @@ -210,11 +217,11 @@ test "usageString" {
try testing.expectEqualStrings("[OUT]", Arg.posArg("out", u32).default(1)._checkOut().usageString(.{}));
}

test "helpString" {
test "helpString 0" {
try testing.expectEqualStrings(
\\ -c, -n, --count, --cnt {COUNT}
\\ This is a help message, with a very very long long long sentence (default is 1)
\\ possible values: [-16,3) or [16,∞) or { 5, 6 }
\\ possible: [-16,3) or [16,∞) or { 5, 6 }
\\
, Arg.optArg("count", i32)
.short('c').short('n')
Expand All @@ -224,34 +231,36 @@ test "helpString" {
.choices(&.{ 5, 6 })
.default(1)
._checkOut().helpString(.{}));

}
test "helpString 1" {
try testing.expectEqualStrings(
\\ -c, -n {COUNT} This is a help message, with a very very long long long sentence
\\ possible values: { 5, 6 }
\\ possible: { 5, 6 }
\\
, Arg.optArg("count", i32)
.short('c').short('n')
.help("This is a help message, with a very very long long long sentence")
.choices(&.{ 5, 6 })
._checkOut().helpString(.{}));

}
test "helpString 2" {
try testing.expectEqualStrings(
\\ -c, -n {COUNT} possible inputs: { 0x05, 0x06 }
\\
, Arg.optArg("count", i32)
.short('c').short('n')
.rawChoices(&.{ "0x05", "0x06" })
._checkOut().helpString(.{}));

{
const Color = enum { Red, Green, Blue };
try testing.expectEqualStrings(
\\ {COLOR} Enum: { Red, Green, Blue }
\\
, Arg.posArg("color", Color)
._checkOut().helpString(.{}));
}

}
test "helpString 3" {
const Color = enum { Red, Green, Blue };
try testing.expectEqualStrings(
\\ {COLOR} Enum: { Red, Green, Blue }
\\
, Arg.posArg("color", Color)
._checkOut().helpString(.{}));
}
test "helpString 4" {
try testing.expectEqualStrings(
\\ -o, -u, -t, --out, --output
\\ Help of out (default is false)
Expand All @@ -260,14 +269,16 @@ test "helpString" {
.short('o').short('u').short('t')
.long("out").long("output").help("Help of out")
._checkOut().helpString(.{}));

}
test "helpString 5" {
try testing.expectEqualStrings(
\\ -o {OUT} Help of out
\\
, Arg.optArg("out", String)
.short('o').help("Help of out")
._checkOut().helpString(.{}));

}
test "helpString 6" {
try testing.expectEqualStrings(
\\ -o, --out, --output {OUT}
\\ Help of out (default is a.out)
Expand All @@ -277,54 +288,59 @@ test "helpString" {
.default("a.out")
.help("Help of out")
._checkOut().helpString(.{}));

}
test "helpString 7" {
try testing.expectEqualStrings(
\\ -p, --point {POINT} (default is { 1, 1 })
\\
, Arg.optArg("point", @Vector(2, i32))
.short('p').long("point").default(.{ 1, 1 })
._checkOut().helpString(.{}));

{
const Color = enum { Red, Green, Blue };
try testing.expectEqualStrings(
\\ -c, --color {[3]COLORS} Help of colors (default is { Red, Green, Blue })
\\ Enum: { Red, Green, Blue }
\\
, Arg.optArg("colors", [3]Color)
.short('c').long("color")
.default(.{ .Red, .Green, .Blue })
.help("Help of colors")
._checkOut().helpString(.{}));
}

}
test "helpString 8" {
const Color = enum { Red, Green, Blue };
try testing.expectEqualStrings(
\\ -c, --color {[3]COLORS} Help of colors (default is { Red, Green, Blue })
\\ Enum: { Red, Green, Blue }
\\
, Arg.optArg("colors", [3]Color)
.short('c').long("color")
.default(.{ .Red, .Green, .Blue })
.help("Help of colors")
._checkOut().helpString(.{}));
}
test "helpString 9" {
try testing.expectEqualStrings(
\\ --u32 {U32} (default input is 3)
\\
, Arg.optArg("u32", u32).long("u32")
.rawDefault("3")
._checkOut().helpString(.{}));
}
test "helpString a" {
try testing.expectEqualStrings(
\\ {U32} (default is 3)
\\ possible values: [5,10) or [32,∞) or { 15, 29 }
\\ possible: [5,10) or [32,∞) or { 15, 29 }
\\
,
Arg.posArg("u32", u32)
.default(3)
.ranges(Ranges(u32).new().u(5, 10).u(32, null))
.choices(&.{ 15, 29 })
._checkOut().helpString(.{}),
);

, Arg.posArg("u32", u32)
.default(3)
.ranges(Ranges(u32).new().u(5, 10).u(32, null))
.choices(&.{ 15, 29 })
._checkOut().helpString(.{}));
}
test "helpString b" {
try testing.expectEqualStrings(
\\ {CC} possible values: { gcc, clang }
\\ {CC} possible: { gcc, clang }
\\
,
Arg.posArg("cc", String)
.choices(&.{ "gcc", "clang" })
._checkOut().helpString(.{}),
);

, Arg.posArg("cc", String)
.choices(&.{ "gcc", "clang" })
._checkOut().helpString(.{}));
}
test "helpString c" {
try testing.expectEqualStrings(
\\ {CC} possible inputs: { gcc, clang }
\\
,
Arg.posArg("cc", String)
.rawChoices(&.{ "gcc", "clang" })
._checkOut().helpString(.{}),
);
, Arg.posArg("cc", String)
.rawChoices(&.{ "gcc", "clang" })
._checkOut().helpString(.{}));
}
Loading
Loading