From bde89d2af1d042c015c3999933b5101278af83eb Mon Sep 17 00:00:00 2001 From: Rene Schallner Date: Fri, 23 Dec 2022 14:44:34 +0100 Subject: [PATCH 1/3] updated code to work with zig master 0.11 --- build.zig | 2 +- src/args.zig | 202 ++++++++++++++++++++++++++---------------- src/gitversiontag.zig | 7 +- src/main.zig | 13 +-- 4 files changed, 140 insertions(+), 84 deletions(-) diff --git a/build.zig b/build.zig index 92e964e..ab9b67e 100644 --- a/build.zig +++ b/build.zig @@ -5,7 +5,7 @@ pub fn build(b: *std.build.Builder) void { // write src/version.zig const alloc = std.heap.page_allocator; const gvs = gitVersionTag(alloc); - const efmt = "WARNING: could not write src/version.zig:\n {s}\n"; + const efmt = "WARNING: could not write src/version.zig:\n {!}\n"; if (std.fs.cwd().createFile("src/version.zig", .{})) |file| { defer file.close(); const zigfmt = "pub const version_string = \"{s}\";"; diff --git a/src/args.zig b/src/args.zig index a790120..7fa3517 100644 --- a/src/args.zig +++ b/src/args.zig @@ -1,7 +1,3 @@ -// From: https://github.com/MasterQ32/zig-args -// Commit: 72a79c87fdf5aaa98f81796fbf6500b5c06b1ebc -// (compatible with zig 0.9.1) - const std = @import("std"); /// Parses arguments for the given specification and our current process. @@ -9,9 +5,13 @@ const std = @import("std"); /// - `allocator` is the allocator that is used to allocate all required memory /// - `error_handling` defines how parser errors will be handled. pub fn parseForCurrentProcess(comptime Spec: type, allocator: std.mem.Allocator, error_handling: ErrorHandling) !ParseArgsResult(Spec, null) { - var args = std.process.args(); + // Use argsWithAllocator for portability. + // All data allocated by the ArgIterator is freed at the end of the function. + // Data returned to the user is always duplicated using the allocator. + var args = try std.process.argsWithAllocator(allocator); + defer args.deinit(); - const executable_name = try (args.next(allocator) orelse { + const executable_name = args.next() orelse { try error_handling.process(error.NoExecutableName, Error{ .option = "", .kind = .missing_executable_name, @@ -19,12 +19,11 @@ pub fn parseForCurrentProcess(comptime Spec: type, allocator: std.mem.Allocator, // we do not assume any more arguments appear here anyways... return error.NoExecutableName; - }); - errdefer allocator.free(executable_name); + }; var result = try parseInternal(Spec, null, &args, allocator, error_handling); - result.executable_name = executable_name; + result.executable_name = try allocator.dupeZ(u8, executable_name); return result; } @@ -34,9 +33,13 @@ pub fn parseForCurrentProcess(comptime Spec: type, allocator: std.mem.Allocator, /// - `allocator` is the allocator that is used to allocate all required memory /// - `error_handling` defines how parser errors will be handled. pub fn parseWithVerbForCurrentProcess(comptime Spec: type, comptime Verb: type, allocator: std.mem.Allocator, error_handling: ErrorHandling) !ParseArgsResult(Spec, Verb) { - var args = std.process.args(); + // Use argsWithAllocator for portability. + // All data allocated by the ArgIterator is freed at the end of the function. + // Data returned to the user is always duplicated using the allocator. + var args = try std.process.argsWithAllocator(allocator); + defer args.deinit(); - const executable_name = try (args.next(allocator) orelse { + const executable_name = args.next() orelse { try error_handling.process(error.NoExecutableName, Error{ .option = "", .kind = .missing_executable_name, @@ -44,12 +47,11 @@ pub fn parseWithVerbForCurrentProcess(comptime Spec: type, comptime Verb: type, // we do not assume any more arguments appear here anyways... return error.NoExecutableName; - }); - errdefer allocator.free(executable_name); + }; var result = try parseInternal(Spec, Verb, &args, allocator, error_handling); - result.executable_name = executable_name; + result.executable_name = try allocator.dupeZ(u8, executable_name); return result; } @@ -92,16 +94,15 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato var result_arena_allocator = result.arena.allocator(); var arglist = std.ArrayList([:0]const u8).init(allocator); - errdefer arglist.deinit(); + defer arglist.deinit(); var last_error: ?anyerror = null; - while (args_iterator.next(result_arena_allocator)) |item_or_error| { - const item = try item_or_error; - + while (args_iterator.next()) |item| { if (std.mem.startsWith(u8, item, "--")) { if (std.mem.eql(u8, item, "--")) { // double hyphen is considered 'everything from here now is positional' + result.raw_start_index = arglist.items.len; break; } @@ -135,19 +136,21 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato const Tag = std.meta.Tag(Verb); inline for (std.meta.fields(Verb)) |verb_info| { if (verb.* == @field(Tag, verb_info.name)) { - inline for (std.meta.fields(verb_info.field_type)) |fld| { - if (std.mem.eql(u8, pair.name, fld.name)) { - try parseOption( - verb_info.field_type, - result_arena_allocator, - &@field(verb.*, verb_info.name), - args_iterator, - error_handling, - &last_error, - fld.name, - pair.value, - ); - found = true; + if (comptime canHaveFieldsAndIsNotZeroSized(verb_info.type)) { + inline for (std.meta.fields(verb_info.type)) |fld| { + if (std.mem.eql(u8, pair.name, fld.name)) { + try parseOption( + verb_info.type, + result_arena_allocator, + &@field(verb.*, verb_info.name), + args_iterator, + error_handling, + &last_error, + fld.name, + pair.value, + ); + found = true; + } } } } @@ -166,7 +169,7 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato } else if (std.mem.startsWith(u8, item, "-")) { if (std.mem.eql(u8, item, "-")) { // single hyphen is considered a positional argument - try arglist.append(item); + try arglist.append(try result_arena_allocator.dupeZ(u8, item)); } else { var any_shorthands = false; for (item[1..]) |char, index| { @@ -202,30 +205,32 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato if (!found) { const Tag = std.meta.Tag(Verb); inline for (std.meta.fields(Verb)) |verb_info| { - const VerbType = verb_info.field_type; - if (verb.* == @field(Tag, verb_info.name)) { - const target_value = &@field(verb.*, verb_info.name); - if (@hasDecl(VerbType, "shorthands")) { - any_shorthands = true; - inline for (std.meta.fields(@TypeOf(VerbType.shorthands))) |fld| { - if (fld.name.len != 1) - @compileError("All shorthand fields must be exactly one character long!"); - if (fld.name[0] == char) { - const real_name = @field(VerbType.shorthands, fld.name); - const real_fld_type = @TypeOf(@field(target_value.*, real_name)); - - // -2 because we stripped of the "-" at the beginning - if (requiresArg(real_fld_type) and index != item.len - 2) { - last_error = error.EncounteredUnexpectedArgument; - try error_handling.process(error.EncounteredUnexpectedArgument, Error{ - .option = &option_name, - .kind = .invalid_placement, - }); - } else { - try parseOption(VerbType, result_arena_allocator, target_value, args_iterator, error_handling, &last_error, real_name, null); + const VerbType = verb_info.type; + if (comptime canHaveFieldsAndIsNotZeroSized(VerbType)) { + if (verb.* == @field(Tag, verb_info.name)) { + const target_value = &@field(verb.*, verb_info.name); + if (@hasDecl(VerbType, "shorthands")) { + any_shorthands = true; + inline for (std.meta.fields(@TypeOf(VerbType.shorthands))) |fld| { + if (fld.name.len != 1) + @compileError("All shorthand fields must be exactly one character long!"); + if (fld.name[0] == char) { + const real_name = @field(VerbType.shorthands, fld.name); + const real_fld_type = @TypeOf(@field(target_value.*, real_name)); + + // -2 because we stripped of the "-" at the beginning + if (requiresArg(real_fld_type) and index != item.len - 2) { + last_error = error.EncounteredUnexpectedArgument; + try error_handling.process(error.EncounteredUnexpectedArgument, Error{ + .option = &option_name, + .kind = .invalid_placement, + }); + } else { + try parseOption(VerbType, result_arena_allocator, target_value, args_iterator, error_handling, &last_error, real_name, null); + } + last_error = null; // we need to reset that error here, as it was set previously + found = true; } - last_error = null; // we need to reset that error here, as it was set previously - found = true; } } } @@ -255,7 +260,7 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato inline for (std.meta.fields(Verb)) |fld| { if (std.mem.eql(u8, item, fld.name)) { // found active verb, default-initialize it - result.verb = @unionInit(Verb, fld.name, fld.field_type{}); + result.verb = @unionInit(Verb, fld.name, fld.type{}); } } @@ -270,7 +275,7 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato } } - try arglist.append(item); + try arglist.append(try result_arena_allocator.dupeZ(u8, item)); } } @@ -279,22 +284,28 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato // This will consume the rest of the arguments as positional ones. // Only executes when the above loop is broken. - while (args_iterator.next(result_arena_allocator)) |item_or_error| { - const item = try item_or_error; - try arglist.append(item); + while (args_iterator.next()) |item| { + try arglist.append(try result_arena_allocator.dupeZ(u8, item)); } - result.positionals = arglist.toOwnedSlice(); + result.positionals = try arglist.toOwnedSlice(); return result; } +fn canHaveFieldsAndIsNotZeroSized(comptime T: type) bool { + return switch (@typeInfo(T)) { + .Struct, .Union, .Enum, .ErrorSet => @sizeOf(T) != 0, + else => false, + }; +} + /// The return type of the argument parser. pub fn ParseArgsResult(comptime Generic: type, comptime MaybeVerb: ?type) type { if (@typeInfo(Generic) != .Struct) @compileError("Generic argument definition must be a struct"); if (MaybeVerb) |Verb| { - const ti: std.builtin.TypeInfo = @typeInfo(Verb); + const ti: std.builtin.Type = @typeInfo(Verb); if (ti != .Union or ti.Union.tag_type == null) @compileError("Verb must be a tagged union"); } @@ -318,6 +329,9 @@ pub fn ParseArgsResult(comptime Generic: type, comptime MaybeVerb: ?type) type { /// The positional arguments that were passed to the process. positionals: [][:0]const u8, + // The index of the first "raw arg", meaning the first arg after "--" + raw_start_index: ?usize = null, + /// Name of the executable file (or: zeroth argument) executable_name: ?[:0]const u8, @@ -491,21 +505,28 @@ fn parseOption( ) !void { const field_type = @TypeOf(@field(target_struct, name)); - const final_value = if (value) |val| - val // use the literal value - else if (requiresArg(field_type)) + const final_value = if (value) |val| blk: { + // use the literal value + const res = try arena.dupeZ(u8, val); + break :blk res; + } else if (requiresArg(field_type)) blk: { // fetch from parser - try (args.next(arena) orelse { + const val = args.next(); + if (val == null or std.mem.eql(u8, val.?, "--")) { last_error.* = error.MissingArgument; try error_handling.process(error.MissingArgument, Error{ .option = "--" ++ name, .kind = .missing_argument, }); return; - }) - else + } + + const res = try arena.dupeZ(u8, val.?); + break :blk res; + } else blk: { // argument is "empty" - ""; + break :blk ""; + }; @field(target_struct, name) = convertArgumentValue(field_type, arena, final_value) catch |err| { last_error.* = err; @@ -515,7 +536,7 @@ fn parseOption( }); // we couldn't parse the value, so we return a undefined value as we have signalled an // error and won't return this anyways. - return undefined; + return; }; } @@ -671,10 +692,10 @@ const TestIterator = struct { return TestIterator{ .sequence = items }; } - pub fn next(self: *@This(), allocator: std.mem.Allocator) ?(error{OutOfMemory}![:0]u8) { + pub fn next(self: *@This()) ?[:0]const u8 { if (self.index >= self.sequence.len) return null; - const result = try allocator.dupeZ(u8, self.sequence[self.index]); + const result = self.sequence[self.index]; self.index += 1; return result; } @@ -791,9 +812,9 @@ test "shorthand parsing (no verbs)" { test "basic parsing (with verbs)" { var titerator = TestIterator.init(&[_][:0]const u8{ - "booze", // verb - "--output", + "--output", // non-verb options can come before or after verb "foobar", + "booze", // verb "--with-offset", "--numberOfBytes", "-250", @@ -901,3 +922,36 @@ test "strings with sentinel" { try std.testing.expectEqualStrings("foobar", args.options.output.?); } + +test "option argument --" { + var titerator = TestIterator.init(&[_][:0]const u8{ + "--output", + "--", + }); + + try std.testing.expectError(error.MissingArgument, parseInternal( + struct { + output: ?[:0]const u8 = null, + }, + null, + &titerator, + std.testing.allocator, + .silent, + )); +} + +test "index of raw indicator --" { + var titerator = TestIterator.init(&[_][:0]const u8{ "stdin", "-", "--", "not-stdin", "-", "--" }); + + var args = try parseInternal( + struct {}, + null, + &titerator, + std.testing.allocator, + .print, + ); + defer args.deinit(); + + try std.testing.expectEqual(args.raw_start_index, 2); + try std.testing.expectEqual(args.positionals.len, 5); +} diff --git a/src/gitversiontag.zig b/src/gitversiontag.zig index 2a05a24..40aed0f 100644 --- a/src/gitversiontag.zig +++ b/src/gitversiontag.zig @@ -1,5 +1,5 @@ const std = @import("std"); -pub fn gitVersionTag(a: std.mem.Allocator) ![]const u8 { +pub fn gitVersionTag(a: std.mem.Allocator) []const u8 { const args = [_][]const u8{ "git", "tag", @@ -7,13 +7,14 @@ pub fn gitVersionTag(a: std.mem.Allocator) ![]const u8 { }; if (std.ChildProcess.exec(.{ .argv = args[0..], .allocator = a })) |ret| { - if (std.mem.split(u8, ret.stdout, "\n").next()) |firstline| { + var it = std.mem.split(u8, ret.stdout, "\n"); + if (it.next()) |firstline| { return firstline; } else { return "unknown"; } } else |err| { - std.log.err("Unable to spawn and wait: {any}", .{err}); + std.log.err("Unable to spawn and wait: {!}", .{err}); } return "unknown"; } diff --git a/src/main.zig b/src/main.zig index 3b4d57d..ae21302 100644 --- a/src/main.zig +++ b/src/main.zig @@ -108,7 +108,8 @@ fn addToAddressBook( key: []const u8, value: []const u8, ) !void { - var file = try std.fs.cwd().openFile(filn, .{ .write = true }); + // TODO check if write onlu would be OK + var file = try std.fs.cwd().openFile(filn, .{ .mode = .read_write }); defer file.close(); try file.seekFromEnd(0); try file.writer().print("\n{s} : {s}", .{ key, value }); @@ -119,7 +120,7 @@ fn addEmailsToAddressBook( map: std.StringHashMap([]const u8), emails: std.StringHashMap([]const u8), ) !void { - var file = try std.fs.cwd().openFile(filn, .{ .write = true }); + var file = try std.fs.cwd().openFile(filn, .{ .mode = .read_write }); defer file.close(); try file.seekFromEnd(0); var it = emails.iterator(); @@ -355,7 +356,7 @@ pub fn main() anyerror!void { // do nothing } else |err| { const errwriter = std.io.getStdErr().writer(); - try errwriter.print("Error {s}: {s}\n", .{ err, filn }); + try errwriter.print("Error {!}: {s}\n", .{ err, filn }); return; } const ret = try parseMailFromStdin(alloc); @@ -393,7 +394,7 @@ pub fn main() anyerror!void { // } else |err| { const errwriter = std.io.getStdErr().writer(); - try errwriter.print("Error {s}: {s}\n", .{ err, filn }); + try errwriter.print("Error {!}: {s}\n", .{ err, filn }); return; } // check if key exists @@ -426,7 +427,7 @@ pub fn main() anyerror!void { // do nothing } else |err| { const errwriter = std.io.getStdErr().writer(); - try errwriter.print("Error {s}: {s}\n", .{ err, filn }); + try errwriter.print("Error {!}: {s}\n", .{ err, filn }); return; } @@ -470,7 +471,7 @@ pub fn main() anyerror!void { } } } else |err| { - std.debug.print("{s}", .{err}); + std.debug.print("{!}", .{err}); help(); } } -- 2.40.0 From 7efc520767869f2eea82a65b943c0d3d64a2b33c Mon Sep 17 00:00:00 2001 From: Rene Schallner Date: Wed, 28 Dec 2022 13:33:16 +0100 Subject: [PATCH 2/3] back-ported zig-master branch for zig-0.10 --- src/args.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/args.zig b/src/args.zig index 7fa3517..daf27ac 100644 --- a/src/args.zig +++ b/src/args.zig @@ -94,7 +94,7 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato var result_arena_allocator = result.arena.allocator(); var arglist = std.ArrayList([:0]const u8).init(allocator); - defer arglist.deinit(); + errdefer arglist.deinit(); var last_error: ?anyerror = null; @@ -136,11 +136,11 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato const Tag = std.meta.Tag(Verb); inline for (std.meta.fields(Verb)) |verb_info| { if (verb.* == @field(Tag, verb_info.name)) { - if (comptime canHaveFieldsAndIsNotZeroSized(verb_info.type)) { - inline for (std.meta.fields(verb_info.type)) |fld| { + if (comptime canHaveFieldsAndIsNotZeroSized(verb_info.field_type)) { + inline for (std.meta.fields(verb_info.field_type)) |fld| { if (std.mem.eql(u8, pair.name, fld.name)) { try parseOption( - verb_info.type, + verb_info.field_type, result_arena_allocator, &@field(verb.*, verb_info.name), args_iterator, @@ -205,7 +205,7 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato if (!found) { const Tag = std.meta.Tag(Verb); inline for (std.meta.fields(Verb)) |verb_info| { - const VerbType = verb_info.type; + const VerbType = verb_info.field_type; if (comptime canHaveFieldsAndIsNotZeroSized(VerbType)) { if (verb.* == @field(Tag, verb_info.name)) { const target_value = &@field(verb.*, verb_info.name); @@ -260,7 +260,7 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato inline for (std.meta.fields(Verb)) |fld| { if (std.mem.eql(u8, item, fld.name)) { // found active verb, default-initialize it - result.verb = @unionInit(Verb, fld.name, fld.type{}); + result.verb = @unionInit(Verb, fld.name, fld.field_type{}); } } @@ -288,7 +288,7 @@ fn parseInternal(comptime Generic: type, comptime MaybeVerb: ?type, args_iterato try arglist.append(try result_arena_allocator.dupeZ(u8, item)); } - result.positionals = try arglist.toOwnedSlice(); + result.positionals = arglist.toOwnedSlice(); return result; } -- 2.40.0 From 85e5c4c3c8dfdec3d103ce3778e8cfe35ad4b074 Mon Sep 17 00:00:00 2001 From: Rene Schallner Date: Wed, 28 Dec 2022 14:00:59 +0100 Subject: [PATCH 3/3] updated readme --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e75ebba..8d0b6da 100644 --- a/README.md +++ b/README.md @@ -260,8 +260,8 @@ suggested as completion. # Building it -Make sure you have [zig 0.9.1](https://ziglang.org/download/) installed. Then -run: +Make sure you have the latest stable release of zig, [zig +0.10.0](https://ziglang.org/download/) installed. Then run: ```console zig build @@ -272,7 +272,8 @@ This will produce `aercbook` in the `./zig-out/bin/` directory. From there, # Tested with -- zig 0.9.1 -- aerc 0.11.0 -- on Linux: NixOS 22.05 ([patched for aerc 0.11.0 instead of - 0.10.0](https://sr.ht/~renerocksai/nixpkgs/)) +- zig 0.10.0 +- aerc 0.13.0 +- on Linux: + - NixOS 22.05 ([patched for aerc 0.11.0 instead of 0.10.0](https://sr.ht/~renerocksai/nixpkgs/)) + - Ubuntu 20.04.5 LTS on crostini (ChromeOS x86_64) -- 2.40.0