~gpanders/wk

c0e190f572906dced35606b0d999d4390183c3c6 — Greg Anders 2 months ago fe401d3
Make patterns match on zettel contents

Instead of just matching on the filename, id, or title, make command
line patterns match anywhere in the zettel. This reduces the necessity
of shell completions and also removes the need for the 'search' command
(since 'zet ls PATTERN' is now identical to 'zet search PATTERN').
9 files changed, 58 insertions(+), 170 deletions(-)

M src/cmd.zig
M src/cmd/list.zig
M src/cmd/open.zig
M src/cmd/preview.zig
D src/cmd/search.zig
M src/cmd/show.zig
M src/cmd/tags.zig
M src/tty.zig
M src/zettel.zig
M src/cmd.zig => src/cmd.zig +0 -1
@@ 13,7 13,6 @@ const commands = .{
    @import("cmd/open.zig").cmd,
    @import("cmd/preview.zig").cmd,
    @import("cmd/sync.zig").cmd,
    @import("cmd/search.zig").cmd,
    @import("cmd/show.zig").cmd,
    @import("cmd/tags.zig").cmd,
};

M src/cmd/list.zig => src/cmd/list.zig +3 -9
@@ 4,14 4,13 @@ const mem = std.mem;
const stdout = std.io.getStdOut().outStream();

const Command = @import("../cmd.zig").Command;
const Zettel = @import("../zettel.zig").Zettel;
const getZettels = @import("../zettel.zig").getZettels;

pub const cmd = Command{
    .name = "list",
    .aliases = &[_][]const u8{ "l", "ls" },
    .usage = "l|list [PATTERN [PATTERN ...]]",
    .desc = "With no argument, list all notes. Otherwise list notes matching any of the given patterns.",
    .desc = "With no argument, list all notes. Otherwise list notes containing any of the given patterns.",
    .run = run,
};



@@ 19,13 18,8 @@ pub fn run(allocator: *mem.Allocator, args: ?[]const []const u8) Command.Error!v
    const zettels = try getZettels(allocator);
    for (zettels) |zet| {
        if (args) |patterns| {
            for (patterns) |pat| {
                if (mem.indexOf(u8, zet.id, pat) != null or
                    ascii.indexOfIgnoreCase(zet.fname, pat) != null or
                    ascii.indexOfIgnoreCase(zet.title, pat) != null)
                {
                    stdout.print("{} {}\n", .{ zet.id, zet.title }) catch return;
                }
            if (zet.match(patterns)) {
                stdout.print("{} {}\n", .{ zet.id, zet.title }) catch return;
            }
        } else {
            stdout.print("{} {}\n", .{ zet.id, zet.title }) catch return;

M src/cmd/open.zig => src/cmd/open.zig +5 -7
@@ 1,4 1,5 @@
const std = @import("std");
const ascii = std.ascii;
const fmt = std.fmt;
const mem = std.mem;
const os = std.os;


@@ 6,7 7,6 @@ const warn = std.debug.warn;

const Command = @import("../cmd.zig").Command;
const Zettel = @import("../zettel.zig").Zettel;
const findZettels = @import("../zettel.zig").findZettels;
const getZettels = @import("../zettel.zig").getZettels;
const commit = @import("../util.zig").commit;



@@ 20,16 20,14 @@ pub const cmd = Command{

pub fn run(allocator: *mem.Allocator, args: ?[]const []const u8) Command.Error!void {
    const zettels = try getZettels(allocator);
    const matches = if (args) |_|
        try findZettels(allocator, zettels, args.?)
    else
        null;

    var argv = std.ArrayList([]const u8).init(allocator);
    try argv.append(os.getenv("EDITOR") orelse "vi");

    if (matches) |_| {
        for (matches.?) |zet| try argv.append(zet.fname);
    if (args) |patterns| {
        for (zettels) |zet| {
            if (zet.match(patterns)) try argv.append(zet.fname);
        }
    }

    var proc = try std.ChildProcess.init(argv.items, allocator);

M src/cmd/preview.zig => src/cmd/preview.zig +7 -7
@@ 8,7 8,6 @@ const warn = std.debug.warn;

const Command = @import("../cmd.zig").Command;
const Zettel = @import("../zettel.zig").Zettel;
const findZettels = @import("../zettel.zig").findZettels;
const getZettels = @import("../zettel.zig").getZettels;
const execAndCheck = @import("../util.zig").execAndCheck;



@@ 21,15 20,16 @@ pub const cmd = Command{
};

pub fn run(allocator: *mem.Allocator, args: ?[]const []const u8) Command.Error!void {
    const matches = if (args) |_|
        try findZettels(allocator, try getZettels(allocator), args.?)
    else
        return error.MissingRequiredArgument;
    const patterns = if (args) |_| args.? else return error.MissingRequiredArgument;

    const zettels = try getZettels(allocator);

    var output_files = std.ArrayList([]const u8).init(allocator);
    defer for (output_files.items) |file| fs.cwd().deleteFile(file) catch {};

    for (matches) |zet| {
    for (zettels) |zet| {
        if (!zet.match(patterns)) continue;

        const html_file = try convert(allocator, zet);
        try output_files.append(html_file);



@@ 55,7 55,7 @@ pub fn run(allocator: *mem.Allocator, args: ?[]const []const u8) Command.Error!v
    std.time.sleep(1 * std.time.ns_per_s);
}

fn convert(allocator: *mem.Allocator, zet: *const Zettel) Command.Error![]const u8 {
fn convert(allocator: *mem.Allocator, zet: Zettel) Command.Error![]const u8 {
    const output = try fmt.allocPrint(allocator, "{}{}", .{ zet.basename, ".html" });

    _ = execAndCheck(allocator, &[_][]const u8{

D src/cmd/search.zig => src/cmd/search.zig +0 -32
@@ 1,32 0,0 @@
const std = @import("std");
const ascii = std.ascii;
const mem = std.mem;
const stdout = std.io.getStdOut().outStream();

const Command = @import("../cmd.zig").Command;
const Zettel = @import("../zettel.zig").Zettel;
const getZettels = @import("../zettel.zig").getZettels;

pub const cmd = Command{
    .name = "search",
    .aliases = &[_][]const u8{"s"},
    .usage = "s|search <PATTERN> [PATTERN ...]",
    .desc = "Search for notes whose contents match any of the given patterns.",
    .run = run,
};

pub fn run(allocator: *mem.Allocator, args: ?[]const []const u8) Command.Error!void {
    const zettels = try getZettels(allocator);
    if (args) |patterns| {
        for (zettels) |zet| {
            for (patterns) |pat| {
                if (ascii.indexOfIgnoreCase(zet.contents, pat)) |_| {
                    stdout.print("{} {}\n", .{ zet.id, zet.title }) catch return;
                    break;
                }
            }
        }
    } else {
        return error.MissingRequiredArgument;
    }
}

M src/cmd/show.zig => src/cmd/show.zig +9 -8
@@ 1,27 1,28 @@
const std = @import("std");
const ChildProcess = std.ChildProcess;
const fs = std.fs;
const mem = std.mem;

const TtyWriter = @import("../tty.zig").TtyWriter;
const ColorWriter = @import("../tty.zig").ColorWriter;
const Command = @import("../cmd.zig").Command;
const Zettel = @import("../zettel.zig").Zettel;
const findZettels = @import("../zettel.zig").findZettels;
const getZettels = @import("../zettel.zig").getZettels;

pub const cmd = Command{
    .name = "show",
    .aliases = &[_][]const u8{"sh"},
    .usage = "sh|show <NOTE> [NOTE ...]",
    .aliases = &[_][]const u8{"s"},
    .usage = "s|show <NOTE> [NOTE ...]",
    .desc = "Display contents of notes to stdout.",
    .run = run,
};

pub fn run(allocator: *mem.Allocator, args: ?[]const []const u8) Command.Error!void {
    if (args) |_| {
    if (args) |patterns| {
        const zettels = try getZettels(allocator);
        const matches = try findZettels(allocator, zettels, args.?);
        for (matches) |zet| {
            const writer = TtyWriter.new(std.io.getStdOut());
        for (zettels) |zet| {
            if (!zet.match(patterns)) continue;

            const writer = ColorWriter.new(std.io.getStdOut());

            writer.setColor(.Blue).print("{}\n", .{zet.fname});
            writer.setColor(.Yellow).print("{}", .{zet.contents[0..3]});

M src/cmd/tags.zig => src/cmd/tags.zig +2 -2
@@ 17,10 17,10 @@ pub const cmd = Command{

pub fn run(allocator: *mem.Allocator, args: ?[]const []const u8) Command.Error!void {
    const zettels = try getZettels(allocator);
    if (args) |_| {
    if (args) |tags| {
        outer: for (zettels) |zet| {
            for (zet.tags) |t| {
                for (args.?) |tag| {
                for (tags) |tag| {
                    if (ascii.eqlIgnoreCase(tag, t)) {
                        stdout.print("{} {}\n", .{ zet.id, zet.title }) catch return;
                        continue :outer;

M src/tty.zig => src/tty.zig +10 -14
@@ 21,7 21,7 @@ pub const Color = enum(u8) {
    BrWhite = 15,
};

pub const TtyWriter = struct {
pub const ColorWriter = struct {
    color: ?Color = null,
    bold: bool = false,
    italic: bool = false,


@@ 38,25 38,21 @@ pub const TtyWriter = struct {
    }

    pub fn print(self: Self, comptime format: []const u8, args: var) void {
        if (self.file.isTty()) {
            if (self.color) |_| {
                self.file.outStream().print("\x1b[38;5;{}m", .{@enumToInt(self.color.?)}) catch return;
            }
        if (self.color) |_| {
            self.file.outStream().print("\x1b[38;5;{}m", .{@enumToInt(self.color.?)}) catch return;
        }

            if (self.bold) {
                self.file.outStream().print("\x1b[1m", .{}) catch return;
            }
        if (self.bold) {
            self.file.outStream().print("\x1b[1m", .{}) catch return;
        }

            if (self.italic) {
                self.file.outStream().print("\x1b[3m", .{}) catch return;
            }
        if (self.italic) {
            self.file.outStream().print("\x1b[3m", .{}) catch return;
        }

        self.file.outStream().print(format, args) catch return;

        if (self.file.isTty()) {
            self.file.outStream().print("\x1b[0m", .{}) catch return;
        }
        self.file.outStream().print("\x1b[0m", .{}) catch return;
    }

    pub fn setBold(self: Self) Self {

M src/zettel.zig => src/zettel.zig +22 -90
@@ 4,7 4,6 @@ const fmt = std.fmt;
const fs = std.fs;
const mem = std.mem;
const os = std.os;
const stdout = std.io.getStdOut().outStream();
const warn = std.debug.warn;

const c = @cImport(@cInclude("time.h"));


@@ 47,7 46,7 @@ pub const Zettel = struct {
        const contents = try fmt.allocPrint(allocator, new_template, .{ title, date });
        try file.writeAll(contents);

        var zettel = Zettel{
        return Zettel{
            .allocator = allocator,
            .basename = fname[0 .. fname.len - extension.len],
            .fname = fname,


@@ 58,8 57,6 @@ pub const Zettel = struct {
            .contents = contents,
            .backlinks = std.ArrayList(*const Zettel).init(allocator),
        };

        return zettel;
    }

    pub fn read(self: Zettel) ![]const u8 {


@@ 84,6 81,26 @@ pub const Zettel = struct {
        return true;
    }

    pub fn match(self: Zettel, keywords: []const []const u8) bool {
        for (keywords) |keyword| {
            if (ascii.indexOfIgnoreCase(self.fname, keyword) != null or
                ascii.indexOfIgnoreCase(self.contents, keyword) != null)
            {
                return true;
            }

            // Also try id + title
            if (mem.startsWith(u8, keyword, self.id) and
                keyword.len > id_length + 1 and keyword[id_length] == ' ' and
                ascii.eqlIgnoreCase(keyword[id_length + 1 ..], self.title))
            {
                return true;
            }
        }

        return false;
    }

    /// Return a list of zettels linked to in the given zettel
    pub fn links(self: Zettel) ![]const []const u8 {
        var links_list = std.ArrayList([]const u8).init(self.allocator);


@@ 223,89 240,6 @@ pub fn getZettels(allocator: *mem.Allocator) ![]Zettel {
    return zettels.toOwnedSlice();
}

pub fn findZettels(allocator: *mem.Allocator, zettels: []const Zettel, keywords: []const []const u8) ![]*const Zettel {
    var found = std.ArrayList(*const Zettel).init(allocator);

    for (keywords) |keyword| {
        if (findZettel(zettels, keyword)) |zet| {
            try found.append(zet);
        } else {
            warn("No matches found for '{}'\n", .{keyword});
        }
    }

    if (found.items.len == 0) {
        return error.NoMatches;
    }

    return found.toOwnedSlice();
}

fn findZettel(zettels: []const Zettel, keyword: []const u8) ?*const Zettel {
    comptime var fields = .{ "id", "fname", "basename", "title" };
    for (zettels) |*zet| {
        inline for (fields) |field| {
            if (ascii.eqlIgnoreCase(keyword, @field(zet, field))) {
                return zet;
            }
        }

        // Also try id + title
        if (mem.startsWith(u8, keyword, zet.id) and
            keyword.len > id_length + 1 and keyword[id_length] == ' ' and
            ascii.eqlIgnoreCase(keyword[id_length + 1 ..], zet.title))
        {
            return zet;
        }
    }

    return null;
}

test "findZettel" {
    const expectEqual = std.testing.expectEqual;

    const zettels = &[_]Zettel{
        .{
            .allocator = undefined,
            .basename = "12345678900000-test-one",
            .fname = "12345678900000-test-one" ++ extension,
            .id = "12345678900000",
            .tags = &[_][]const u8{},
            .title = "Test One",
            .mtime = -1,
            .contents = &[_]u8{},
            .backlinks = undefined,
        },
        .{
            .allocator = undefined,
            .basename = "12345678900001-test-two",
            .fname = "12345678900001-test-two" ++ extension,
            .id = "12345678900001",
            .tags = &[_][]const u8{},
            .title = "Test Two",
            .mtime = -1,
            .contents = &[_]u8{},
            .backlinks = undefined,
        },
    };

    expectEqual(&zettels[0], findZettel(zettels, "12345678900000").?);
    expectEqual(&zettels[0], findZettel(zettels, "12345678900000-test-one" ++ extension).?);
    expectEqual(&zettels[0], findZettel(zettels, "12345678900000-test-one").?);
    expectEqual(&zettels[0], findZettel(zettels, "Test One").?);

    expectEqual(&zettels[1], findZettel(zettels, "12345678900001").?);
    expectEqual(&zettels[1], findZettel(zettels, "12345678900001-test-two" ++ extension).?);
    expectEqual(&zettels[1], findZettel(zettels, "12345678900001-test-two").?);
    expectEqual(&zettels[1], findZettel(zettels, "Test Two").?);

    expectEqual(@as(?*const Zettel, null), findZettel(zettels, "12345678900002"));
    expectEqual(@as(?*const Zettel, null), findZettel(zettels, "12345678900002-test-three" ++ extension));
    expectEqual(@as(?*const Zettel, null), findZettel(zettels, "12345678900002-test-three"));
    expectEqual(@as(?*const Zettel, null), findZettel(zettels, "Test Three"));
}

fn fromEntry(allocator: *mem.Allocator, entry: fs.Dir.Entry) !Zettel {
    var file = try fs.cwd().openFile(entry.name, .{ .read = true });
    defer file.close();


@@ 368,7 302,7 @@ fn fromEntry(allocator: *mem.Allocator, entry: fs.Dir.Entry) !Zettel {

    const fname = try mem.dupe(allocator, u8, entry.name);

    var zettel = Zettel{
    return Zettel{
        .allocator = allocator,
        .basename = fname[0 .. fname.len - extension.len],
        .fname = fname,


@@ 379,8 313,6 @@ fn fromEntry(allocator: *mem.Allocator, entry: fs.Dir.Entry) !Zettel {
        .contents = contents,
        .backlinks = std.ArrayList(*const Zettel).init(allocator),
    };

    return zettel;
}

fn hasId(name: []const u8) bool {