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
Binary file modified .asset/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const Ranges = zargs.Ranges;
pub fn main() !void {
// Like Py3 argparse, https://docs.python.org/3.13/library/argparse.html
const remove = Command.new("remove")
.about("Remove something")
.alias("rm").alias("uninstall").alias("del")
.opt("verbose", u32, .{ .short = 'v' })
.optArg("count", u32, .{ .short = 'c', .argName = "CNT", .default = 9 })
Expand All @@ -35,6 +36,7 @@ pub fn main() !void {
.arg(Arg.opt("verbose", u32).short('v').help("help of verbose"))
.arg(Arg.optArg("logfile", ?[]const u8).long("log").help("Store log into a file"))
.sub(Command.new("install")
.about("Install something")
.arg(Arg.optArg("count", u32).default(10)
.short('c').short('n').short('t')
.long("count").long("cnt")
Expand All @@ -47,7 +49,7 @@ pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();

const args = cmd.parse(allocator) catch |e|
const args = cmd.config(.{ .style = .classic }).parse(allocator) catch |e|
zargs.exitf(e, 1, "\n{s}\n", .{cmd.usageString()});
defer cmd.destroy(&args, allocator);
if (args.logfile) |logfile| std.debug.print("Store log into {s}\n", .{logfile});
Expand Down Expand Up @@ -90,7 +92,7 @@ zig fetch --save https://github.com/kioz-wang/zargs/archive/refs/tags/v0.14.3.ta

> See https://github.com/kioz-wang/zargs/releases

The version number follows the format `vx.y.z`:
The version number follows the format `vx.y.z[-alpha.n]`:
- **x**: Currently fixed at 0. It will increment to 1 when the project stabilizes. Afterward, it will increment by 1 for any breaking changes.
- **y**: Represents the supported Zig version. For example, `vx.14.z` supports [Zig 0.14.0](https://github.com/ziglang/zig/releases/tag/0.14.0).
- **z**: Iteration version, indicating releases with new features or significant changes (see [milestones](https://github.com/kioz-wang/zargs/milestones)).
Expand Down Expand Up @@ -122,9 +124,9 @@ run_step.dependOn(&run_cmd.step);

After importing in your source code, you will gain access to the following features:

- Command and argument builders: Command, Arg
- Versatile iterator support: TokenIter
- Convenient exit functions: exit, exitf
- Command and argument builders: `Command`, `Arg`
- Versatile iterator support: `TokenIter`
- Convenient exit functions: `exit`, `exitf`

> See the [documentation](#APIs) for details.

Expand Down Expand Up @@ -217,7 +219,6 @@ Value ranges (`.ranges`, `.choices`) can be configured for arguments, which are
> Default values are not validated (intentional feature? 😄)

If constructing value ranges is cumbersome, `.rawChoices` can be used to filter values before parsing.
Ranges

##### Ranges

Expand All @@ -244,8 +245,6 @@ A command cannot have both positional arguments and subcommands simultaneously.

For the parser, except for accumulative options and options with a variable number of arguments, no option can appear more than once.

Various representations are primarily supported by the iterator.

Options are further divided into short options and long options:
- **Short Option**: `-v`
- **Long Option**: `--verbose`
Expand Down Expand Up @@ -339,13 +338,13 @@ Flexible for real and test scenarios
- Line iterator (`initLine`): same as regular iterator, but you can specify delimiters.
- List iterator (`initList`): iterates over a list of strings.

Short option prefixes (`-`), long option prefixes (`--`), connectors (`=`), option terminators (`--`) can be customized for iterators (see [presentation](#presentation) for usage scenarios).
Short option prefixes (`-`), long option prefixes (`--`), connectors (`=`), option terminators (`--`) can be customized for iterators (see [ex-05](examples/ex-05.custom_config.zig)).

### Compile-Time Usage and Help Generation

```zig
_ = cmd.usage();
_ = cmd.help();
_ = cmd.usageString();
_ = cmd.helpString();
```

## APIs
Expand Down
12 changes: 6 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const Ranges = zargs.Ranges;
pub fn main() !void {
// Like Py3 argparse, https://docs.python.org/3.13/library/argparse.html
const remove = Command.new("remove")
.about("Remove something")
.alias("rm").alias("uninstall").alias("del")
.opt("verbose", u32, .{ .short = 'v' })
.optArg("count", u32, .{ .short = 'c', .argName = "CNT", .default = 9 })
Expand All @@ -35,6 +36,7 @@ pub fn main() !void {
.arg(Arg.opt("verbose", u32).short('v').help("help of verbose"))
.arg(Arg.optArg("logfile", ?[]const u8).long("log").help("Store log into a file"))
.sub(Command.new("install")
.about("Install something")
.arg(Arg.optArg("count", u32).default(10)
.short('c').short('n').short('t')
.long("count").long("cnt")
Expand All @@ -47,7 +49,7 @@ pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();

const args = cmd.parse(allocator) catch |e|
const args = cmd.config(.{ .style = .classic }).parse(allocator) catch |e|
zargs.exitf(e, 1, "\n{s}\n", .{cmd.usageString()});
defer cmd.destroy(&args, allocator);
if (args.logfile) |logfile| std.debug.print("Store log into {s}\n", .{logfile});
Expand Down Expand Up @@ -241,8 +243,6 @@ pub fn getString(self: Self) *const [stringify(self, "fname").count():0]u8 {

对解析器来说,除了累加选项和带不定数量参数的选项,任何选项都不可以重复出现。

各种表现形式主要由迭代器负责支持。

选项又分为短选项和长选项:
- 短选项:`-v`
- 长选项:`--verbose`
Expand Down Expand Up @@ -336,13 +336,13 @@ defer cmd.destroy(&args, allocator);
- 行迭代器(`initLine`):和常规迭代器一样,但可以指定分隔符
- 列表迭代器(`initList`):从给定的字符串列表中迭代

可为迭代器自定义短选项前缀(`-`)、长选项前缀(`--`)、连接符(`=`)、选项终止符(`--`)(使用场景见[表现形式](#表现形式))。
可为迭代器自定义短选项前缀(`-`)、长选项前缀(`--`)、连接符(`=`)、选项终止符(`--`)(参考[ex-05](examples/ex-05.custom_config.zig))。

### 编译时生成 usage 和 help

```zig
_ = cmd.usage();
_ = cmd.help();
_ = cmd.usageString();
_ = cmd.helpString();
```

## APIs
Expand Down
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub fn build(b: *std.Build) void {
mod_command.addImport("par", mod_par);
mod_command.addImport("helper", mod_helper);
mod_command.addImport("iter", mod_iter);
mod_command.addImport("attr", b.dependency("zterm", .{}).module("attr"));

const mod_zargs = b.addModule("zargs", .{
.root_source_file = b.path("src/root.zig"),
Expand Down
8 changes: 7 additions & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
.{
.name = .zargs,
.fingerprint = 0x421ffecd6ed6acb6,
.version = "0.14.7",
.version = "0.14.8",
.dependencies = .{
.zterm = .{
.url = "git+https://github.com/kioz-wang/zterm#4de67bc6e443899cc1630aef504d75e7b7eed0fd",
.hash = "zterm-0.14.4-buNfZ4zbBgAJP9ajZYV64zuWXswQhauzgIzluM3c210q",
},
},
.minimum_zig_version = "0.14.0",

.paths = .{
Expand Down
4 changes: 3 additions & 1 deletion examples/ex-02.simple.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Ranges = zargs.Ranges;
pub fn main() !void {
// Like Py3 argparse, https://docs.python.org/3.13/library/argparse.html
const remove = Command.new("remove")
.about("Remove something")
.alias("rm").alias("uninstall").alias("del")
.opt("verbose", u32, .{ .short = 'v' })
.optArg("count", u32, .{ .short = 'c', .argName = "CNT", .default = 9 })
Expand All @@ -20,6 +21,7 @@ pub fn main() !void {
.arg(Arg.opt("verbose", u32).short('v').help("help of verbose"))
.arg(Arg.optArg("logfile", ?[]const u8).long("log").help("Store log into a file"))
.sub(Command.new("install")
.about("Install something")
.arg(Arg.optArg("count", u32).default(10)
.short('c').short('n').short('t')
.long("count").long("cnt")
Expand All @@ -32,7 +34,7 @@ pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();

const args = cmd.parse(allocator) catch |e|
const args = cmd.config(.{ .style = .classic }).parse(allocator) catch |e|
zargs.exitf(e, 1, "\n{s}\n", .{cmd.usageString()});
defer cmd.destroy(&args, allocator);
if (args.logfile) |logfile| std.debug.print("Store log into {s}\n", .{logfile});
Expand Down
3 changes: 2 additions & 1 deletion examples/ex-mess.zig
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ pub fn main() !void {
.author("kioz.wang@gmail.com")
.arg(Arg.opt("verbose", u8).short('v'))
.sub(sub0)
.sub(Command.new("sub1").about("This is an empty subCmd"));
.sub(Command.new("sub1").about("This is an empty subCmd"))
.config(.{ .style = .classic });

var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const allocator = gpa.allocator();
Expand Down
74 changes: 59 additions & 15 deletions src/command/AFormatter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,26 @@ pub fn init(arg: Arg, config: Config) Self {
.arg = arg,
.config = config,
};
var counting = std.io.countingWriter(std.io.null_writer);
try self.usage1(counting.writer());
self.left_length = counting.bytes_written;
self.left_length = blk: {
var pure = self;
pure.config.style = .none;
var counting = std.io.countingWriter(std.io.null_writer);
try pure.usage1(counting.writer());
break :blk counting.bytes_written;
};
return self;
}
pub fn usage(self: Self, w: anytype) !void {
_, _, const sConfig = self.config.destruct();
var sRec = Config.StyleRecord(3){};
const meta = self.arg.meta;
var is_first = true;

if (meta.default != null) {
try w.writeByte('[');
try w.print("{}[", .{sRec.apply(sConfig.usage.optional)});
}
if ((meta.short.len > 0 or meta.long.len > 0) and meta.argName != null) {
try w.print("{}", .{sRec.apply(sConfig.usage.optarg)});
}
if (meta.short.len > 0) {
try w.writeAll(self.config.token.prefix.short);
Expand All @@ -45,6 +55,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) {
try w.writeByte('{');
}
Expand All @@ -59,24 +70,36 @@ pub fn usage(self: Self, w: anytype) !void {
}
}
if (meta.default != null) {
try w.writeByte(']');
try w.print("{}]", .{sRec.restore(sConfig.usage.optional)});
}
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)) {
try w.writeAll("...");
}
}
fn usage1(self: Self, w: anytype) !void {
const tConfig, _, const sConfig = self.config.destruct();
var sRec = Config.StyleRecord(3){};
const meta = self.arg.meta;
var is_first = true;
for (meta.short) |short| {

for (meta.short, 0..) |short, i| {
if (i != 0) {
try w.print("{}", .{sRec.apply(sConfig.usage.alias)});
}
if (is_first) is_first = false else try w.writeAll(", ");
try w.writeAll(self.config.token.prefix.short);
try w.writeAll(tConfig.prefix.short);
try w.writeByte(short);
try w.print("{}", .{sRec.reset()});
}
for (meta.long) |long| {
for (meta.long, 0..) |long, i| {
if (i != 0) {
try w.print("{}", .{sRec.apply(sConfig.usage.alias)});
}
if (is_first) is_first = false else try w.writeAll(", ");
try w.writeAll(self.config.token.prefix.long);
try w.writeAll(tConfig.prefix.long);
try w.writeAll(long);
try w.print("{}", .{sRec.reset()});
}
if (meta.argName) |s| {
if (!is_first) try w.writeByte(' ');
Expand Down Expand Up @@ -105,12 +128,13 @@ fn indent(self: Self, w: anytype, is_firstline: *bool) !void {
}
}
pub fn help(self: Self, w: anytype) !void {
_, const fConfig, const sConfig = self.config.destruct();
var sRec = Config.StyleRecord(5){};
const Base = ztype.Type.Base;
const meta = self.arg.meta;

var is_firstline = true;

try w.writeAll(" " ** self.config.format.indent);
try w.writeAll(" " ** fConfig.indent);
try self.usage1(w);

if (meta.help != null or meta.default != null) {
Expand All @@ -120,31 +144,51 @@ pub fn help(self: Self, w: anytype) !void {
}
if (meta.default) |_| {
if (meta.help != null) try w.writeByte(' ');
try w.print("(default is {})", .{any(self.arg._toField().defaultValue().?, .{})});
try w.print("{}(default is {}{}{}){}", .{
sRec.apply(sConfig.help.default),
sRec.apply(sConfig.help.defaultValue),
any(self.arg._toField().defaultValue().?, .{}),
sRec.restore(sConfig.help.default),
sRec.reset(),
});
}
}

if (meta.ranges != null or meta.choices != null) {
try self.indent(w, &is_firstline);
try w.writeAll("possible values: ");
try w.print("{}possible values: {}", .{
sRec.apply(sConfig.help.possible),
sRec.apply(sConfig.help.possibleValue),
});
if (meta.ranges) |_| {
try w.print("{}", .{any(self.arg.getRanges().?.rs, .{ .multiple = .dump(" or ", 1) })});
}
if (meta.choices) |_| {
if (meta.ranges != null) try w.writeAll(" or ");
try w.print("{}", .{any(self.arg.getChoices().?.*, .{})});
}
try w.print("{}", .{sRec.reset()});
}

if (meta.rawChoices) |cs| {
try self.indent(w, &is_firstline);
try w.print("possible inputs: {}", .{any(cs, .{})});
try w.print("{}possible inputs: {}{}{}", .{
sRec.apply(sConfig.help.possible),
sRec.apply(sConfig.help.possibleInput),
any(cs, .{}),
sRec.reset(),
});
}

if (meta.ranges == null and meta.choices == null and meta.rawChoices == null) {
if (@typeInfo(Base(self.arg.T)) == .@"enum" and self.arg.getParseFn() == null and !std.meta.hasMethod(Base(self.arg.T), "parse")) {
try self.indent(w, &is_firstline);
try w.print("Enum: {}", .{any(std.meta.fieldNames(Base(self.arg.T)).*, .{})});
try w.print("{}Enum: {}{}{}", .{
sRec.apply(sConfig.help.enum_),
sRec.apply(sConfig.help.enumValue),
any(std.meta.fieldNames(Base(self.arg.T)).*, .{}),
sRec.reset(),
});
is_firstline = false;
}
}
Expand Down
Loading
Loading