From cca3fe38db0f3580b6ddf6255b1ec192087c3620 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Mon, 2 Mar 2026 19:37:51 -0600 Subject: [PATCH] more documentation and api updates --- src/Database.zig | 136 +++++++++++++++++++++++++++++++---------------- src/Message.zig | 6 +-- src/Query.zig | 98 +++++++++++++++++++++++----------- src/enums.zig | 18 +++---- src/error.zig | 2 +- src/notmuch.zig | 4 +- 6 files changed, 173 insertions(+), 91 deletions(-) diff --git a/src/Database.zig b/src/Database.zig index d1fd71e..8129439 100644 --- a/src/Database.zig +++ b/src/Database.zig @@ -15,16 +15,17 @@ const wrap = @import("error.zig").wrap; const wrapMessage = @import("error.zig").wrapMessage; const enums = @import("enums.zig"); -pub const Config = enums.CONFIG; -pub const Mode = enums.DATABASE_MODE; +pub const Config = enums.Config; +pub const Mode = enums.DatabaseMode; const Decrypt = enums.DECRYPT; -const QUERY_SYNTAX = enums.QUERY_SYNTAX; -const STATUS = enums.STATUS; +const QuerySyntax = enums.QuerySyntax; +const STATUS = enums.Status; const status = enums.status; const Directory = @import("Directory.zig"); const Message = @import("Message.zig"); const Query = @import("Query.zig"); +const TagsIterator = @import("TagsIterator.zig"); /// A callback invoked by `compact` to notify the user of the progress of the /// compaction process. @@ -334,10 +335,10 @@ pub fn destroy(self: *const Database) Error!void { } } -/// Return the database path of the given database. +/// Return the database path of the database. /// -/// The return value is a string owned by notmuch so should not be modified nor -/// freed by the caller. +/// The return value is a string owned by `notmuch` so should not be modified +/// nor freed by the caller. pub fn getPath(self: *const Database) ?[:0]const u8 { return std.mem.span(c.notmuch_database_get_path(self.database) orelse return null); } @@ -400,8 +401,8 @@ pub fn beginAtomic(self: *const Database) error{XapianException}!void { } /// Indicate the end of an atomic database operation. If repeated (with matching -/// notmuch_database_begin_atomic) "database.autocommit" times, commit the the -/// transaction and all previous (non-cancelled) transactions to the database. +/// `beginAtomic`) "database.autocommit" times, commit the the transaction and +/// all previous (non-cancelled) transactions to the database. pub fn endAtomic(self: *const Database) error{ UnbalancedAtomic, XapianException }!void { switch (status(c.notmuch_database_begin_atomic(self.database))) { .success => {}, @@ -453,6 +454,7 @@ pub fn getRevision(self: *const Database) Revision { .uuid = std.mem.span(uuid), }; } + /// Retrieve a directory object from the database for `path`. /// /// Here, 'path' should be a path relative to the path of `database` (see @@ -467,16 +469,14 @@ pub fn getRevision(self: *const Database) Revision { pub fn getDirectory(self: *const Database, path: [:0]const u8) Error!?Directory { var directory: ?*c.notmuch_directory_t = null; - switch (status(c.notmuch_database_get_directory(self.database, path, &directory))) { - .success => {}, - .null_pointer => return error.NullPointer, - .upgrade_required => return error.UpgradeRequired, - .xapian_exception => return error.XapianException, + return switch (status(c.notmuch_database_get_directory(self.database, path, &directory))) { + .success => .{ + .directory = directory orelse return null, + }, + .null_pointer => error.NullPointer, + .upgrade_required => error.UpgradeRequired, + .xapian_exception => error.XapianException, else => unreachable, - } - - return .{ - .directory = directory orelse return null, }; } @@ -672,6 +672,79 @@ pub fn findMessageByFilename(self: *const Database, filename: [:0]const u8) Find }; } +/// Return a list of all tags found in the database. +/// +/// This function creates a list of all tags found in the database. The +/// resulting list contains all tags from all messages found in the database. +/// +/// On error this function returns `null`. +pub fn getAllTags(self: *const Database) ?TagsIterator { + return .{ + .tags = c.notmuch_database_get_all_tags(self.database) orelse return null, + }; +} + +pub const ReopenError = error{ + /// The database was not open. + IllegalArgument, + /// A Xapian exception occurred. + XapianException, +}; + +/// Reopen an open notmuch database. +pub fn reopen(self: *Database, mode: Mode) ReopenError!void { + return switch (status(c.notmuch_database_reopen(self.database, @intFromEnum(mode)))) { + .success => {}, + .illegal_argument => error.IllegalArgument, + .xapian_exception => error.XapianException, + else => unreachable, + }; +} + +/// Create a new query. +/// +/// For the query string, we'll document the syntax here more completely in the +/// future, but it's likely to be a specialized version of the general Xapian +/// query syntax: +/// +/// https://xapian.org/docs/queryparser.html +/// +/// As a special case, passing either a length-zero string, (that is ""), or a +/// string consisting of a single asterisk (that is "*"), will result in a query +/// that returns all messages in the database. +/// +/// See `Query.setSort` for controlling the order of results. See +/// `Query.searchMessages` and `Query.searchThreads` to actually execute the +/// query. +pub fn queryCreate(self: *const Database, query_string: [:0]const u8) error{OutOfMemory}!Query { + return .init(c.notmuch_query_create(self.database, query_string) orelse return error.OutOfMemory); +} + +/// Create a new query. +/// +/// For the query string, we'll document the syntax here more completely in the +/// future, but it's likely to be a specialized version of the general Xapian +/// query syntax: +/// +/// https://xapian.org/docs/queryparser.html +/// +/// As a special case, passing either a length-zero string, (that is `""`), or +/// a string consisting of a single asterisk (that is `"*"`), will result in a +/// query that returns all messages in the database. +/// +/// See `Query.setSort` for controlling the order of results. See +/// `Query.searchMessages` and `Query.searchThreads` to actually execute the +/// query. +/// +/// User should call `Query.deinit` when finished with this query. +pub fn queryCreateWithSyntax(self: *const Database, query_string: [:0]const u8, syntax: QuerySyntax) Error!Query { + var query: ?*c.notmuch_query_t = undefined; + + try wrap(c.notmuch_query_create_with_syntax(self.database, query_string, @intFromEnum(syntax), &query)); + + return .init(query orelse return error.OutOfMemory); +} + pub fn getDefaultIndexOpts(self: *const Database) ?IndexOpts { return .{ .indexopts = c.notmuch_database_get_default_indexopts(self.database) orelse return null, @@ -747,33 +820,6 @@ pub fn configGetValuesString( }; } -/// Create a new query. -/// -/// For the query string, we'll document the syntax here more completely in the -/// future, but it's likely to be a specialized version of the general Xapian -/// query syntax: -/// -/// https://xapian.org/docs/queryparser.html -/// -/// As a special case, passing either a length-zero string, (that is ""), or a -/// string consisting of a single asterisk (that is "*"), will result in a query -/// that returns all messages in the database. -/// -/// See `Query.setSort` for controlling the order of results. See -/// `Query.searchMessages` and `Query.searchThreads` to actually execute the -/// query. -pub fn queryCreate(self: *const Database, query_string: [:0]const u8) Error!Query { - return .init(c.notmuch_query_create(self.database, query_string) orelse return error.OutOfMemory); -} - -pub fn queryCreateWithSyntax(self: *const Database, query_string: [:0]const u8, syntax: QUERY_SYNTAX) Error!Query { - var query: ?*c.notmuch_query_t = undefined; - - try wrap(c.notmuch_query_create_with_syntax(self.database, query_string, @intFromEnum(syntax), &query)); - - return .init(query orelse return error.OutOfMemory); -} - pub const IndexOpts = struct { indexopts: *c.notmuch_indexopts_t, diff --git a/src/Message.zig b/src/Message.zig index b7f077b..152c657 100644 --- a/src/Message.zig +++ b/src/Message.zig @@ -11,7 +11,7 @@ const c = @import("c"); const Error = @import("error.zig").Error; const wrap = @import("error.zig").wrap; -const MESSAGE_FLAG = @import("enums.zig").MESSAGE_FLAG; +const MessageFlag = @import("enums.zig").MessageFlag; const TagsIterator = @import("TagsIterator.zig"); @@ -123,14 +123,14 @@ pub fn thaw(self: *const Message) Error!void { } /// Get a value of a flag for the email corresponding to 'message'. -pub fn getFlag(self: *const Message, flag: MESSAGE_FLAG) Error!bool { +pub fn getFlag(self: *const Message, flag: MessageFlag) Error!bool { var is_set: c.notmuch_bool_t = undefined; try wrap(c.notmuch_message_get_flag_st(self.message, @intFromEnum(flag), &is_set)); return is_set != 0; } /// Set a value of a flag for the email corresponding to 'message'. -pub fn setFlag(self: *const Message, flag: MESSAGE_FLAG, value: bool) void { +pub fn setFlag(self: *const Message, flag: MessageFlag, value: bool) void { c.notmuch_message_set_flag(self.message, @intFromEnum(flag), @intFromBool(value)); } diff --git a/src/Query.zig b/src/Query.zig index 68b8fac..3507dd4 100644 --- a/src/Query.zig +++ b/src/Query.zig @@ -7,12 +7,14 @@ const std = @import("std"); const c = @import("c"); +const status = @import("enums.zig").status; const Error = @import("error.zig").Error; const wrap = @import("error.zig").wrap; -const EXCLUDE = @import("enums.zig").EXCLUDE; -const SORT = @import("enums.zig").SORT; +const Exclude = @import("enums.zig").Exclude; +const Sort = @import("enums.zig").Sort; +const Database = @import("Database.zig"); const MessagesIterator = @import("MessagesIterator.zig"); const ThreadsIterator = @import("ThreadsIterator.zig"); @@ -22,49 +24,62 @@ pub fn init(query: *c.notmuch_query_t) Query { return .{ .query = query }; } -/// Return the query string of this query. +/// Return the query string of this query. See `Database.queryCreate` pub fn getQueryString(self: *const Query) [:0]const u8 { return std.mem.span(c.notmuch_query_get_query_string(self.query)); } +/// Return the notmuch database of this query. See `Database.queryCreate`. +pub fn getDatabase(self: *const Query) ?Database { + return .{ + .database = c.notmuch_query_get_database(self.query) orelse null, + }; +} + /// Specify whether to omit excluded results or simply flag them. By default, -/// this is set to TRUE. +/// this is set to `true`. /// -/// If set to TRUE or ALL, notmuch_query_search_messages will omit excluded -/// messages from the results, and notmuch_query_search_threads will -/// omit threads that match only in excluded messages. If set to TRUE, -/// notmuch_query_search_threads will include all messages in threads that -/// match in at least one non-excluded message. Otherwise, if set to ALL, -/// notmuch_query_search_threads will omit excluded messages from all threads. +/// If set to `true` or `all`, `searchMessages` will omit excluded messages +/// from the results, and `searchThreads` will omit threads that match only +/// in excluded messages. If set to `true`, `searchThreads` will include +/// all messages in threads that match in at least one non-excluded message. +/// Otherwise, if set to `all`, `searchThreads` will omit excluded messages from +/// all threads. /// -/// If set to FALSE or FLAG then both notmuch_query_search_messages and -/// notmuch_query_search_threads will return all matching messages/threads -/// regardless of exclude status. If set to FLAG then the exclude -/// flag will be set for any excluded message that is returned by -/// notmuch_query_search_messages, and the thread counts for threads returned -/// by notmuch_query_search_threads will be the number of non-excluded -/// messages/matches. Otherwise, if set to FALSE, then the exclude status is +/// If set to `false` or `flag` then both `searchMessages` and `searchThreads` +/// will return all matching messages/threads regardless of exclude status. +/// If set to `flag` then the exclude flag will be set for any excluded +/// message that is returned by `searchMessages`, and the thread counts for +/// threads returned by `searchThreads` will be the number of non-excluded +/// messages/matches. Otherwise, if set to `false`, then the exclude status is /// completely ignored. /// -/// The performance difference when calling notmuch_query_search_messages should -/// be relatively small (and both should be very fast). However, in some cases, -/// notmuch_query_search_threads is very much faster when omitting excluded -/// messages as it does not need to construct the threads that only match in -/// excluded messages. -pub fn setOmitExcluded(self: *const Query, omit_excluded: EXCLUDE) void { +/// The performance difference when calling `searchMessages` should be +/// relatively small (and both should be very fast). However, in some cases, +/// `searchThreads` is very much faster when omitting excluded messages as it +/// does not need to construct the threads that only match in excluded messages. +pub fn setOmitExcluded(self: *const Query, omit_excluded: Exclude) void { c.notmuch_query_set_omit_excluded(self.query, @intFromEnum(omit_excluded)); } /// Specify the sorting desired for this query. -pub fn setSort(self: *const Query, sort: SORT) void { +pub fn setSort(self: *const Query, sort: Sort) void { c.notmuch_query_set_sort(self.query, @intFromEnum(sort)); } /// Return the sort specified for this query. -pub fn getSort(self: *const Query) SORT { +pub fn getSort(self: *const Query) Sort { return @enumFromInt(c.notmuch_query_get_sort(self.query)); } +pub const AddTagExcludeError = error{ + /// The tag is explicitly present in the query, so not excluded. + Ignored, + /// A Xapian exception occurred. Most likely a problem lazily parsing the + /// query string. + XapianException, +}; + /// Add a tag that will be excluded from the query results by default. This /// exclusion will be ignored if this tag appears explicitly in the query. /// @@ -74,19 +89,38 @@ pub fn getSort(self: *const Query) SORT { /// parsing the query string. /// /// Ignored: tag is explicitly present in the query, so not excluded. -pub fn addTagExclude(self: *const Query, tag: [:0]const u8) Error!void { - try wrap(c.notmuch_query_add_tag_exclude(self.query, tag)); +pub fn addTagExclude(self: *const Query, tag: [:0]const u8) AddTagExcludeError!void { + return switch (status(c.notmuch_query_add_tag_exclude(self.query, tag))) { + .success => {}, + .ignored => error.Ignored, + .xapian_exception => error.XapianException, + else => unreachable, + }; } -/// Execute a query for threads, returning a ThreadsIterator object which can +/// Execute a query for threads, returning a `ThreadsIterator` object which can /// be used to iterate over the results. The returned threads object is owned by /// the query and as such, will only be valid until `Query.deinit`. /// +/// Typical usage might be: +/// ``` +/// const db = try Database.open(…); +/// defer db.deinit(); +/// const query = db.queryCreate(query_string); +/// defer query.deinit(); +/// var it = query.searchThreads(); +/// defer it.deinit(); +/// while (try it.next()) |thread| { +/// defer thread.deinit(); +/// … +/// } +/// ``` +/// /// Note: If you are finished with a thread before its containing query, you -/// can call `Thread.deinit` to clean up some memory sooner (as in the above -/// example). Otherwise, if your thread objects are long-lived, then you don't -/// need to call `Thread.deinit` and all the memory will still be reclaimed when -/// the query is destroyed. +/// can call `ThreadsIterator.deinit` to clean up some memory sooner (as in the +/// above example). Otherwise, if your thread objects are long-lived, then you +/// don't need to call `ThreadsIterator.deinit` and all the memory will still be +/// reclaimed when the query is destroyed. pub fn searchThreads(self: *const Query) Error!ThreadsIterator { var out: ?*c.notmuch_threads_t = undefined; diff --git a/src/enums.zig b/src/enums.zig index bf5dee9..80b49e0 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -41,27 +41,27 @@ fn generateEnum(comptime prefix: []const u8, skips: []const []const u8) type { } /// Configuration keys known to notmuch. -pub const CONFIG = generateEnum("NOTMUCH_CONFIG_", &.{ "NOTMUCH_CONFIG_FIRST", "NOTMUCH_CONFIG_LAST" }); +pub const Config = generateEnum("NOTMUCH_CONFIG_", &.{ "NOTMUCH_CONFIG_FIRST", "NOTMUCH_CONFIG_LAST" }); -pub const DATABASE_MODE = generateEnum("NOTMUCH_DATABASE_MODE_", &.{}); +pub const DatabaseMode = generateEnum("NOTMUCH_DATABASE_MODE_", &.{}); pub const Decrypt = generateEnum("NOTMUCH_DECRYPT_", &.{}); /// Exclude values for `Query.setOmitExcluded` -pub const EXCLUDE = generateEnum("NOTMUCH_EXCLUDE_", &.{}); +pub const Exclude = generateEnum("NOTMUCH_EXCLUDE_", &.{}); -pub const MESSAGE_FLAG = generateEnum("NOTMUCH_MESSAGE_FLAG_", &.{}); +pub const MessageFlag = generateEnum("NOTMUCH_MESSAGE_FLAG_", &.{}); /// query syntax -pub const QUERY_SYNTAX = generateEnum("NOTMUCH_QUERY_SYNTAX_", &.{}); +pub const QuerySyntax = generateEnum("NOTMUCH_QUERY_SYNTAX_", &.{}); /// Sort values for notmuch_query_set_sort. -pub const SORT = generateEnum("NOTMUCH_SORT_", &.{}); +pub const Sort = generateEnum("NOTMUCH_SORT_", &.{}); /// Status codes used for the return values of most functions. -pub const STATUS = generateEnum("NOTMUCH_STATUS_", &.{"NOTMUCH_STATUS_LAST_STATUS"}); +pub const Status = generateEnum("NOTMUCH_STATUS_", &.{"NOTMUCH_STATUS_LAST_STATUS"}); -/// Convenience function to convert a notmuch API return code to a STATUS enum. -pub fn status(rc: c_uint) STATUS { +/// Convenience function to convert a notmuch API return code to a Status enum. +pub fn status(rc: c_uint) Status { return @enumFromInt(rc); } diff --git a/src/error.zig b/src/error.zig index fbe9b32..cb8bc99 100644 --- a/src/error.zig +++ b/src/error.zig @@ -7,7 +7,7 @@ const log = std.log.scoped(.notmuch); const c = @import("c"); -const STATUS = @import("enums.zig").STATUS; +const Status = @import("enums.zig").Status; const status = @import("enums.zig").status; pub const Error = error{ diff --git a/src/notmuch.zig b/src/notmuch.zig index fa915df..9c9b9a6 100644 --- a/src/notmuch.zig +++ b/src/notmuch.zig @@ -7,10 +7,12 @@ const std = @import("std"); const c = @import("c"); -pub const Error = @import("error.zig").Error; pub const Database = @import("Database.zig"); +pub const Error = @import("error.zig").Error; pub const Message = @import("Message.zig"); pub const Query = @import("Query.zig"); +pub const Thread = @import("Thread.zig"); +pub const ThreadsIterator = @import("ThreadsIterator.zig"); const wrap = @import("error.zig").wrap; -- 2.51.2