diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Messages.zig | 48 | ||||
| -rw-r--r-- | src/builtins.zig | 11 | ||||
| -rw-r--r-- | src/colors.zig | 18 | ||||
| -rw-r--r-- | src/dump.zig | 13 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 2 |
5 files changed, 78 insertions, 14 deletions
diff --git a/src/Messages.zig b/src/Messages.zig index a316377a..366445c7 100644 --- a/src/Messages.zig +++ b/src/Messages.zig @@ -486,13 +486,53 @@ pub fn reportError(p: *Pardes, id: usize, operation: []const u8, err: anyerror) p.fs.no_pane_slot_len = @intCast(w.buffered().len); return reportFailure(p, id, w.buffered()); } - for (@errorName(err), 0..) |c, i| { - if (std.ascii.isUpper(c) and i > 0) w.writeByte(' ') catch break; - w.writeByte(std.ascii.toLower(c)) catch break; - } + var words: [128]u8 = undefined; + w.writeAll(errorWords(err, &words)) catch {}; reportFailure(p, id, w.buffered()); } +/// An error in a person's words, never its Zig name: the few that read +/// badly spaced out said plainly, the rest spaced and lower case +/// (`FileNotFound`, "file not found"). +pub fn errorWords(err: anyerror, buf: []u8) []const u8 { + return switch (err) { + error.FileTooLarge => "too large", + error.NotAFile => "not a regular file", + error.ReadFailed => "could not be read", + error.ParseZon => "not valid ZON", + error.ThemeFileTooLarge => "too large: a theme file is at most 1 MiB", + error.InvalidThemeName => "its name is empty or over 255 bytes", + else => { + var w = std.Io.Writer.fixed(buf); + for (@errorName(err), 0..) |c, i| { + if (std.ascii.isUpper(c) and i > 0) w.writeByte(' ') catch break; + w.writeByte(std.ascii.toLower(c)) catch break; + } + return w.buffered(); + }, + }; +} + +/// A ZON parse failure in a person's words: the first error the parser +/// found and its line, `line 3: expected ','`. +pub fn zonWhy(diag: *const std.zon.parse.Diagnostics, buf: []u8) []const u8 { + var errors = diag.iterateErrors(); + const first = errors.next() orelse return "not valid ZON"; + const loc = first.getLocation(diag); + return std.fmt.bufPrint(buf, "line {d}: {f}", .{ loc.line + 1, first.fmtMessage(diag) }) catch "not valid ZON"; +} + +test "errors are said in words, and a ZON failure names its line" { + var buf: [128]u8 = undefined; + try std.testing.expectEqualStrings("file not found", errorWords(error.FileNotFound, &buf)); + try std.testing.expectEqualStrings("too large", errorWords(error.FileTooLarge, &buf)); + var diag: std.zon.parse.Diagnostics = .{}; + defer diag.deinit(std.testing.allocator); + const T = struct { a: u8 }; + try std.testing.expectError(error.ParseZon, std.zon.parse.fromSliceAlloc(T, std.testing.allocator, ".{\n .a = 1,\n .b = 2 }", &diag, .{})); + try std.testing.expect(std.mem.startsWith(u8, zonWhy(&diag, &buf), "line 3: ")); +} + /// The longest start of `text` that fits `max` bytes and ends between /// words (or, with no space to cut at, between characters). pub fn clip(text: []const u8, max: usize) []const u8 { diff --git a/src/builtins.zig b/src/builtins.zig index f51409aa..55779c8a 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -715,19 +715,22 @@ pub const Restore = struct { if (comptime pardes.hosted) { const bytes = @import("fs.zig").readRestore(c.p.gpa, path, c.p.settings.dump_dir.get()) catch |err| { var buf: [limits.host_path_cap + 64]u8 = undefined; + var words: [128]u8 = undefined; const why = switch (err) { error.FileNotFound => "no such file", error.PermissionDenied => "permission denied", error.IsDirectory => "a directory, not a dump", - else => @errorName(err), + else => pardes.Messages.errorWords(err, &words), }; return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: {s}", .{ path, why }) catch "Restore: cannot read the dump"); }; defer c.p.gpa.free(bytes); // Nor does a file that is not a dump, before any warning. - pardes.dump.check(c.p.gpa, bytes) catch |err| { - var buf: [limits.host_path_cap + 64]u8 = undefined; - return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: not a pardes dump ({s})", .{ path, @errorName(err) }) catch "Restore: not a pardes dump"); + var why_buf: [256]u8 = undefined; + pardes.dump.check(c.p.gpa, bytes, &why_buf) catch |err| { + var buf: [limits.host_path_cap + 320]u8 = undefined; + const why = if (err == error.ParseZon) why_buf[0..std.mem.indexOfScalar(u8, &why_buf, 0).?] else pardes.Messages.errorWords(err, &why_buf); + return c.p.reportFailure(c.id, std.fmt.bufPrint(&buf, "Restore: {s}: not a pardes dump: {s}", .{ path, why }) catch "Restore: not a pardes dump"); }; } // acme's Load adds a dump's windows to the ones there; a Restore diff --git a/src/colors.zig b/src/colors.zig index d04c009e..a45184c8 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -766,10 +766,15 @@ pub fn failThemeFile(p: *Pardes, generation: u32, err: anyerror) void { /// A theme file's failure, said with its path: `theme file x.zon: file /// not found`, not which file left to guess. fn themeFileError(p: *Pardes, request: ThemeFileRequest, err: anyerror) void { - var said: [300]u8 = undefined; + var words: [128]u8 = undefined; + themeFileFailure(p, request, pardes.Messages.errorWords(err, &words)); +} + +fn themeFileFailure(p: *Pardes, request: ThemeFileRequest, why: []const u8) void { + var said: [520]u8 = undefined; var cut: [200]u8 = undefined; const path = @import("ninep/tree.zig").fitErr(request.path, &cut); - p.reportError(request.pane, std.fmt.bufPrint(&said, "theme file {s}", .{path}) catch "theme file", err); + p.reportFailure(request.pane, std.fmt.bufPrint(&said, "theme file {s}: {s}", .{ path, why }) catch "theme file: not loaded"); revertThemeFile(p); } @@ -802,7 +807,11 @@ pub fn loadThemeFile(p: *Pardes, generation: u32, bytes: []const u8, animate: bo var diagnostics: std.zon.parse.Diagnostics = .{}; defer diagnostics.deinit(p.gpa); const parsed = std.zon.parse.fromSliceAlloc(Theme, p.gpa, source, &diagnostics, .{}) catch |err| { - themeFileError(p, request, err); + if (err != error.ParseZon) themeFileError(p, request, err) else { + // Which line, and what the parser wanted there. + var why: [200]u8 = undefined; + themeFileFailure(p, request, pardes.Messages.zonWhy(&diagnostics, &why)); + } return false; }; if (parsed.name.len == 0 or parsed.name.len > 255) { @@ -983,6 +992,9 @@ test "ThemeFile parses ZON atomically and Theme returns to the compiled ring" { // A half-written save is rejected without disturbing the last good value. try std.testing.expect(!loadThemeFile(p, request_effect.generation, ".{ .name = ", true)); + // Said with its line, in words: never a bare `parse zon`. + const said = p.panes[0].?.msg[0..p.panes[0].?.msg_len]; + try std.testing.expect(std.mem.indexOf(u8, said, ": line 1: ") != null and std.mem.indexOf(u8, said, "parse zon") == null); try std.testing.expectEqualStrings("mine", p.theme().name); try std.testing.expectEqual(@as(?[3]u8, .{ 1, 2, 3 }), p.theme().bg); diff --git a/src/dump.zig b/src/dump.zig index f8b99ef4..e76bb1a2 100644 --- a/src/dump.zig +++ b/src/dump.zig @@ -396,11 +396,20 @@ pub const Parsed = struct { /// Whether `bytes` are a dump a Restore could load, saying nothing: a /// Restore asks before it warns about unsaved text or replaces anything. -pub fn check(gpa: std.mem.Allocator, bytes: []const u8) !void { +/// Whether `bytes` are a dump; a ZON failure's line and reason are left in +/// `why`, ended by a 0. +pub fn check(gpa: std.mem.Allocator, bytes: []const u8, why: []u8) !void { var arena: std.heap.ArenaAllocator = .init(gpa); defer arena.deinit(); const source = try arena.allocator().dupeZ(u8, bytes); - try validate(try std.zon.parse.fromSliceAlloc(State, arena.allocator(), source, null, .{ .free_on_error = false })); + var diag: std.zon.parse.Diagnostics = .{}; + const state = std.zon.parse.fromSliceAlloc(State, arena.allocator(), source, &diag, .{ .free_on_error = false }) catch |err| { + const said = pardes.Messages.zonWhy(&diag, why[0 .. why.len - 1]); + if (said.ptr != why.ptr) @memcpy(why[0..said.len], said); + why[said.len] = 0; + return err; + }; + try validate(state); } pub fn readZon(gpa: std.mem.Allocator, bytes: []const u8, label: []const u8) !Parsed { diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index d6d7a6ee..7d8a370d 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -2287,7 +2287,7 @@ test "a Restore of a file that is no dump fails the write before any warning, an const root_ctl = @intFromEnum(tree.TopFile.ctl); const refused = wr(p, root_ctl, line); try testing.expectEqual(E.IO, refused.errno()); - try testing.expect(std.mem.indexOf(u8, refused.reply.ename, "not a pardes dump") != null); + try testing.expect(std.mem.indexOf(u8, refused.reply.ename, "not a pardes dump: line 1: ") != null); try testing.expect(p.restore_req == null); try testing.expect(p.panes[0].?.discard_warned == null); |
