summaryrefslogtreecommitdiff
path: root/src/cli/args.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli/args.zig')
-rw-r--r--src/cli/args.zig271
1 files changed, 135 insertions, 136 deletions
diff --git a/src/cli/args.zig b/src/cli/args.zig
index c4a40acf5..a34560b78 100644
--- a/src/cli/args.zig
+++ b/src/cli/args.zig
@@ -162,10 +162,11 @@ pub fn parse(
error.InvalidField => "unknown field",
error.ValueRequired => formatValueRequired(T, arena_alloc, key) catch "value required",
error.InvalidValue => formatInvalidValue(T, arena_alloc, key, value) catch "invalid value",
- else => try std.fmt.allocPrintZ(
+ else => try std.fmt.allocPrintSentinel(
arena_alloc,
"unknown error {}",
.{err},
+ 0,
),
};
@@ -235,14 +236,16 @@ fn formatValueRequired(
comptime T: type,
arena_alloc: std.mem.Allocator,
key: []const u8,
-) std.mem.Allocator.Error![:0]const u8 {
- var buf = std.ArrayList(u8).init(arena_alloc);
- errdefer buf.deinit();
- const writer = buf.writer();
+) std.Io.Writer.Error![:0]const u8 {
+ var stream: std.Io.Writer.Allocating = .init(arena_alloc);
+ const writer = &stream.writer;
+
try writer.print("value required", .{});
try formatValues(T, key, writer);
try writer.writeByte(0);
- return buf.items[0 .. buf.items.len - 1 :0];
+
+ const written = stream.written();
+ return written[0 .. written.len - 1 :0];
}
fn formatInvalidValue(
@@ -250,17 +253,23 @@ fn formatInvalidValue(
arena_alloc: std.mem.Allocator,
key: []const u8,
value: ?[]const u8,
-) std.mem.Allocator.Error![:0]const u8 {
- var buf = std.ArrayList(u8).init(arena_alloc);
- errdefer buf.deinit();
- const writer = buf.writer();
+) std.Io.Writer.Error![:0]const u8 {
+ var stream: std.Io.Writer.Allocating = .init(arena_alloc);
+ const writer = &stream.writer;
+
try writer.print("invalid value \"{?s}\"", .{value});
try formatValues(T, key, writer);
try writer.writeByte(0);
- return buf.items[0 .. buf.items.len - 1 :0];
+
+ const written = stream.written();
+ return written[0 .. written.len - 1 :0];
}
-fn formatValues(comptime T: type, key: []const u8, writer: anytype) std.mem.Allocator.Error!void {
+fn formatValues(
+ comptime T: type,
+ key: []const u8,
+ writer: *std.Io.Writer,
+) std.Io.Writer.Error!void {
@setEvalBranchQuota(2000);
const typeinfo = @typeInfo(T);
inline for (typeinfo.@"struct".fields) |f| {
@@ -542,8 +551,8 @@ pub fn parseAutoStruct(
const key = std.mem.trim(u8, entry[0..idx], whitespace);
// used if we need to decode a double-quoted string.
- var buf: std.ArrayListUnmanaged(u8) = .empty;
- defer buf.deinit(alloc);
+ var buf: std.Io.Writer.Allocating = .init(alloc);
+ defer buf.deinit();
const value = value: {
const value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
@@ -554,10 +563,9 @@ pub fn parseAutoStruct(
value[value.len - 1] == '"')
{
// Decode a double-quoted string as a Zig string literal.
- const writer = buf.writer(alloc);
- const parsed = try std.zig.string_literal.parseWrite(writer, value);
+ const parsed = try std.zig.string_literal.parseWrite(&buf.writer, value);
if (parsed == .failure) return error.InvalidValue;
- break :value buf.items;
+ break :value buf.written();
}
break :value value;
@@ -795,15 +803,13 @@ test "parse: diagnostic location" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
- var fbs = std.io.fixedBufferStream(
+ var r: std.Io.Reader = .fixed(
\\a=42
\\what
\\b=two
);
- const r = fbs.reader();
- const Iter = LineIterator(@TypeOf(r));
- var iter: Iter = .{ .r = r, .filepath = "test" };
+ var iter: LineIterator = .{ .r = &r, .filepath = "test" };
try parse(@TypeOf(data), testing.allocator, &data, &iter);
try testing.expect(data._arena != null);
try testing.expectEqualStrings("42", data.a);
@@ -1208,18 +1214,7 @@ test "parseIntoField: struct with basic fields" {
try testing.expectEqual(84, data.value.b);
try testing.expectEqual(24, data.value.c);
- // Set with explicit default
- data.value = try parseAutoStruct(
- @TypeOf(data.value),
- alloc,
- "a:hello",
- .{ .a = "oh no", .b = 42 },
- );
- try testing.expectEqualStrings("hello", data.value.a);
- try testing.expectEqual(42, data.value.b);
- try testing.expectEqual(12, data.value.c);
-
- // Missing required field
+ // Missing require dfield
try testing.expectError(
error.InvalidValue,
parseIntoField(@TypeOf(data), alloc, &data, "value", "a:hello"),
@@ -1395,115 +1390,119 @@ test "ArgsIterator" {
/// Returns an iterator (implements "next") that reads CLI args by line.
/// Each CLI arg is expected to be a single line. This is used to implement
/// configuration files.
-pub fn LineIterator(comptime ReaderType: type) type {
- return struct {
- const Self = @This();
+pub const LineIterator = struct {
+ const Self = @This();
- /// The maximum size a single line can be. We don't expect any
- /// CLI arg to exceed this size. Can't wait to git blame this in
- /// like 4 years and be wrong about this.
- pub const MAX_LINE_SIZE = 4096;
+ /// The maximum size a single line can be. We don't expect any
+ /// CLI arg to exceed this size. Can't wait to git blame this in
+ /// like 4 years and be wrong about this.
+ pub const MAX_LINE_SIZE = 4096;
- /// Our stateful reader.
- r: ReaderType,
+ /// Our stateful reader.
+ r: *std.Io.Reader,
- /// Filepath that is used for diagnostics. This is only used for
- /// diagnostic messages so it can be formatted however you want.
- /// It is prefixed to the messages followed by the line number.
- filepath: []const u8 = "",
+ /// Filepath that is used for diagnostics. This is only used for
+ /// diagnostic messages so it can be formatted however you want.
+ /// It is prefixed to the messages followed by the line number.
+ filepath: []const u8 = "",
- /// The current line that we're on. This is 1-indexed because
- /// lines are generally 1-indexed in the real world. The value
- /// can be zero if we haven't read any lines yet.
- line: usize = 0,
+ /// The current line that we're on. This is 1-indexed because
+ /// lines are generally 1-indexed in the real world. The value
+ /// can be zero if we haven't read any lines yet.
+ line: usize = 0,
- /// This is the buffer where we store the current entry that
- /// is formatted to be compatible with the parse function.
- entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)),
+ /// This is the buffer where we store the current entry that
+ /// is formatted to be compatible with the parse function.
+ entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)),
- pub fn next(self: *Self) ?[]const u8 {
- // TODO: detect "--" prefixed lines and give a friendlier error
- const buf = buf: {
- while (true) {
- // Read the full line
- var entry = self.r.readUntilDelimiterOrEof(self.entry[2..], '\n') catch |err| switch (err) {
- inline else => |e| {
- log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e });
- return null;
- },
- } orelse return null;
+ pub fn init(reader: *std.Io.Reader) Self {
+ return .{ .r = reader };
+ }
- // Increment our line counter
- self.line += 1;
+ pub fn next(self: *Self) ?[]const u8 {
+ // First prime the reader.
+ // File readers at least are initialized with a size of 0,
+ // and this will actually prompt the reader to get the actual
+ // size of the file, which will be used in the EOF check below.
+ //
+ // This will also optimize reads down the line as we're
+ // more likely to beworking with buffered data.
+ self.r.fillMore() catch {};
+
+ var writer: std.Io.Writer = .fixed(self.entry[2..]);
+
+ var entry = while (self.r.seek != self.r.end) {
+ // Reset write head
+ writer.end = 0;
+
+ _ = self.r.streamDelimiterEnding(&writer, '\n') catch |e| {
+ log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e });
+ return null;
+ };
+ _ = self.r.discardDelimiterInclusive('\n') catch {};
- // Trim any whitespace (including CR) around it
- const trim = std.mem.trim(u8, entry, whitespace ++ "\r");
- if (trim.len != entry.len) {
- std.mem.copyForwards(u8, entry, trim);
- entry = entry[0..trim.len];
- }
+ var entry = writer.buffered();
+ self.line += 1;
- // Ignore blank lines and comments
- if (entry.len == 0 or entry[0] == '#') continue;
-
- // Trim spaces around '='
- if (mem.indexOf(u8, entry, "=")) |idx| {
- const key = std.mem.trim(u8, entry[0..idx], whitespace);
- const value = value: {
- var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
-
- // Detect a quoted string.
- if (value.len >= 2 and
- value[0] == '"' and
- value[value.len - 1] == '"')
- {
- // Trim quotes since our CLI args processor expects
- // quotes to already be gone.
- value = value[1 .. value.len - 1];
- }
-
- break :value value;
- };
-
- const len = key.len + value.len + 1;
- if (entry.len != len) {
- std.mem.copyForwards(u8, entry, key);
- entry[key.len] = '=';
- std.mem.copyForwards(u8, entry[key.len + 1 ..], value);
- entry = entry[0..len];
- }
- }
+ // Trim any whitespace (including CR) around it
+ const trim = std.mem.trim(u8, entry, whitespace ++ "\r");
+ if (trim.len != entry.len) {
+ std.mem.copyForwards(u8, entry, trim);
+ entry = entry[0..trim.len];
+ }
- break :buf entry;
+ // Ignore blank lines and comments
+ if (entry.len == 0 or entry[0] == '#') continue;
+ break entry;
+ } else return null;
+
+ if (mem.indexOf(u8, entry, "=")) |idx| {
+ const key = std.mem.trim(u8, entry[0..idx], whitespace);
+ const value = value: {
+ var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
+
+ // Detect a quoted string.
+ if (value.len >= 2 and
+ value[0] == '"' and
+ value[value.len - 1] == '"')
+ {
+ // Trim quotes since our CLI args processor expects
+ // quotes to already be gone.
+ value = value[1 .. value.len - 1];
}
+
+ break :value value;
};
- // We need to reslice so that we include our '--' at the beginning
- // of our buffer so that we can trick the CLI parser to treat it
- // as CLI args.
- return self.entry[0 .. buf.len + 2];
+ const len = key.len + value.len + 1;
+ if (entry.len != len) {
+ std.mem.copyForwards(u8, entry, key);
+ entry[key.len] = '=';
+ std.mem.copyForwards(u8, entry[key.len + 1 ..], value);
+ entry = entry[0..len];
+ }
}
- /// Returns a location for a diagnostic message.
- pub fn location(
- self: *const Self,
- alloc: Allocator,
- ) Allocator.Error!?diags.Location {
- // If we have no filepath then we have no location.
- if (self.filepath.len == 0) return null;
-
- return .{ .file = .{
- .path = try alloc.dupe(u8, self.filepath),
- .line = self.line,
- } };
- }
- };
-}
+ // We need to reslice so that we include our '--' at the beginning
+ // of our buffer so that we can trick the CLI parser to treat it
+ // as CLI args.
+ return self.entry[0 .. entry.len + 2];
+ }
-// Constructs a LineIterator (see docs for that).
-fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) {
- return .{ .r = reader };
-}
+ /// Returns a location for a diagnostic message.
+ pub fn location(
+ self: *const Self,
+ alloc: Allocator,
+ ) Allocator.Error!?diags.Location {
+ // If we have no filepath then we have no location.
+ if (self.filepath.len == 0) return null;
+
+ return .{ .file = .{
+ .path = try alloc.dupe(u8, self.filepath),
+ .line = self.line,
+ } };
+ }
+};
/// An iterator valid for arg parsing from a slice.
pub const SliceIterator = struct {
@@ -1526,7 +1525,7 @@ pub fn sliceIterator(slice: []const []const u8) SliceIterator {
test "LineIterator" {
const testing = std.testing;
- var fbs = std.io.fixedBufferStream(
+ var reader: std.Io.Reader = .fixed(
\\A
\\B=42
\\C
@@ -1541,7 +1540,7 @@ test "LineIterator" {
\\F= "value "
);
- var iter = lineIterator(fbs.reader());
+ var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A", iter.next().?);
try testing.expectEqualStrings("--B=42", iter.next().?);
try testing.expectEqualStrings("--C", iter.next().?);
@@ -1554,9 +1553,9 @@ test "LineIterator" {
test "LineIterator end in newline" {
const testing = std.testing;
- var fbs = std.io.fixedBufferStream("A\n\n");
+ var reader: std.Io.Reader = .fixed("A\n\n");
- var iter = lineIterator(fbs.reader());
+ var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());
try testing.expectEqual(@as(?[]const u8, null), iter.next());
@@ -1564,9 +1563,9 @@ test "LineIterator end in newline" {
test "LineIterator spaces around '='" {
const testing = std.testing;
- var fbs = std.io.fixedBufferStream("A = B\n\n");
+ var reader: std.Io.Reader = .fixed("A = B\n\n");
- var iter = lineIterator(fbs.reader());
+ var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A=B", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());
try testing.expectEqual(@as(?[]const u8, null), iter.next());
@@ -1574,18 +1573,18 @@ test "LineIterator spaces around '='" {
test "LineIterator no value" {
const testing = std.testing;
- var fbs = std.io.fixedBufferStream("A = \n\n");
+ var reader: std.Io.Reader = .fixed("A = \n\n");
- var iter = lineIterator(fbs.reader());
+ var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A=", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());
}
test "LineIterator with CRLF line endings" {
const testing = std.testing;
- var fbs = std.io.fixedBufferStream("A\r\nB = C\r\n");
+ var reader: std.Io.Reader = .fixed("A\r\nB = C\r\n");
- var iter = lineIterator(fbs.reader());
+ var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A", iter.next().?);
try testing.expectEqualStrings("--B=C", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next());