Skip to content

Commit e893a79

Browse files
committed
Silence the Writergate
1 parent e97515b commit e893a79

File tree

1 file changed

+144
-34
lines changed

1 file changed

+144
-34
lines changed

known-folders.zig

Lines changed: 144 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,11 @@ const TestingSystem = struct {
337337
if (std.mem.eql(u8, key, kv.key)) return kv.value;
338338
}
339339
system.deinit();
340-
std.debug.panic("the result of `getenv(\"{}\")` must explicitly specified in the TestingSystem", .{std.zig.fmtEscapes(key)});
340+
if (@hasDecl(std.zig, "fmtString")) {
341+
std.debug.panic("the result of `getenv(\"{f}\")` must explicitly specified in the TestingSystem", .{std.zig.fmtString(key)});
342+
} else {
343+
std.debug.panic("the result of `getenv(\"{}\")` must explicitly specified in the TestingSystem", .{std.zig.fmtEscapes(key)});
344+
}
341345
}
342346

343347
/// Asserts that the file is specified in `TestingSystem.files`.
@@ -349,7 +353,11 @@ const TestingSystem = struct {
349353
if (std.mem.eql(u8, file_path, kv.path)) break kv;
350354
} else {
351355
system.deinit();
352-
std.debug.panic("`openFile(\"{0}\", \"{1}\")` has been called on an unexpected file", .{ std.zig.fmtEscapes(dir_path), std.zig.fmtEscapes(sub_path) });
356+
if (@hasDecl(std.zig, "fmtString")) {
357+
std.debug.panic("`openFile(\"{0f}\", \"{1f}\")` has been called on an unexpected file", .{ std.zig.fmtString(dir_path), std.zig.fmtString(sub_path) });
358+
} else {
359+
std.debug.panic("`openFile(\"{0}\", \"{1}\")` has been called on an unexpected file", .{ std.zig.fmtEscapes(dir_path), std.zig.fmtEscapes(sub_path) });
360+
}
353361
};
354362

355363
const data = kv.data orelse return error.FileNotFound;
@@ -435,40 +443,12 @@ fn xdgUserDirLookup(
435443
try system.openFile(home_dir, ".config/user-dirs.dirs");
436444
defer file.close();
437445

438-
var fbr = std.io.bufferedReaderSize(512, file.reader());
439-
const reader = fbr.reader();
446+
var buffer: [xdg_user_dir_lookup_line_buffer_size + 1]u8 = undefined;
447+
var line_it: if (@hasDecl(std.fs.File, "stdin")) LineIterator else OldLineIterator = .init(file);
440448

441449
var user_dir: ?[]u8 = null;
442-
outer: while (true) {
443-
var buffer: [xdg_user_dir_lookup_line_buffer_size + 1]u8 = undefined;
444-
445-
// Similar to `readUntilDelimiterOrEof` but also writes a null-terminator
446-
var line: [:0]u8 = for (&buffer, 0..) |*out, index| {
447-
const byte = reader.readByte() catch |err| switch (err) {
448-
error.EndOfStream => if (index == 0) break :outer else '\n',
449-
else => |e| return e,
450-
};
451-
if (byte == '\n') {
452-
out.* = 0;
453-
break buffer[0..index :0];
454-
}
455-
out.* = byte;
456-
} else blk: {
457-
// This happens when the line is longer than 511 characters
458-
// There are four possible ways to handle this:
459-
// - use dynamic allocation to acquire enough storage
460-
// - return an error
461-
// - skip the line
462-
// - truncate the line
463-
//
464-
// The xdg-user-dir implementation chooses to trunacte the line.
465-
// See "getPath - user-dirs.dirs - very long line" test
466-
467-
try reader.skipUntilDelimiterOrEof('\n');
468-
469-
buffer[buffer.len - 1] = 0;
470-
break :blk buffer[0 .. buffer.len - 1 :0];
471-
};
450+
while (try line_it.next(&buffer)) |line_capture| {
451+
var line = line_capture;
472452

473453
while (line[0] == ' ' or line[0] == '\t')
474454
line = line[1..];
@@ -549,6 +529,108 @@ fn xdgUserDirLookup(
549529
return user_dir;
550530
}
551531

532+
const OldLineIterator = struct {
533+
fbr: std.io.BufferedReader(4096, std.fs.File.Reader),
534+
535+
fn init(file: std.fs.File) OldLineIterator {
536+
return .{
537+
.fbr = std.io.bufferedReader(file.reader()),
538+
};
539+
}
540+
541+
fn next(it: *OldLineIterator, buffer: []u8) std.posix.ReadError!?[:0]const u8 {
542+
const reader = it.fbr.reader();
543+
544+
// Similar to `readUntilDelimiterOrEof` but also writes a null-terminator
545+
for (buffer, 0..) |*out, index| {
546+
const byte = reader.readByte() catch |err| switch (err) {
547+
error.EndOfStream => if (index == 0) return null else '\n',
548+
else => |e| return e,
549+
};
550+
if (byte == '\n') {
551+
out.* = 0;
552+
return buffer[0..index :0];
553+
}
554+
out.* = byte;
555+
} else {
556+
// This happens when the line is longer than 511 characters
557+
// There are four possible ways to handle this:
558+
// - use dynamic allocation to acquire enough storage
559+
// - return an error
560+
// - skip the line
561+
// - truncate the line
562+
//
563+
// The xdg-user-dir implementation chooses to trunacte the line.
564+
// See "getPath - user-dirs.dirs - very long line" test
565+
566+
try reader.skipUntilDelimiterOrEof('\n');
567+
568+
buffer[buffer.len - 1] = 0;
569+
return buffer[0 .. buffer.len - 1 :0];
570+
}
571+
}
572+
};
573+
574+
const LineIterator = struct {
575+
file_reader: std.fs.File.Reader,
576+
keep_reading: bool,
577+
discard_until_newline: bool,
578+
579+
fn init(file: std.fs.File) LineIterator {
580+
return .{
581+
.file_reader = file.reader(undefined),
582+
.keep_reading = true,
583+
.discard_until_newline = false,
584+
};
585+
}
586+
587+
fn next(it: *LineIterator, buffer: []u8) std.posix.ReadError!?[:'\n']const u8 {
588+
if (!it.keep_reading) return null;
589+
590+
const reader = &it.file_reader.interface;
591+
reader.buffer = buffer[0 .. buffer.len - 1]; // leave space for the sentinel
592+
593+
if (it.discard_until_newline) {
594+
it.discard_until_newline = false;
595+
_ = reader.discardDelimiterInclusive('\n') catch |discard_err| switch (discard_err) {
596+
error.ReadFailed => return it.file_reader.err.?,
597+
error.EndOfStream => return null,
598+
};
599+
}
600+
601+
return reader.takeSentinel('\n') catch |err| switch (err) {
602+
error.ReadFailed => return it.file_reader.err.?,
603+
error.EndOfStream => {
604+
if (reader.bufferedLen() == 0)
605+
return null; // trailing newline
606+
607+
// This is the last line
608+
buffer[reader.end] = '\n';
609+
const line = buffer[reader.seek..reader.end :'\n'];
610+
it.keep_reading = false;
611+
return line;
612+
},
613+
error.StreamTooLong => {
614+
// This happens when the line is longer than 511 characters
615+
// There are four possible ways to handle this:
616+
// - use dynamic allocation to acquire enough storage
617+
// - return an error
618+
// - skip the line
619+
// - truncate the line
620+
//
621+
// The xdg-user-dir implementation chooses to trunacte the line.
622+
// See "getPath - user-dirs.dirs - very long line" test
623+
624+
buffer[reader.end] = '\n';
625+
const line = buffer[reader.seek..reader.end :'\n'];
626+
it.discard_until_newline = true;
627+
628+
return line;
629+
},
630+
};
631+
}
632+
};
633+
552634
/// Contains the GUIDs for each available known-folder on windows
553635
const WindowsFolderSpec = union(enum) {
554636
by_guid: std.os.windows.GUID,
@@ -891,6 +973,34 @@ test "getPath - user-dirs.dirs - duplicate config" {
891973
});
892974
}
893975

976+
test "getPath - user-dirs.dirs - trailing newline" {
977+
if (builtin.os.tag == .windows) return error.SkipZigTest;
978+
979+
var system: TestingSystem = .{
980+
.config = .{ .xdg_on_mac = true },
981+
.env_map = &.{
982+
.{ .key = "HOME", .value = "/home/janedoe" },
983+
.{ .key = "XDG_CONFIG_HOME", .value = "/home/janedoe/custom" },
984+
985+
.{ .key = "XDG_VIDEOS_DIR", .value = null },
986+
},
987+
.files = &.{.{
988+
.path = "/home/janedoe/custom/user-dirs.dirs",
989+
.data =
990+
\\XDG_VIDEOS_DIR="/mnt/Videos"
991+
\\
992+
,
993+
}},
994+
};
995+
defer system.deinit();
996+
997+
try testGetPath(.{
998+
.system = system,
999+
.folder = .videos,
1000+
.expected = "/mnt/Videos",
1001+
});
1002+
}
1003+
8941004
test "getPath - user-dirs.dirs - escaped path" {
8951005
if (builtin.os.tag == .windows) return error.SkipZigTest;
8961006

0 commit comments

Comments
 (0)