diff --git a/src/Database.zig b/src/Database.zig
index 6ac5875..6e71402 100644
--- a/src/Database.zig
+++ b/src/Database.zig
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: © 2024 Jeffrey C. Ollie
// SPDX-License-Identifier: GPL-3.0-or-later
+//! Zig wrapper around the `notmuch` database APIs.
+
const Database = @This();
const std = @import("std");
@@ -13,8 +15,8 @@ const wrap = @import("error.zig").wrap;
const wrapMessage = @import("error.zig").wrapMessage;
const enums = @import("enums.zig");
-const CONFIG = enums.CONFIG;
-const DATABASE_MODE = enums.DATABASE_MODE;
+pub const Config = enums.CONFIG;
+pub const Mode = enums.DATABASE_MODE;
const DECRYPT = enums.DECRYPT;
const QUERY_SYNTAX = enums.QUERY_SYNTAX;
const STATUS = enums.STATUS;
@@ -26,30 +28,96 @@ const Query = @import("Query.zig");
database: *c.notmuch_database_t,
pub const OpenOptions = struct {
- /// Specify a config file.
+ /// Path to config file.
+ ///
+ /// Config file is key-value, with mandatory sections. See
+ /// **notmuch-config(5)** for more information. The key-value pair
+ /// overrides the corresponding configuration data stored in the
+ /// database (see notmuch_database_get_config)
+ ///
+ /// If `config_path` is `null` use the path specified
+ ///
+ /// - in environment variable `NOTMUCH_CONFIG`, if non-empty
+ ///
+ /// - by `XDG_CONFIG_HOME`/notmuch/ where
+ /// `XDG_CONFIG_HOME` defaults to `$HOME/.config`.
+ ///
+ /// - by `$HOME/.notmuch-config`
+ ///
+ /// If `config_path` is "" (empty string) then do not
+ /// open any configuration file.
config_path: ?[:0]const u8 = null,
- /// Specify a database path.
+ /// Path to existing database.
+ ///
+ /// A notmuch database is a Xapian database containing appropriate
+ /// metadata.
+ ///
+ /// The database should have been created at some time in the past,
+ /// (not necessarily by this process), by calling
+ /// notmuch_database_create.
+ ///
+ /// If 'database_path' is NULL, use the location specified
+ ///
+ /// - in the environment variable NOTMUCH_DATABASE, if non-empty
+ ///
+ /// - in a configuration file, located as described under 'config_path'
+ ///
+ /// - by $XDG_DATA_HOME/notmuch/$PROFILE where XDG_DATA_HOME defaults
+ /// to "$HOME/.local/share" and PROFILE as as discussed in
+ /// 'profile'
+ ///
+ /// If 'database_path' is non-NULL, but does not appear to be a Xapian
+ /// database, check for a directory '.notmuch/xapian' below
+ /// 'database_path' (this is the behavior of
+ /// notmuch_database_open_verbose pre-0.32).
database_path: ?[*:0]const u8 = null,
- /// Specify a profile.
+ /// Name of profile (configuration/database variant).
+ ///
+ /// If non-`null`, append to the directory / file path determined for
+ /// config_path and database_path.
+ ///
+ /// If `null` then use
+ /// - environment variable `NOTMUCH_PROFILE` if defined,
+ /// - otherwise `"default"` for directories and `""` (empty string) for paths.
profile: ?[:0]const u8 = null,
};
-/// Open an existing notmuch database.
-pub fn open(mode: DATABASE_MODE, options: OpenOptions) Error!Database {
+pub const OpenError = error{
+ NullPointer,
+ NoConfig,
+ OutOfMemory,
+ FileError,
+ XapianException,
+};
+
+/// Open an existing notmuch database located at `database_path`, using
+/// configuration in `config_path`.
+pub fn open(mode: Mode, options: OpenOptions) OpenError!Database {
if (!c.LIBNOTMUCH_CHECK_VERSION(5, 6, 0)) {
return error.NotmuchVersion;
}
var error_message: [*c]u8 = null;
+ defer if (error_message) |m| c.free(m);
+
var database: ?*c.notmuch_database_t = null;
- try wrapMessage(c.notmuch_database_open_with_config(
+
+ switch (status(c.notmuch_database_open_with_config(
options.database_path,
@intFromEnum(mode),
options.config_path,
options.profile,
&database,
&error_message,
- ), error_message);
+ ), error_message)) {
+ .success => {},
+ .null_pointer => return error.NullPointer,
+ .NO_CONFIG => return error.NoConfig,
+ .out_of_memory => return error.OutOfMemory,
+ .file_error => return error.FileError,
+ .xapian_exception => return error.XapianException,
+ }
+
return .{
.database = database orelse unreachable,
};
@@ -178,8 +246,8 @@ pub fn upgrade(self: *const Database, progress_notify: ?UpgradeProgressNotifyCal
/// called in pairs.
pub fn beginAtomic(self: *const Database) error{XapianException}!void {
switch (status(c.notmuch_database_begin_atomic(self.database))) {
- .SUCCESS => {},
- .XAPIAN_EXCEPTION => return error.XapianException,
+ .success => {},
+ .xapian_exception => return error.XapianException,
else => unreachable,
}
}
@@ -189,9 +257,9 @@ pub fn beginAtomic(self: *const Database) error{XapianException}!void {
/// 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 => {},
- .UNBALANCED_ATOMIC => return error.UnbalancedAtomic,
- .XAPIAN_EXCEPTION => return error.XapianException,
+ .success => {},
+ .unbalanced_atomic => return error.UnbalancedAtomic,
+ .xapian_exception => return error.XapianException,
else => unreachable,
}
}
@@ -281,12 +349,12 @@ pub fn configPath(self: *const Database) ?[:0]const u8 {
/// Returns `null` if `key` is unknown or if no value is known for `key`.
/// Otherwise returns a string owned by `notmuch` which should not be modified
/// nor freed by the caller.
-pub fn configGet(self: *const Database, key: CONFIG) Error!?[:0]const u8 {
+pub fn configGet(self: *const Database, key: Config) Error!?[:0]const u8 {
return std.mem.span(c.notmuch_config_get(self.database, @intFromEnum(key)) orelse return null);
}
/// Set a configuration value
-pub fn configSet(self: *const Database, key: CONFIG, value: [:0]const u8) Error!void {
+pub fn configSet(self: *const Database, key: Config, value: [:0]const u8) Error!void {
try wrap(c.notmuch_config_set(self.database, @intFromEnum(key), value));
}
@@ -297,7 +365,7 @@ pub fn configSet(self: *const Database, key: CONFIG, value: [:0]const u8) Error!
pub fn configGetValues(
self: *const Database,
/// configuration key
- key: CONFIG,
+ key: Config,
) ValuesIterator {
return .{
.values = c.notmuch_config_get_values(self.database, @intFromEnum(key)),
@@ -315,7 +383,7 @@ pub fn configGetBool(
/// the database
self: *const Database,
/// configuration key
- key: CONFIG,
+ key: Config,
) Error!bool {
var value: c.notmuch_bool_t = undefined;
try wrap(c.notmuch_config_get_bool(self.database, @intFromEnum(key), &value));
@@ -329,7 +397,7 @@ pub fn configGetBool(
pub fn configGetValuesString(
self: *const Database,
/// configuration key
- key: CONFIG,
+ key: Config,
) ValuesIterator {
return .{
.values = c.notmuch_config_get_values_string(self.database, @intFromEnum(key)),
diff --git a/src/enums.zig b/src/enums.zig
index 35a64eb..b6c6a65 100644
--- a/src/enums.zig
+++ b/src/enums.zig
@@ -26,7 +26,8 @@ fn generateEnum(comptime prefix: []const u8, skips: []const []const u8) type {
outer: for (info.@"struct".decls) |decl| {
for (skips) |skip| if (std.mem.eql(u8, skip, decl.name)) continue :outer;
if (std.mem.cutPrefix(u8, decl.name, prefix)) |suffix| {
- field_names[index] = suffix;
+ var buf: [suffix.len]u8 = undefined;
+ field_names[index] = std.ascii.lowerString(&buf, suffix);
field_values[index] = @field(c, decl.name);
index += 1;
}
diff --git a/src/error.zig b/src/error.zig
index 7778e25..fbe9b32 100644
--- a/src/error.zig
+++ b/src/error.zig
@@ -97,33 +97,33 @@ pub fn wrapMessage(rc: c.notmuch_status_t, message: [*c]const u8) Error!void {
pub fn wrap(rc: c.notmuch_status_t) Error!void {
return switch (status(rc)) {
- .SUCCESS => {},
- .BAD_QUERY_SYNTAX => error.BadQuerySyntax,
- .CLOSED_DATABASE => error.ClosedDatabase,
- .DATABASE_EXISTS => error.DatabaseExists,
- .DUPLICATE_MESSAGE_ID => error.DuplicateMessageID,
- .FAILED_CRYPTO_CONTEXT_CREATION => error.FailedCryptoContextCreation,
- .FILE_ERROR => error.FileError,
- .FILE_NOT_EMAIL => error.FileNotEmail,
- .IGNORED => error.Ignored,
- .ILLEGAL_ARGUMENT => error.IllegalArgument,
- .ITERATOR_EXHAUSTED => error.IteratorExhausted,
- .MALFORMED_CRYPTO_PROTOCOL => error.MaformedCryptoProtocol,
- .NO_CONFIG => error.NoConfig,
- .NO_DATABASE => error.NoDatabase,
- .NO_MAIL_ROOT => error.NoMailRoot,
- .NULL_POINTER => error.NullPointer,
- .OPERATION_INVALIDATED => error.OperationInvalidated,
- .OUT_OF_MEMORY => error.OutOfMemory,
- .PATH_ERROR => error.PathError,
- .READ_ONLY_DATABASE => error.ReadOnlyDatabase,
- .TAG_TOO_LONG => error.TagTooLong,
- .UNBALANCED_ATOMIC => error.UnbalancedAtomic,
- .UNBALANCED_FREEZE_THAW => error.UnbalancedFreezeThaw,
- .UNKNOWN_CRYPTO_PROTOCOL => error.UnknownCryptoProtocol,
- .UNSUPPORTED_OPERATION => error.UnsupportedOperation,
- .UPGRADE_REQUIRED => error.UpgradeRequired,
- .XAPIAN_EXCEPTION => error.XapianException,
+ .success => {},
+ .bad_query_syntax => error.BadQuerySyntax,
+ .closed_database => error.ClosedDatabase,
+ .database_exists => error.DatabaseExists,
+ .duplicate_message_id => error.DuplicateMessageID,
+ .failed_crypto_context_creation => error.FailedCryptoContextCreation,
+ .file_error => error.FileError,
+ .file_not_email => error.FileNotEmail,
+ .ignored => error.Ignored,
+ .illegal_argument => error.IllegalArgument,
+ .iterator_exhausted => error.IteratorExhausted,
+ .malformed_crypto_protocol => error.MaformedCryptoProtocol,
+ .no_config => error.NoConfig,
+ .no_database => error.NoDatabase,
+ .no_mail_root => error.NoMailRoot,
+ .null_pointer => error.NullPointer,
+ .operation_invalidated => error.OperationInvalidated,
+ .out_of_memory => error.OutOfMemory,
+ .path_error => error.PathError,
+ .read_only_database => error.ReadOnlyDatabase,
+ .tag_too_long => error.TagTooLong,
+ .unbalanced_atomic => error.UnbalancedAtomic,
+ .unbalanced_freeze_thaw => error.UnbalancedFreezeThaw,
+ .unknown_crypto_protocol => error.UnknownCryptoProtocol,
+ .unsupported_operation => error.UnsupportedOperation,
+ .upgrade_required => error.UpgradeRequired,
+ .xapian_exception => error.XapianException,
};
}