M src/cmd.zig => src/cmd.zig +18 -29
@@ 44,52 44,41 @@ pub const Command = enum {
}
};
-pub fn handleCommand(command: []const u8, arg: ?[]const u8, allocator: *std.mem.Allocator) !void {
+pub fn handleCommand(allocator: *std.mem.Allocator, command: []const u8, args: ?[][]const u8) !void {
const zettels = try zettel.getZettels(allocator);
defer allocator.free(zettels);
switch (try parseCommand(command)) {
- .Show => if (arg != null) {
- if (zettel.findZettel(zettels, arg.?)) |zet| {
- try show.run(allocator, zet);
- } else {
- return error.DoesNotExist;
- }
+ .Show => if (args != null) {
+ const matches = try zettel.findZettels(allocator, zettels, args.?);
+ try show.run(allocator, matches);
} else {
return error.MissingRequiredArgument;
},
- .List => try list.run(allocator, arg, zettels),
- .Open => {
- if (arg != null) {
- if (zettel.findZettel(zettels, arg.?)) |zet| {
- try open.run(allocator, zet);
- } else {
- return error.DoesNotExist;
- }
- } else {
- try open.run(allocator, null);
- }
+ .List => try list.run(allocator, args, zettels),
+ .Open => if (args != null) {
+ const matches = try zettel.findZettels(allocator, zettels, args.?);
+ try open.run(allocator, matches);
+ } else {
+ try open.run(allocator, null);
},
- .New => if (arg != null) {
- try new.run(allocator, arg.?);
+ .New => if (args != null) {
+ try new.run(allocator, args.?[0]);
} else {
return error.MissingRequiredArgument;
},
- .Search => if (arg != null) {
- try search.run(allocator, arg.?, zettels);
+ .Search => if (args != null) {
+ try search.run(allocator, args.?, zettels);
} else {
return error.MissingRequiredArgument;
},
- .Preview => if (arg != null) {
- if (zettel.findZettel(zettels, arg.?)) |zet| {
- try preview.run(allocator, zet);
- } else {
- return error.DoesNotExist;
- }
+ .Preview => if (args != null) {
+ const matches = try zettel.findZettels(allocator, zettels, args.?);
+ try preview.run(allocator, matches);
} else {
return error.MissingRequiredArgument;
},
- .Tags => try tags.run(allocator, arg, zettels),
+ .Tags => try tags.run(allocator, args, zettels),
}
}
M src/cmd/list.zig => src/cmd/list.zig +13 -4
@@ 3,12 3,21 @@ const stdout = std.io.getStdOut().outStream();
const Zettel = @import("../zettel.zig").Zettel;
-pub const usage = "l|list [pattern]";
-pub const desc = "With no argument, list all notes. Otherwise list notes matching the given pattern";
+pub const usage = "l|list [PATTERN [PATTERN ...]]";
+pub const desc = "With no argument, list all notes. Otherwise list notes matching any of the given patterns";
-pub fn run(allocator: *std.mem.Allocator, pattern: ?[]const u8, zettels: []const Zettel) !void {
+pub fn run(allocator: *std.mem.Allocator, patterns: ?[][]const u8, zettels: []const Zettel) !void {
for (zettels) |zet| {
- if (pattern == null or std.ascii.indexOfIgnoreCase(zet.fname, pattern.?) != null) {
+ if (patterns) |pats| {
+ for (pats) |pat| {
+ if (std.mem.indexOf(u8, zet.id, pat) != null or
+ std.ascii.indexOfIgnoreCase(zet.fname, pat) != null or
+ std.ascii.indexOfIgnoreCase(zet.title, pat) != null)
+ {
+ try stdout.print("{} {}\n", .{ zet.id, zet.title });
+ }
+ }
+ } else {
try stdout.print("{} {}\n", .{ zet.id, zet.title });
}
}
M src/cmd/new.zig => src/cmd/new.zig +6 -6
@@ 5,14 5,14 @@ const open = @import("../cmd.zig").open;
const Zettel = @import("../zettel.zig").Zettel;
const util = @import("../util.zig");
-pub const usage = "n|new <title>";
+pub const usage = "n|new <TITLE>";
pub const desc = "Create a new note with the given title";
pub fn run(allocator: *std.mem.Allocator, title: []const u8) !void {
const new_zettel = try Zettel.new(allocator, title);
defer new_zettel.deinit();
- try open.run(allocator, new_zettel);
+ try open.run(allocator, &[_]Zettel{new_zettel});
try commit(allocator, new_zettel);
}
@@ 22,7 22,7 @@ fn commit(allocator: *std.mem.Allocator, zet: Zettel) !void {
const git = util.expandPath(allocator, "git") orelse return;
defer allocator.free(git);
- try util.execAndCheck(allocator, &[_][]const u8{
+ _ = try util.execAndCheck(allocator, &[_][]const u8{
git,
"stash",
"push",
@@ 30,7 30,7 @@ fn commit(allocator: *std.mem.Allocator, zet: Zettel) !void {
"--quiet",
});
- try util.execAndCheck(allocator, &[_][]const u8{ git, "add", zet.fname });
- try util.execAndCheck(allocator, &[_][]const u8{ git, "commit", "-m", zet.title });
- try util.execAndCheck(allocator, &[_][]const u8{ git, "stash", "pop", "--quiet" });
+ _ = try util.execAndCheck(allocator, &[_][]const u8{ git, "add", zet.fname });
+ _ = try util.execAndCheck(allocator, &[_][]const u8{ git, "commit", "-m", zet.title });
+ _ = try util.execAndCheck(allocator, &[_][]const u8{ git, "stash", "pop", "--quiet" });
}
M src/cmd/open.zig => src/cmd/open.zig +18 -7
@@ 1,19 1,30 @@
const std = @import("std");
+const stderr = std.io.getStdErr().outStream();
-const zettel = @import("../zettel.zig");
+const Zettel = @import("../zettel.zig").Zettel;
-pub const usage = "o|open [name]";
-pub const desc = "Open the given note in your $EDITOR";
+pub const usage = "o|open [NOTE [NOTE ...]]";
+pub const desc = "Open the given notes in your $EDITOR";
-pub fn run(allocator: *std.mem.Allocator, zet: ?zettel.Zettel) !void {
+pub fn run(allocator: *std.mem.Allocator, zettels: ?[]const Zettel) !void {
var args = std.ArrayList([]const u8).init(allocator);
try args.append(std.os.getenv("EDITOR") orelse "vi");
- if (zet != null) {
- try args.append(zet.?.fname);
+ if (zettels != null) {
+ for (zettels.?) |zet| {
+ try args.append(zet.fname);
+ }
}
var proc = try std.ChildProcess.init(args.items, allocator);
defer proc.deinit();
- _ = try proc.spawnAndWait();
+ const term = try proc.spawnAndWait();
+ switch (term) {
+ .Exited => {},
+ else => {
+ try stderr.print("The following command terminated unexpectedly:\n", .{});
+ for (args.items) |arg| try stderr.print("{} ", .{arg});
+ return error.ExecFailed;
+ },
+ }
}
M src/cmd/preview.zig => src/cmd/preview.zig +44 -32
@@ 4,47 4,59 @@ const stderr = std.io.getStdErr().outStream();
const Zettel = @import("../zettel.zig").Zettel;
const util = @import("../util.zig");
-pub const usage = "p|preview <name>";
-pub const desc = "View a note as HTML";
+pub const usage = "p|preview <NOTE> [NOTE ...]";
+pub const desc = "View notes as HTML";
-pub fn run(allocator: *std.mem.Allocator, zet: Zettel) !void {
+pub fn run(allocator: *std.mem.Allocator, zettels: []const Zettel) !void {
const pandoc = util.expandPath(allocator, "pandoc") orelse {
try stderr.print("Couldn't find pandoc on PATH\n", .{});
return;
};
- var html_file = try std.fmt.allocPrint(allocator, "{}{}", .{ zet.basename, ".html" });
- defer allocator.free(html_file);
-
- try util.execAndCheck(allocator, &[_][]const u8{
- pandoc,
- "--standalone",
- "--to",
- "html",
- "--from",
- "markdown",
- "--output",
- html_file,
- zet.fname,
- });
-
- var open_cmd: []const u8 = undefined;
- if (std.os.getenv("BROWSER")) |browser| {
- open_cmd = try std.mem.dupe(allocator, u8, browser);
- } else if (util.expandPath(allocator, "xdg-open")) |xdg_open| {
- open_cmd = xdg_open;
- } else if (util.expandPath(allocator, "open")) |open| {
- open_cmd = open;
- } else if (util.expandPath(allocator, "firefox")) |firefox| {
- open_cmd = firefox;
- } else {
- return error.NoAvailableBrowser;
- }
+ const open_cmd = blk: {
+ if (std.os.getenv("BROWSER")) |browser| {
+ break :blk try std.mem.dupe(allocator, u8, browser);
+ } else if (util.expandPath(allocator, "xdg-open")) |xdg_open| {
+ break :blk xdg_open;
+ } else if (util.expandPath(allocator, "open")) |open| {
+ break :blk open;
+ } else if (util.expandPath(allocator, "firefox")) |firefox| {
+ break :blk firefox;
+ } else {
+ return error.NoAvailableBrowser;
+ }
+ };
defer allocator.free(open_cmd);
- try util.execAndCheck(allocator, &[_][]const u8{ open_cmd, html_file });
+ var output_files = std.ArrayList([]const u8).init(allocator);
+ defer {
+ for (output_files.items) |file| {
+ std.fs.cwd().deleteFile(file) catch {};
+ allocator.free(file);
+ }
+ output_files.deinit();
+ }
+
+ for (zettels) |zet| {
+ var html_file = try std.fmt.allocPrint(allocator, "{}{}", .{ zet.basename, ".html" });
+ // Don't defer free, it's done in the outer defer block
+ try output_files.append(html_file);
+
+ _ = try util.execAndCheck(allocator, &[_][]const u8{
+ pandoc,
+ "--standalone",
+ "--to",
+ "html",
+ "--from",
+ "markdown",
+ "--output",
+ html_file,
+ zet.fname,
+ });
+
+ _ = try util.execAndCheck(allocator, &[_][]const u8{ open_cmd, html_file });
+ }
// Sleep so that the chosen `open_cmd` has time to open the file before it's deleted
std.time.sleep(1 * std.time.ns_per_s);
- try std.fs.cwd().deleteFile(html_file);
}
M src/cmd/search.zig => src/cmd/search.zig +8 -4
@@ 3,17 3,21 @@ const stdout = std.io.getStdOut().outStream();
const Zettel = @import("../zettel.zig").Zettel;
-pub const usage = "s|search <pattern>";
-pub const desc = "Search notes for the given pattern";
+pub const usage = "s|search <PATTERN> [PATTERN ...]";
+pub const desc = "Search for notes whose contents match any of the given patterns";
-pub fn run(allocator: *std.mem.Allocator, pattern: []const u8, zettels: []const Zettel) !void {
+pub fn run(allocator: *std.mem.Allocator, patterns: [][]const u8, zettels: []const Zettel) !void {
var matches = std.ArrayList(Zettel).init(allocator);
defer matches.deinit();
var args = std.ArrayList([]const u8).init(allocator);
defer args.deinit();
- try args.appendSlice(&[_][]const u8{ "grep", "-Fil", pattern });
+ try args.appendSlice(&[_][]const u8{ "grep", "-Fil" });
+ for (patterns) |pat| {
+ try args.appendSlice(&[_][]const u8{ "-e", pat });
+ }
+
for (zettels) |zet| {
try args.append(zet.fname);
}
M src/cmd/show.zig => src/cmd/show.zig +11 -9
@@ 4,16 4,18 @@ const stderr = std.io.getStdErr().outStream();
const Zettel = @import("../zettel.zig").Zettel;
-pub const usage = "sh|show <name>";
-pub const desc = "Display contents of a note to stdout";
+pub const usage = "sh|show <NOTE> [NOTE ...]";
+pub const desc = "Display contents of notes to stdout";
-pub fn run(allocator: *std.mem.Allocator, zet: Zettel) !void {
- var file = try std.fs.cwd().openFile(zet.fname, .{ .read = true });
- defer file.close();
+pub fn run(allocator: *std.mem.Allocator, zettels: []const Zettel) !void {
+ for (zettels) |zet| {
+ var file = try std.fs.cwd().openFile(zet.fname, .{ .read = true });
+ defer file.close();
- const size = try file.getEndPos();
- const contents = try file.inStream().readAllAlloc(allocator, size);
- defer allocator.free(contents);
+ const size = try file.getEndPos();
+ const contents = try file.inStream().readAllAlloc(allocator, size);
+ defer allocator.free(contents);
- try stdout.print("{}", .{contents});
+ try stdout.print("{}", .{contents});
+ }
}
M src/cmd/tags.zig => src/cmd/tags.zig +16 -14
@@ 3,36 3,38 @@ const stdout = std.io.getStdOut().outStream();
const Zettel = @import("../zettel.zig").Zettel;
-pub const usage = "t|tags [tag]";
-pub const desc = "With no argument, list all tags found in notes. Otherwise list all notes containing the given tag";
+pub const usage = "t|tags [TAG [TAG ...]]";
+pub const desc = "With no argument, list all tags found in notes. Otherwise list all notes containing any of the given tags";
-pub fn run(allocator: *std.mem.Allocator, tag: ?[]const u8, zettels: []const Zettel) !void {
- if (tag == null) {
+pub fn run(allocator: *std.mem.Allocator, tags: ?[][]const u8, zettels: []const Zettel) !void {
+ if (tags == null) {
// List tags
- var tags = std.ArrayList([]const u8).init(allocator);
- defer tags.deinit();
+ var existing_tags = std.ArrayList([]const u8).init(allocator);
+ defer existing_tags.deinit();
for (zettels) |zet| {
- try tags.appendSlice(zet.tags);
+ try existing_tags.appendSlice(zet.tags);
}
- std.sort.sort([]const u8, tags.items, struct {
+ std.sort.sort([]const u8, existing_tags.items, struct {
fn lessThan(lhs: []const u8, rhs: []const u8) bool {
return std.mem.lessThan(u8, lhs, rhs);
}
}.lessThan);
- for (tags.items) |t, i| {
- if (i == 0 or !std.mem.eql(u8, t, tags.items[i - 1])) {
+ for (existing_tags.items) |t, i| {
+ if (i == 0 or !std.mem.eql(u8, t, existing_tags.items[i - 1])) {
try stdout.print("{}\n", .{t});
}
}
} else {
- for (zettels) |zet| {
+ outer: for (zettels) |zet| {
for (zet.tags) |t| {
- if (std.ascii.eqlIgnoreCase(tag.?, t)) {
- try stdout.print("{} {}\n", .{ zet.id, zet.title });
- break;
+ for (tags.?) |tag| {
+ if (std.ascii.eqlIgnoreCase(tag, t)) {
+ try stdout.print("{} {}\n", .{ zet.id, zet.title });
+ continue :outer;
+ }
}
}
}
M src/main.zig => src/main.zig +8 -5
@@ 11,7 11,7 @@ fn printUsage(exe: []const u8) void {
inline for (@typeInfo(cmd.Command).Enum.fields) |command| {
comptime var usage = @field(cmd.Command, command.name).usage();
stdout.print(" {}", .{usage}) catch return;
- stdout.print("{}{}\n", .{ " " ** (24 - usage.len), @field(cmd.Command, command.name).desc() }) catch return;
+ stdout.print("{}{}\n", .{ " " ** (36 - usage.len), @field(cmd.Command, command.name).desc() }) catch return;
}
}
@@ 65,9 65,9 @@ pub fn main() anyerror!void {
try std.process.changeCurDir(dir);
const command = arglist[1];
- const arg: ?[]const u8 = if (arglist.len > 2) arglist[2] else null;
+ const args: ?[][]const u8 = if (arglist.len > 2) arglist[2..] else null;
- cmd.handleCommand(command, arg, allocator) catch |err| return switch (err) {
+ cmd.handleCommand(allocator, command, args) catch |err| return switch (err) {
error.UnknownCommand => {
try stderr.print("Unknown command: {}\n", .{command});
printUsage(exe);
@@ 78,8 78,11 @@ pub fn main() anyerror!void {
printUsage(exe);
std.process.exit(1);
},
- error.DoesNotExist => {
- try stderr.print("Couldn't find any zettels matching {}\n", .{arg.?});
+ error.NoAvailableBrowser => {
+ try stderr.print("Couldn't find a way to open compiled notes\n", .{});
+ std.process.exit(1);
+ },
+ error.NoMatches => {
std.process.exit(1);
},
else => return err,
M src/util.zig => src/util.zig +3 -1
@@ 15,7 15,7 @@ pub fn expandPath(allocator: *std.mem.Allocator, command: []const u8) ?[]const u
return null;
}
-pub fn execAndCheck(allocator: *std.mem.Allocator, args: [][]const u8) !void {
+pub fn execAndCheck(allocator: *std.mem.Allocator, args: [][]const u8) !std.ChildProcess.ExecResult {
const result = try std.ChildProcess.exec(.{
.allocator = allocator,
.argv = args,
@@ 37,4 37,6 @@ pub fn execAndCheck(allocator: *std.mem.Allocator, args: [][]const u8) !void {
return error.ExecFailed;
},
}
+
+ return result;
}
M src/zettel.zig => src/zettel.zig +21 -3
@@ 1,7 1,6 @@
const std = @import("std");
-const c = @cImport({
- @cInclude("time.h");
-});
+const stderr = std.io.getStdErr().outStream();
+const c = @cImport(@cInclude("time.h"));
const ID_LENGTH = "20200106121000".len;
const EXT = ".md";
@@ 87,6 86,25 @@ pub fn getZettels(allocator: *std.mem.Allocator) ![]const Zettel {
return entries.toOwnedSlice();
}
+pub fn findZettels(allocator: *std.mem.Allocator, zettels: []const Zettel, keywords: [][]const u8) ![]Zettel {
+ var found = std.ArrayList(Zettel).init(allocator);
+ defer found.deinit();
+
+ for (keywords) |keyword| {
+ if (findZettel(zettels, keyword)) |zet| {
+ try found.append(zet);
+ } else {
+ try stderr.print("No matches found for '{}'\n", .{keyword});
+ }
+ }
+
+ if (found.items.len == 0) {
+ return error.NoMatches;
+ }
+
+ return found.toOwnedSlice();
+}
+
pub fn findZettel(zettels: []const Zettel, keyword: []const u8) ?Zettel {
comptime var fields = .{ "id", "fname", "basename", "title" };
for (zettels) |zet| {