const c = @cImport({ @cInclude("tkrzw_langc.h"); }); const Self = @This(); const std = @import("std"); pub const free = c.free; dbm: *c.TkrzwDBM, const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const encode_len = 4; pub fn numEncode(num: usize) ![encode_len]u8 { return uintEncode(num, 4, chars); } pub fn numDecode(str: []const u8) usize { return uintDecode(str, chars); } const EncodeError = error{ NumberTooBig, }; fn uintEncode(num: usize, comptime bufflen: usize, comptime charset: []const u8) ![bufflen]u8 { // Check if the number is bigger than the maximum possible number var pow = comptime std.math.pow(usize, charset.len, bufflen); if (num >= pow) return EncodeError.NumberTooBig; var buff = [_]u8{charset[0]} ** bufflen; var num_i: usize = num; for (buff) |*pos| { pow /= charset.len; pos.* = charset[@divTrunc(num_i, pow)]; num_i %= pow; } return buff; } fn uintDecode(str: []const u8, comptime charset: []const u8) usize { var ret: usize = 0; for (str) |needle, i| { for (charset) |blade, j| { if (needle == blade) { ret += std.math.pow(usize, charset.len, str.len - i - 1) * j; break; } } } return ret; } pub fn open(path: [:0]const u8, writable: bool, params: [:0]const u8) Self { return Self{ .dbm = c.tkrzw_dbm_open(path, writable, params), }; } // TODO: Return error pub fn set(self: *Self, key: []const u8, value: []const u8) void { const key_len = @intCast(i32, key.len); const value_len = @intCast(i32, value.len); _ = c.tkrzw_dbm_set(self.dbm, key.ptr, key_len, value.ptr, value_len, true); } // TODO: Return error pub fn remove(self: *Self, key: []const u8) bool { return c.tkrzw_dbm_remove(self.dbm, key.ptr, @intCast(i32, key.len)); } // TODO: Return error pub fn append(self: *Self, key: []const u8, value: []const u8, delim: []const u8) void { const key_len = @intCast(i32, key.len); const value_len = @intCast(i32, value.len); const delim_len = @intCast(i32, delim.len); _ = c.tkrzw_dbm_append(self.dbm, key.ptr, key_len, value.ptr, value_len, delim.ptr, delim_len); } pub fn check(self: *Self, key: []const u8) bool { const key_len = @intCast(i32, key.len); return c.tkrzw_dbm_check(self.dbm, key.ptr, key_len); } // You gotta call free on return pub fn get(self: *Self, key: []const u8) ?[]u8 { const key_len = @intCast(i32, key.len); var len: i32 = 0; var ret = c.tkrzw_dbm_get(self.dbm, key.ptr, key_len, &len); return if (ret) |nnret| nnret[0..@intCast(usize, len)] else return null; } pub fn getList(self: *Self, key: []const u8, allocator: std.mem.Allocator) !?[][:0]u8 { const list_str = self.get(key) orelse return null; defer Self.free(list_str.ptr); const count = std.mem.count(u8, list_str, " ") + 1; var buffer = try allocator.alloc([:0]u8, count); var iter = std.mem.split(u8, list_str, " "); var i: usize = 0; while (iter.next()) |unit| { defer i += 1; buffer[i] = try allocator.dupeZ(u8, unit); } return buffer; } /// Helper function to make it easier to find occurrences in lists pub fn isInList(self: *Self, key: []const u8, needle: []const u8) bool { if (self.get(key)) |haystack| { defer Self.free(haystack.ptr); if (isInStringDelim(haystack, needle, ' ')) return true; } return false; } // TODO: Return wether or not the list is void after this pub fn removeFromList(self: *Self, key: []const u8, needle: []const u8) void { // TODO: Better error handling, do not fail silently const haystack = self.get(key) orelse return; // If there's no start, somesthing's wrong const start = std.mem.indexOf(u8, haystack, needle) orelse return; const end = start + needle.len; // Re-set the rest of the IDs self.set(key, haystack[0..start]); // We were deleting the last in the list, no need to append anything if (end >= haystack.len) return; // +1 to remove the space self.append(key, haystack[(end + 1)..], " "); return; } /// Generic function to find occurences of a string inside another one with delimiters fn isInStringDelim(haystack: []const u8, needle: []const u8, delim: u8) bool { var spliter = std.mem.split(u8, haystack, &[_]u8{delim}); while (spliter.next()) |blade| { if (std.mem.eql(u8, blade, needle)) { return true; } } return false; }