move code into better places (pt 1)
This commit is contained in:
parent
4718bce5af
commit
2febea12d7
4 changed files with 379 additions and 353 deletions
|
@ -76,7 +76,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
configure(exe, paths, reload_discovered_native_paths);
|
configure(lib_unit_tests, paths, reload_discovered_native_paths);
|
||||||
|
|
||||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||||
|
|
||||||
|
|
358
src/main.zig
358
src/main.zig
|
@ -1,5 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const notmuch = @import("notmuch.zig");
|
const root = @import("root.zig");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
|
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
|
||||||
|
@ -14,351 +14,15 @@ pub fn main() !void {
|
||||||
|
|
||||||
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
||||||
|
|
||||||
|
// Example of using the root.zig functionality
|
||||||
|
const allocator = std.heap.page_allocator;
|
||||||
|
var db_result = root.openNotmuchDb(allocator, "mail") catch |err| {
|
||||||
|
std.debug.print("Failed to open notmuch database: {}\n", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer db_result.close();
|
||||||
|
|
||||||
|
std.debug.print("Successfully opened notmuch database at: {s}\n", .{db_result.path});
|
||||||
|
|
||||||
try bw.flush(); // don't forget to flush!
|
try bw.flush(); // don't forget to flush!
|
||||||
}
|
}
|
||||||
|
|
||||||
test "can get status" {
|
|
||||||
// const allocator = std.testing.allocator;
|
|
||||||
// const db_path = try std.fs.path.join(
|
|
||||||
// allocator,
|
|
||||||
// std.fs.cwd(),
|
|
||||||
// "mail",
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Current directory under test is root of project
|
|
||||||
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
||||||
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
|
||||||
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
|
||||||
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
|
||||||
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
|
||||||
{
|
|
||||||
var status: notmuch.Status = undefined;
|
|
||||||
var db = try notmuch.Db.open(db_path, &status);
|
|
||||||
defer db.deinit();
|
|
||||||
defer db.close();
|
|
||||||
defer status.deinit();
|
|
||||||
try std.testing.expectEqualStrings("No error occurred", status.statusString());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var db = try notmuch.Db.open(db_path, null);
|
|
||||||
defer db.deinit();
|
|
||||||
defer db.close();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var status: notmuch.Status = undefined;
|
|
||||||
try std.testing.expectError(error.CouldNotOpenDatabase, notmuch.Db.open(
|
|
||||||
"NON-EXISTANT",
|
|
||||||
&status,
|
|
||||||
));
|
|
||||||
defer status.deinit();
|
|
||||||
try std.testing.expectEqualStrings(
|
|
||||||
"Path supplied is illegal for this function",
|
|
||||||
status.statusString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// // This is the python that's executing
|
|
||||||
// // def get(self, thread_id):
|
|
||||||
// // threads = notmuch.Query(
|
|
||||||
// // get_db(), "thread:{}".format(thread_id)
|
|
||||||
// // ).search_threads()
|
|
||||||
// // thread = next(threads) # there can be only 1
|
|
||||||
// // messages = thread.get_messages()
|
|
||||||
// // return messages_to_json(messages)
|
|
||||||
// try std.testing.expectEqualStrings("No error occurred", std.mem.span(c.notmuch_status_to_string(open_status)));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "can search threads" {
|
|
||||||
// const allocator = std.testing.allocator;
|
|
||||||
// const db_path = try std.fs.path.join(
|
|
||||||
// allocator,
|
|
||||||
// std.fs.cwd(),
|
|
||||||
// "mail",
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Current directory under test is root of project
|
|
||||||
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
||||||
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
|
||||||
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
|
||||||
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
|
||||||
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
|
||||||
{
|
|
||||||
var status: notmuch.Status = undefined;
|
|
||||||
var db = try notmuch.Db.open(db_path, &status);
|
|
||||||
defer db.deinit();
|
|
||||||
defer db.close();
|
|
||||||
defer status.deinit();
|
|
||||||
try std.testing.expectEqualStrings("No error occurred", status.statusString());
|
|
||||||
var t_iter = try db.searchThreads("Tablets");
|
|
||||||
defer t_iter.deinit();
|
|
||||||
var inx: usize = 0;
|
|
||||||
while (t_iter.next()) |t| : (inx += 1) {
|
|
||||||
defer t.deinit();
|
|
||||||
try std.testing.expectEqual(@as(c_int, 1), t.getTotalMessages());
|
|
||||||
try std.testing.expectEqualStrings("0000000000000001", t.getThreadId());
|
|
||||||
var message_iter = try t.getMessages();
|
|
||||||
var jnx: usize = 0;
|
|
||||||
while (message_iter.next()) |m| : (jnx += 1) {
|
|
||||||
defer m.deinit();
|
|
||||||
try std.testing.expectStringEndsWith(m.getFilename(), "/1721591945.R4187135327503631514.nucman:2,S");
|
|
||||||
}
|
|
||||||
try std.testing.expectEqual(@as(usize, 1), jnx);
|
|
||||||
}
|
|
||||||
try std.testing.expectEqual(@as(usize, 1), inx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the json we're looking to match on api/query/<term>
|
|
||||||
// [
|
|
||||||
// {
|
|
||||||
// "authors": "The Washington Post",
|
|
||||||
// "matched_messages": 1,
|
|
||||||
// "newest_date": 1721664948,
|
|
||||||
// "oldest_date": 1721664948,
|
|
||||||
// "subject": "Biden is out. What now?",
|
|
||||||
// "tags": [
|
|
||||||
// "inbox",
|
|
||||||
// "unread"
|
|
||||||
// ],
|
|
||||||
// "thread_id": "0000000000031723",
|
|
||||||
// "total_messages": 1
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "authors": "The Washington Post",
|
|
||||||
// "matched_messages": 1,
|
|
||||||
// "newest_date": 1721603115,
|
|
||||||
// "oldest_date": 1721603115,
|
|
||||||
// "subject": "Upcoming Virtual Programs",
|
|
||||||
// "tags": [
|
|
||||||
// "inbox",
|
|
||||||
// "unread"
|
|
||||||
// ],
|
|
||||||
// "thread_id": "0000000000031712",
|
|
||||||
// "total_messages": 1
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "authors": "The Washington Post",
|
|
||||||
// "matched_messages": 1,
|
|
||||||
// "newest_date": 1721590157,
|
|
||||||
// "oldest_date": 1721590157,
|
|
||||||
// "subject": "Biden Steps Aside",
|
|
||||||
// "tags": [
|
|
||||||
// "inbox"
|
|
||||||
// ],
|
|
||||||
// "thread_id": "000000000003170d",
|
|
||||||
// "total_messages": 1
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
//
|
|
||||||
// And on api/thread/<threadid>
|
|
||||||
//
|
|
||||||
// [
|
|
||||||
// {
|
|
||||||
// "from": "The Washington Post <email@washingtonpost.com>",
|
|
||||||
// "to": "elerch@lerch.org",
|
|
||||||
// "cc": null,
|
|
||||||
// "bcc": null,
|
|
||||||
// "date": "Sun, 21 Jul 2024 19:23:38 +0000",
|
|
||||||
// "subject": "Biden steps aside",
|
|
||||||
// "content": "...content...",
|
|
||||||
// "content_type": "text/html",
|
|
||||||
// "attachments": [],
|
|
||||||
// "message_id": "01010190d6bfe4e1-185e2720-e415-4086-8865-9604cde886c2-000000@us-west-2.amazonses.com"
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
}
|
|
||||||
|
|
||||||
const Thread = struct {
|
|
||||||
thread: *notmuch.Db.Thread,
|
|
||||||
|
|
||||||
pub fn init(t: *notmuch.Db.Thread) Thread {
|
|
||||||
return .{ .thread = t };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn jsonStringify(self: Thread, jws: anytype) !void {
|
|
||||||
try jws.beginArray();
|
|
||||||
var mi = self.thread.getMessages() catch return error.OutOfMemory;
|
|
||||||
while (mi.next()) |m| {
|
|
||||||
try jws.beginObject();
|
|
||||||
try jws.objectField("from");
|
|
||||||
try jws.write(m.getHeader("from") catch return error.OutOfMemory);
|
|
||||||
try jws.objectField("to");
|
|
||||||
try jws.write(m.getHeader("to") catch return error.OutOfMemory);
|
|
||||||
try jws.objectField("cc");
|
|
||||||
try jws.write(m.getHeader("cc") catch return error.OutOfMemory);
|
|
||||||
try jws.objectField("bcc");
|
|
||||||
try jws.write(m.getHeader("bcc") catch return error.OutOfMemory);
|
|
||||||
try jws.objectField("date");
|
|
||||||
try jws.write(m.getHeader("date") catch return error.OutOfMemory);
|
|
||||||
try jws.objectField("subject");
|
|
||||||
try jws.write(m.getHeader("subject") catch return error.OutOfMemory);
|
|
||||||
try jws.objectField("content");
|
|
||||||
try jws.write(m.getFilename()); // TODO: Parse file
|
|
||||||
try jws.objectField("content-type");
|
|
||||||
try jws.write(m.getHeader("Content-Type") catch return error.OutOfMemory);
|
|
||||||
|
|
||||||
try jws.objectField("message_id");
|
|
||||||
try jws.write(m.getMessageId());
|
|
||||||
try jws.endObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
try jws.endArray();
|
|
||||||
//[
|
|
||||||
// {
|
|
||||||
// "from": "The Washington Post <email@washingtonpost.com>",
|
|
||||||
// "to": "elerch@lerch.org",
|
|
||||||
// "cc": null,
|
|
||||||
// "bcc": null,
|
|
||||||
// "date": "Sun, 21 Jul 2024 19:23:38 +0000",
|
|
||||||
// "subject": "Biden steps aside",
|
|
||||||
// "content": "...content...",
|
|
||||||
// "content_type": "text/html",
|
|
||||||
// "attachments": [],
|
|
||||||
// "message_id": "01010190d6bfe4e1-185e2720-e415-4086-8865-9604cde886c2-000000@us-west-2.amazonses.com"
|
|
||||||
// }
|
|
||||||
//]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Threads = struct {
|
|
||||||
iterator: *notmuch.Db.ThreadIterator,
|
|
||||||
|
|
||||||
pub fn init(it: *notmuch.Db.ThreadIterator) Threads {
|
|
||||||
return .{
|
|
||||||
.iterator = it,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn jsonStringify(self: Threads, jws: anytype) !void {
|
|
||||||
// jws should be this:
|
|
||||||
// https://ziglang.org/documentation/0.13.0/std/#std.json.stringify.WriteStream
|
|
||||||
try jws.beginArray();
|
|
||||||
while (self.iterator.next()) |t| {
|
|
||||||
defer t.deinit();
|
|
||||||
try jws.beginObject();
|
|
||||||
// {
|
|
||||||
// "authors": "The Washington Post",
|
|
||||||
// "matched_messages": 1,
|
|
||||||
// "newest_date": 1721664948,
|
|
||||||
// "oldest_date": 1721664948,
|
|
||||||
// "subject": "Biden is out. What now?",
|
|
||||||
// "tags": [
|
|
||||||
// "inbox",
|
|
||||||
// "unread"
|
|
||||||
// ],
|
|
||||||
// "thread_id": "0000000000031723",
|
|
||||||
// "total_messages": 1
|
|
||||||
// },
|
|
||||||
try jws.objectField("authors");
|
|
||||||
try jws.write(t.getAuthors());
|
|
||||||
try jws.objectField("matched_messages");
|
|
||||||
try jws.write(t.getMatchedMessages());
|
|
||||||
try jws.objectField("newest_date");
|
|
||||||
try jws.write(t.getNewestDate());
|
|
||||||
try jws.objectField("oldest_date");
|
|
||||||
try jws.write(t.getOldestDate());
|
|
||||||
try jws.objectField("subject");
|
|
||||||
try jws.write(t.getSubject());
|
|
||||||
try jws.objectField("tags");
|
|
||||||
var tags = t.getTags() catch return error.OutOfMemory;
|
|
||||||
try tags.jsonStringify(jws);
|
|
||||||
try jws.objectField("thread_id");
|
|
||||||
try jws.write(t.getThreadId());
|
|
||||||
try jws.objectField("total_messages");
|
|
||||||
try jws.write(t.getTotalMessages());
|
|
||||||
|
|
||||||
try jws.endObject();
|
|
||||||
}
|
|
||||||
try jws.endArray();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test "can stringify general queries" {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
// const db_path = try std.fs.path.join(
|
|
||||||
// allocator,
|
|
||||||
// std.fs.cwd(),
|
|
||||||
// "mail",
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Current directory under test is root of project
|
|
||||||
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
||||||
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
|
||||||
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
|
||||||
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
|
||||||
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
|
||||||
{
|
|
||||||
var status: notmuch.Status = undefined;
|
|
||||||
var db = try notmuch.Db.open(db_path, &status);
|
|
||||||
defer db.deinit();
|
|
||||||
defer db.close();
|
|
||||||
defer status.deinit();
|
|
||||||
var al = std.ArrayList(u8).init(allocator);
|
|
||||||
defer al.deinit();
|
|
||||||
var ti = try db.searchThreads("Tablets");
|
|
||||||
defer ti.deinit();
|
|
||||||
try std.json.stringify(Threads.init(&ti), .{ .whitespace = .indent_2 }, al.writer());
|
|
||||||
const actual = al.items;
|
|
||||||
try std.testing.expectEqualStrings(
|
|
||||||
\\[
|
|
||||||
\\ {
|
|
||||||
\\ "authors": "Top Medications",
|
|
||||||
\\ "matched_messages": 1,
|
|
||||||
\\ "newest_date": 1721484138,
|
|
||||||
\\ "oldest_date": 1721484138,
|
|
||||||
\\ "subject": "***SPAM*** Tablets without a prescription",
|
|
||||||
\\ "tags": [
|
|
||||||
\\ "inbox"
|
|
||||||
\\ ],
|
|
||||||
\\ "thread_id": "0000000000000001",
|
|
||||||
\\ "total_messages": 1
|
|
||||||
\\ }
|
|
||||||
\\]
|
|
||||||
, actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "can stringify specific threads" {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
// const db_path = try std.fs.path.join(
|
|
||||||
// allocator,
|
|
||||||
// std.fs.cwd(),
|
|
||||||
// "mail",
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Current directory under test is root of project
|
|
||||||
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
||||||
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
|
||||||
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
|
||||||
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
|
||||||
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
|
||||||
{
|
|
||||||
var status: notmuch.Status = undefined;
|
|
||||||
var db = try notmuch.Db.open(db_path, &status);
|
|
||||||
defer db.deinit();
|
|
||||||
defer db.close();
|
|
||||||
defer status.deinit();
|
|
||||||
var al = std.ArrayList(u8).init(allocator);
|
|
||||||
defer al.deinit();
|
|
||||||
var ti = try db.searchThreads("Tablets");
|
|
||||||
defer ti.deinit();
|
|
||||||
var t = ti.next().?;
|
|
||||||
try std.json.stringify(Thread.init(&t), .{ .whitespace = .indent_2 }, al.writer());
|
|
||||||
const actual = al.items;
|
|
||||||
try std.testing.expectEqualStrings(
|
|
||||||
\\[
|
|
||||||
\\ {
|
|
||||||
\\ "from": "Top Medications <mail@youpharm.co>",
|
|
||||||
\\ "to": "emil@lerch.org",
|
|
||||||
\\ "cc": null,
|
|
||||||
\\ "bcc": null,
|
|
||||||
\\ "date": "Sat, 20 Jul 2024 16:02:18 +0200",
|
|
||||||
\\ "subject": "***SPAM*** Tablets without a prescription",
|
|
||||||
\\ "content": "...content...",
|
|
||||||
\\ "content_type": "text/html",
|
|
||||||
\\ "attachments": [],
|
|
||||||
\\ "message_id": "01010190d6bfe4e1-185e2720-e415-4086-8865-9604cde886c2-000000@us-west-2.amazonses.com"
|
|
||||||
\\ }
|
|
||||||
\\]
|
|
||||||
, actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -294,3 +294,76 @@ pub const Db = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "can get status" {
|
||||||
|
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
||||||
|
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
||||||
|
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
||||||
|
{
|
||||||
|
var status: Status = undefined;
|
||||||
|
var db = try Db.open(db_path, &status);
|
||||||
|
defer db.deinit();
|
||||||
|
defer db.close();
|
||||||
|
defer status.deinit();
|
||||||
|
try std.testing.expectEqualStrings("No error occurred", status.statusString());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var db = try Db.open(db_path, null);
|
||||||
|
defer db.deinit();
|
||||||
|
defer db.close();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var status: Status = undefined;
|
||||||
|
try std.testing.expectError(error.CouldNotOpenDatabase, Db.open(
|
||||||
|
"NON-EXISTANT",
|
||||||
|
&status,
|
||||||
|
));
|
||||||
|
defer status.deinit();
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
"Path supplied is illegal for this function",
|
||||||
|
status.statusString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "can search threads" {
|
||||||
|
// const allocator = std.testing.allocator;
|
||||||
|
// const db_path = try std.fs.path.join(
|
||||||
|
// allocator,
|
||||||
|
// std.fs.cwd(),
|
||||||
|
// "mail",
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Current directory under test is root of project
|
||||||
|
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
||||||
|
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
||||||
|
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
||||||
|
{
|
||||||
|
var status: Status = undefined;
|
||||||
|
var db = try Db.open(db_path, &status);
|
||||||
|
defer db.deinit();
|
||||||
|
defer db.close();
|
||||||
|
defer status.deinit();
|
||||||
|
try std.testing.expectEqualStrings("No error occurred", status.statusString());
|
||||||
|
var t_iter = try db.searchThreads("Tablets");
|
||||||
|
defer t_iter.deinit();
|
||||||
|
var inx: usize = 0;
|
||||||
|
while (t_iter.next()) |t| : (inx += 1) {
|
||||||
|
defer t.deinit();
|
||||||
|
try std.testing.expectEqual(@as(c_int, 1), t.getTotalMessages());
|
||||||
|
try std.testing.expectEqualStrings("0000000000000001", t.getThreadId());
|
||||||
|
var message_iter = try t.getMessages();
|
||||||
|
var jnx: usize = 0;
|
||||||
|
while (message_iter.next()) |m| : (jnx += 1) {
|
||||||
|
defer m.deinit();
|
||||||
|
try std.testing.expectStringEndsWith(m.getFilename(), "/1721591945.R4187135327503631514.nucman:2,S");
|
||||||
|
}
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), jnx);
|
||||||
|
}
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), inx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
299
src/root.zig
299
src/root.zig
|
@ -1,10 +1,299 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
const notmuch = @import("notmuch.zig");
|
||||||
|
|
||||||
export fn add(a: i32, b: i32) i32 {
|
// Thread representation for JSON serialization
|
||||||
return a + b;
|
pub const Thread = struct {
|
||||||
|
thread: *notmuch.Db.Thread,
|
||||||
|
|
||||||
|
pub fn init(t: *notmuch.Db.Thread) Thread {
|
||||||
|
return .{ .thread = t };
|
||||||
}
|
}
|
||||||
|
|
||||||
test "basic add functionality" {
|
pub fn jsonStringify(self: Thread, jws: anytype) !void {
|
||||||
try testing.expect(add(3, 7) == 10);
|
try jws.beginArray();
|
||||||
|
var mi = self.thread.getMessages() catch return error.OutOfMemory;
|
||||||
|
while (mi.next()) |m| {
|
||||||
|
try jws.beginObject();
|
||||||
|
try jws.objectField("from");
|
||||||
|
try jws.write(m.getHeader("from") catch return error.OutOfMemory);
|
||||||
|
try jws.objectField("to");
|
||||||
|
try jws.write(m.getHeader("to") catch return error.OutOfMemory);
|
||||||
|
try jws.objectField("cc");
|
||||||
|
try jws.write(m.getHeader("cc") catch return error.OutOfMemory);
|
||||||
|
try jws.objectField("bcc");
|
||||||
|
try jws.write(m.getHeader("bcc") catch return error.OutOfMemory);
|
||||||
|
try jws.objectField("date");
|
||||||
|
try jws.write(m.getHeader("date") catch return error.OutOfMemory);
|
||||||
|
try jws.objectField("subject");
|
||||||
|
try jws.write(m.getHeader("subject") catch return error.OutOfMemory);
|
||||||
|
// content, content-type, and attachments are all based on the file itself
|
||||||
|
try jws.objectField("content");
|
||||||
|
try jws.write(m.getFilename()); // TODO: Parse file
|
||||||
|
try jws.objectField("content-type");
|
||||||
|
try jws.write(m.getHeader("Content-Type") catch return error.OutOfMemory);
|
||||||
|
|
||||||
|
try jws.objectField("message_id");
|
||||||
|
try jws.write(m.getMessageId());
|
||||||
|
try jws.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
try jws.endArray();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Threads collection for JSON serialization
|
||||||
|
pub const Threads = struct {
|
||||||
|
iterator: *notmuch.Db.ThreadIterator,
|
||||||
|
|
||||||
|
pub fn init(it: *notmuch.Db.ThreadIterator) Threads {
|
||||||
|
return .{
|
||||||
|
.iterator = it,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jsonStringify(self: Threads, jws: anytype) !void {
|
||||||
|
//[
|
||||||
|
// {
|
||||||
|
// "from": "The Washington Post <email@washingtonpost.com>",
|
||||||
|
// "to": "elerch@lerch.org",
|
||||||
|
// "cc": null,
|
||||||
|
// "bcc": null,
|
||||||
|
// "date": "Sun, 21 Jul 2024 19:23:38 +0000",
|
||||||
|
// "subject": "Biden steps aside",
|
||||||
|
// "content": "...content...",
|
||||||
|
// "content_type": "text/html",
|
||||||
|
// "attachments": [],
|
||||||
|
// "message_id": "01010190d6bfe4e1-185e2720-e415-4086-8865-9604cde886c2-000000@us-west-2.amazonses.com"
|
||||||
|
// }
|
||||||
|
//]
|
||||||
|
try jws.beginArray();
|
||||||
|
while (self.iterator.next()) |t| {
|
||||||
|
defer t.deinit();
|
||||||
|
try jws.beginObject();
|
||||||
|
try jws.objectField("authors");
|
||||||
|
try jws.write(t.getAuthors());
|
||||||
|
try jws.objectField("matched_messages");
|
||||||
|
try jws.write(t.getMatchedMessages());
|
||||||
|
try jws.objectField("newest_date");
|
||||||
|
try jws.write(t.getNewestDate());
|
||||||
|
try jws.objectField("oldest_date");
|
||||||
|
try jws.write(t.getOldestDate());
|
||||||
|
try jws.objectField("subject");
|
||||||
|
try jws.write(t.getSubject());
|
||||||
|
try jws.objectField("tags");
|
||||||
|
var tags = t.getTags() catch return error.OutOfMemory;
|
||||||
|
try tags.jsonStringify(jws);
|
||||||
|
try jws.objectField("thread_id");
|
||||||
|
try jws.write(t.getThreadId());
|
||||||
|
try jws.objectField("total_messages");
|
||||||
|
try jws.write(t.getTotalMessages());
|
||||||
|
try jws.endObject();
|
||||||
|
}
|
||||||
|
try jws.endArray();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to open a notmuch database from the current directory
|
||||||
|
pub const NotmuchDb = struct {
|
||||||
|
db: notmuch.Db,
|
||||||
|
path: [:0]u8,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn close(self: *NotmuchDb) void {
|
||||||
|
self.db.close();
|
||||||
|
self.db.deinit();
|
||||||
|
self.allocator.free(self.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn searchThreads(self: *NotmuchDb, query: []const u8) !Threads {
|
||||||
|
var query_buf: [1024:0]u8 = undefined;
|
||||||
|
const query_z = try std.fmt.bufPrintZ(&query_buf, "{s}", .{query});
|
||||||
|
var thread_iter = try self.db.searchThreads(query_z);
|
||||||
|
return Threads.init(&thread_iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getThread(self: *NotmuchDb, thread_id: []const u8) !Thread {
|
||||||
|
var query_buf: [1024:0]u8 = undefined;
|
||||||
|
const query_z = try std.fmt.bufPrintZ(&query_buf, "thread:{s}", .{thread_id});
|
||||||
|
var thread_iter = try self.db.searchThreads(query_z);
|
||||||
|
defer thread_iter.deinit();
|
||||||
|
|
||||||
|
var thread = thread_iter.next();
|
||||||
|
if (thread) |_| return Thread.init(&thread.?);
|
||||||
|
return error.ThreadNotFound;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn openNotmuchDb(allocator: std.mem.Allocator, relative_path: []const u8) !NotmuchDb {
|
||||||
|
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
||||||
|
const db_path = try std.fs.path.joinZ(allocator, &[_][]const u8{ cwd, relative_path });
|
||||||
|
|
||||||
|
const db = try notmuch.Db.open(db_path, null);
|
||||||
|
return .{
|
||||||
|
.db = db,
|
||||||
|
.path = db_path,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ensure all references are observed" {
|
||||||
|
std.testing.refAllDeclsRecursive(@This());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "open database with public api" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var db = try openNotmuchDb(allocator, "mail");
|
||||||
|
defer db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the json we're looking to match on api/query/<term>
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// "authors": "The Washington Post",
|
||||||
|
// "matched_messages": 1,
|
||||||
|
// "newest_date": 1721664948,
|
||||||
|
// "oldest_date": 1721664948,
|
||||||
|
// "subject": "Biden is out. What now?",
|
||||||
|
// "tags": [
|
||||||
|
// "inbox",
|
||||||
|
// "unread"
|
||||||
|
// ],
|
||||||
|
// "thread_id": "0000000000031723",
|
||||||
|
// "total_messages": 1
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "authors": "The Washington Post",
|
||||||
|
// "matched_messages": 1,
|
||||||
|
// "newest_date": 1721603115,
|
||||||
|
// "oldest_date": 1721603115,
|
||||||
|
// "subject": "Upcoming Virtual Programs",
|
||||||
|
// "tags": [
|
||||||
|
// "inbox",
|
||||||
|
// "unread"
|
||||||
|
// ],
|
||||||
|
// "thread_id": "0000000000031712",
|
||||||
|
// "total_messages": 1
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "authors": "The Washington Post",
|
||||||
|
// "matched_messages": 1,
|
||||||
|
// "newest_date": 1721590157,
|
||||||
|
// "oldest_date": 1721590157,
|
||||||
|
// "subject": "Biden Steps Aside",
|
||||||
|
// "tags": [
|
||||||
|
// "inbox"
|
||||||
|
// ],
|
||||||
|
// "thread_id": "000000000003170d",
|
||||||
|
// "total_messages": 1
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
//
|
||||||
|
// And on api/thread/<threadid>
|
||||||
|
//
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// "from": "The Washington Post <email@washingtonpost.com>",
|
||||||
|
// "to": "elerch@lerch.org",
|
||||||
|
// "cc": null,
|
||||||
|
// "bcc": null,
|
||||||
|
// "date": "Sun, 21 Jul 2024 19:23:38 +0000",
|
||||||
|
// "subject": "Biden steps aside",
|
||||||
|
// "content": "...content...",
|
||||||
|
// "content_type": "text/html",
|
||||||
|
// "attachments": [],
|
||||||
|
// "message_id": "01010190d6bfe4e1-185e2720-e415-4086-8865-9604cde886c2-000000@us-west-2.amazonses.com"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
|
||||||
|
test "can stringify general queries" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
// const db_path = try std.fs.path.join(
|
||||||
|
// allocator,
|
||||||
|
// std.fs.cwd(),
|
||||||
|
// "mail",
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Current directory under test is root of project
|
||||||
|
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
||||||
|
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
||||||
|
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
||||||
|
{
|
||||||
|
var status: notmuch.Status = undefined;
|
||||||
|
var db = try notmuch.Db.open(db_path, &status);
|
||||||
|
defer db.deinit();
|
||||||
|
defer db.close();
|
||||||
|
defer status.deinit();
|
||||||
|
var al = std.ArrayList(u8).init(allocator);
|
||||||
|
defer al.deinit();
|
||||||
|
var ti = try db.searchThreads("Tablets");
|
||||||
|
defer ti.deinit();
|
||||||
|
try std.json.stringify(Threads.init(&ti), .{ .whitespace = .indent_2 }, al.writer());
|
||||||
|
const actual = al.items;
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\[
|
||||||
|
\\ {
|
||||||
|
\\ "authors": "Top Medications",
|
||||||
|
\\ "matched_messages": 1,
|
||||||
|
\\ "newest_date": 1721484138,
|
||||||
|
\\ "oldest_date": 1721484138,
|
||||||
|
\\ "subject": "***SPAM*** Tablets without a prescription",
|
||||||
|
\\ "tags": [
|
||||||
|
\\ "inbox"
|
||||||
|
\\ ],
|
||||||
|
\\ "thread_id": "0000000000000001",
|
||||||
|
\\ "total_messages": 1
|
||||||
|
\\ }
|
||||||
|
\\]
|
||||||
|
, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "can stringify specific threads" {
|
||||||
|
if (true) return error.SkipZigTest;
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
// const db_path = try std.fs.path.join(
|
||||||
|
// allocator,
|
||||||
|
// std.fs.cwd(),
|
||||||
|
// "mail",
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Current directory under test is root of project
|
||||||
|
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
||||||
|
var path_buf: [std.fs.max_path_bytes:0]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(path_buf[0..]);
|
||||||
|
const db_path = try std.fs.path.joinZ(fba.allocator(), &[_][]const u8{ cwd, "mail" });
|
||||||
|
{
|
||||||
|
var status: notmuch.Status = undefined;
|
||||||
|
var db = try notmuch.Db.open(db_path, &status);
|
||||||
|
defer db.deinit();
|
||||||
|
defer db.close();
|
||||||
|
defer status.deinit();
|
||||||
|
var al = std.ArrayList(u8).init(allocator);
|
||||||
|
defer al.deinit();
|
||||||
|
var ti = try db.searchThreads("Tablets");
|
||||||
|
defer ti.deinit();
|
||||||
|
var t = ti.next().?;
|
||||||
|
try std.json.stringify(Thread.init(&t), .{ .whitespace = .indent_2 }, al.writer());
|
||||||
|
const actual = al.items;
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
\\[
|
||||||
|
\\ {
|
||||||
|
\\ "from": "Top Medications <mail@youpharm.co>",
|
||||||
|
\\ "to": "emil@lerch.org",
|
||||||
|
\\ "cc": null,
|
||||||
|
\\ "bcc": null,
|
||||||
|
\\ "date": "Sat, 20 Jul 2024 16:02:18 +0200",
|
||||||
|
\\ "subject": "***SPAM*** Tablets without a prescription",
|
||||||
|
\\ "content": "...content...",
|
||||||
|
\\ "content_type": "text/html",
|
||||||
|
\\ "attachments": [],
|
||||||
|
\\ "message_id": "01010190d6bfe4e1-185e2720-e415-4086-8865-9604cde886c2-000000@us-west-2.amazonses.com"
|
||||||
|
\\ }
|
||||||
|
\\]
|
||||||
|
, actual);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue