mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-17 04:52:47 +00:00
Zig 0.15: zig build test
This commit is contained in:
261
src/cli/args.zig
261
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 {};
|
||||
|
||||
// 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 writer: std.Io.Writer = .fixed(self.entry[2..]);
|
||||
|
||||
// Ignore blank lines and comments
|
||||
if (entry.len == 0 or entry[0] == '#') continue;
|
||||
var entry = while (self.r.seek != self.r.end) {
|
||||
// Reset write head
|
||||
writer.end = 0;
|
||||
|
||||
// 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);
|
||||
_ = self.r.streamDelimiterEnding(&writer, '\n') catch |e| {
|
||||
log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e });
|
||||
return null;
|
||||
};
|
||||
_ = self.r.discardDelimiterInclusive('\n') catch {};
|
||||
|
||||
// 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];
|
||||
}
|
||||
var entry = writer.buffered();
|
||||
self.line += 1;
|
||||
|
||||
break :value value;
|
||||
};
|
||||
// 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];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
// Ignore blank lines and comments
|
||||
if (entry.len == 0 or entry[0] == '#') continue;
|
||||
break entry;
|
||||
} else return null;
|
||||
|
||||
break :buf entry;
|
||||
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;
|
||||
// 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];
|
||||
}
|
||||
|
||||
return .{ .file = .{
|
||||
.path = try alloc.dupe(u8, self.filepath),
|
||||
.line = self.line,
|
||||
} };
|
||||
}
|
||||
};
|
||||
}
|
||||
/// 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;
|
||||
|
||||
// Constructs a LineIterator (see docs for that).
|
||||
fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) {
|
||||
return .{ .r = reader };
|
||||
}
|
||||
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());
|
||||
|
||||
Reference in New Issue
Block a user