From f5ba6a4465b5a85395f7a449d7c9edfee1ca83ef Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 29 Aug 2026 23:23:42 -0500 Subject: [PATCH] add helpers.MessageReader for reading message content Opens the file backing a message and exposes its raw content through a std.Io.Reader, mirroring MessageWriter's shape. The doctest reads a corpus message back byte for byte. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017i7R7ReKJAHGQpitxGXqrb --- README.md | 2 +- src/compile_check.zig | 5 +++++ src/helpers.zig | 50 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4dc9e11..06999fe 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ release memory sooner than the enclosing query would. | `notmuch.TagsIterator`, `notmuch.PropertiesIterator`, `notmuch.PairsIterator`, `notmuch.ValuesIterator` | iteration over tags, message properties, and configuration data | | `notmuch.Error` | the full set of `notmuch_status_t` codes as Zig errors, plus `error.Unexpected` for status codes a function is not documented to return | | `notmuch.compact`, `notmuch.builtWith`, `notmuch.tag_max` | module-level helpers | -| `notmuch.helpers.MessageWriter` | streaming an email through a `std.Io.Writer` into the mail store and indexing it (a convenience unique to these bindings) | +| `notmuch.helpers.MessageWriter`, `notmuch.helpers.MessageReader` | streaming an email through a `std.Io.Writer` into the mail store and indexing it, and reading a message's raw content through a `std.Io.Reader` (conveniences unique to these bindings) | Full API documentation is generated from the source and published at . To read it locally instead: diff --git a/src/compile_check.zig b/src/compile_check.zig index 0d3cad9..95ee664 100644 --- a/src/compile_check.zig +++ b/src/compile_check.zig @@ -124,6 +124,11 @@ test "every wrapper body compiles" { _ = indexopts.getDecryptPolicy(); indexopts.deinit(); + var message_reader: notmuch.helpers.MessageReader = undefined; + message_reader = try notmuch.helpers.MessageReader.init(undefined, message, &.{}); + _ = message_reader.reader(); + message_reader.deinit(); + var message_writer: notmuch.helpers.MessageWriter = undefined; message_writer = try notmuch.helpers.MessageWriter.init(undefined, db, &.{}, .{}); _ = message_writer.writer(); diff --git a/src/helpers.zig b/src/helpers.zig index 0bb03a1..cd36098 100644 --- a/src/helpers.zig +++ b/src/helpers.zig @@ -12,6 +12,56 @@ const Database = @import("Database.zig"); const IndexOpts = @import("IndexOpts.zig"); const Message = @import("Message.zig"); +/// Reads the contents of a message from the mail store through a +/// `std.Io.Reader`. +/// +/// The content is read from the file backing the message. For a message +/// stored under multiple filenames (identical message IDs), the file +/// reported by `Message.getFilename` — an arbitrary one of them — is opened. +/// +/// `buffer` is caller-owned and must remain valid until `deinit` is called. +pub const MessageReader = struct { + file_reader: std.Io.File.Reader, + + /// Open the file backing `message` for reading. Reads are buffered + /// through `buffer`. + pub fn init(io: std.Io, message: Message, buffer: []u8) !MessageReader { + const file = try std.Io.Dir.openFileAbsolute(io, try message.getFilename(), .{}); + return .{ .file_reader = file.reader(io, buffer) }; + } + + /// The `std.Io.Reader` producing the raw message content. + pub fn reader(self: *MessageReader) *std.Io.Reader { + return &self.file_reader.interface; + } + + /// Close the message file. + pub fn deinit(self: *MessageReader) void { + self.file_reader.file.close(self.file_reader.io); + self.* = undefined; + } +}; + +test MessageReader { + const fixture = @import("testing.zig"); + const alloc = std.testing.allocator; + + var test_db = try fixture.corpusDatabase(); + defer test_db.deinit(); + + const message = try test_db.database.findMessage("one@example.org") orelse return error.TestUnexpectedResult; + defer message.deinit(); + + var buffer: [256]u8 = undefined; + var message_reader = try MessageReader.init(std.testing.io, message, &buffer); + defer message_reader.deinit(); + + // The reader produces the raw message content, byte for byte. + const content = try message_reader.reader().allocRemaining(alloc, .unlimited); + defer alloc.free(content); + try std.testing.expectEqualStrings(fixture.corpus[0].data, content); +} + /// The maildir subdirectory a message is delivered into. pub const Subdir = enum { /// Mail that has not yet been seen by the user. -- 2.51.2