@@ 1,273 1,16 @@
const std = @import("std");
+const util = @import("./util.zig");
+const Note = @import("./note.zig").Note;
+
const fs = std.fs;
const mem = std.mem;
-const fmt = std.fmt;
-const util = @import("./util.zig");
pub const io_mode = .evented;
var debug_mode = false;
-const ref_prefix = "%ref";
// Presumably this should be longer?
const max_line_length = 1024;
-const RuntimeError = error{
- IllegalCharacterInBrackets,
-};
-
-const begin_braces = '{';
-const end_braces = '}';
-
-const CharBuffer = std.ArrayList(u8);
-
-const BufferAndBraces = struct {
- buffer: CharBuffer,
- offset: usize,
- slice_begins: std.ArrayList(usize),
-
- fn init(offset: usize, allocator: mem.Allocator) @This() {
- return @This(){
- .buffer = CharBuffer.init(allocator),
- .offset = offset,
- .slice_begins = std.ArrayList(usize).init(allocator),
- };
- }
-
- fn newBrace(self: *@This(), body_offset: usize) !void {
- try self.slice_begins.append(body_offset - self.offset);
- }
-
- fn currentBracesCount(self: @This()) usize {
- return self.slice_begins.items.len;
- }
-
- fn pop(self: *@This()) usize {
- return self.slice_begins.pop();
- }
-
- fn deinit(self: @This()) void {
- self.buffer.deinit();
- self.slice_begins.deinit();
- }
-};
-
-const EscapedString = struct {
- escaped: CharBuffer,
- escape_error: ?EscapeError = null,
-
- const EscapeError = enum { TerminalPeriod };
-
- fn init(to_escape: []const u8, allocator: mem.Allocator) !@This() {
- return EscapedString{
- .escaped = try CharBuffer.initCapacity(allocator, to_escape.len * 2),
- };
- }
-
- // Process a string and escape any delimiters for reference creation.
- fn escape(self: *@This(), s: []const u8) void {
- // Check for illegal characters. Right now the only illegal character is a
- // terminal period, which confuses vim, so this is a bit particular.
- if (s.len > 0 and s[s.len - 1] == '.') {
- self.escape_error = EscapeError.TerminalPeriod;
- return;
- }
- for (s) |char| {
- switch (char) {
- ' ' => {
- self.escaped.appendAssumeCapacity('\\');
- },
- else => {},
- }
- const lower_char = std.ascii.toLower(char);
- self.escaped.appendAssumeCapacity(lower_char);
- }
- }
-
- fn handleErrors(self: @This()) void {
- if (self.escape_error) |escape_error| {
- switch (escape_error) {
- EscapeError.TerminalPeriod => {
- std.debug.print("Note references can't end with a period", .{});
- std.os.exit(1);
- },
- }
- }
- }
-};
-
-// Iterate through a slice and populate an ArrayList with subslices capturing
-// "{<reference>}"
-fn findBracedStrings(
- note_body: []const u8,
- references: *std.ArrayList([]const u8),
- allocator: mem.Allocator,
-) !void {
- var brace_context: ?BufferAndBraces = null;
-
- var i: usize = 0;
- while (i < note_body.len) : (i += 1) {
- const char = note_body[i];
- switch (char) {
- begin_braces => {
- // Use a BufferAndBraces, which can keep track of multiple open
- // braces, to handle nested braces. We'll copy the underlying
- // contents of an outer brace into a buffer while noting (but
- // not copying) any nested braces inside it.
- if (brace_context == null) {
- brace_context = BufferAndBraces.init(i, allocator);
- }
- if (brace_context) |*ctx| {
- try ctx.newBrace(i);
- }
- },
- end_braces => {
- if (brace_context) |*ctx| {
- // Subtract the number of current open braces, because
- // those won't have been included in the current buffer.
- const begin = ctx.pop() - ctx.currentBracesCount();
- const reference_text = ctx.buffer.items[begin..];
-
- const new_string = try allocator.alloc(u8, reference_text.len);
- std.mem.copy(u8, new_string, reference_text);
-
- try references.append(new_string);
-
- if (ctx.slice_begins.items.len == 0) {
- ctx.deinit();
- brace_context = null;
- }
- }
- },
- '\n' => {
- // References can't cross over lines.
- if (brace_context) |ctx| {
- ctx.deinit();
- brace_context = null;
- }
- },
- else => {
- // While we're currently in open braces, collect the non-brace
- // strings.
- if (brace_context) |*ctx| {
- try ctx.buffer.append(char);
- }
- },
- }
- }
-}
-
-const Note = struct {
- name: []const u8,
- body: CharBuffer,
- new_body: CharBuffer,
- backlinks: std.ArrayList([]const u8),
- references: std.ArrayList([]const u8),
-
- fn init(name: []const u8, allocator: mem.Allocator) @This() {
- return @This(){
- .name = name,
- .body = CharBuffer.init(allocator),
- .new_body = CharBuffer.init(allocator),
- .backlinks = std.ArrayList([]const u8).init(allocator),
- .references = std.ArrayList([]const u8).init(allocator),
- };
- }
-
- fn deinit(self: @This()) void {
- self.new_body.deinit();
- self.backlinks.deinit();
- self.references.deinit();
- }
-
- fn append(self: *@This(), c: u8) !void {
- try self.new_body.append(c);
- }
-
- fn appendSlice(self: *@This(), slice: []u8) !void {
- try self.new_body.appendSlice(slice);
- }
-
- // Create a reference line and add it to the note.
- fn appendRef(self: *@This(), referent: []const u8, allocator: mem.Allocator) !void {
- const ref = try fmt.allocPrint(allocator, "{s}:{s}", .{
- ref_prefix,
- referent,
- });
- try self.appendSlice(ref);
- try self.append('\n');
- }
-
- // Backlinks: if any other note body refers to this one, add a reference to
- // that body's name.
- fn appendBacklinks(
- self: *@This(),
- notes: *std.ArrayList(Note),
- allocator: mem.Allocator,
- ) !void {
- var found_one = false;
- const link_here = try fmt.allocPrint(allocator, "{c}{s}{c}", .{
- begin_braces,
- self.name,
- end_braces,
- });
-
- for (notes.items) |other_note| {
- const items = other_note.body.items;
- const lower_items = try std.ascii.allocLowerString(allocator, items);
- if (mem.indexOf(u8, lower_items, link_here) != null) {
- found_one = true;
-
- var escaped = try EscapedString.init(other_note.name, allocator);
- defer escaped.escaped.deinit();
-
- escaped.escape(other_note.name);
- escaped.handleErrors();
-
- try self.appendRef(escaped.escaped.items, allocator);
- }
- allocator.free(lower_items);
- }
-
- // Backlinks padding.
- if (found_one) {
- try self.append('\n');
- }
- }
-
- // References: gather every reference in the body of the note.
- fn appendReferences(
- self: *@This(),
- allocator: mem.Allocator,
- ) !void {
- var braces = std.ArrayList([]const u8).init(allocator);
- defer braces.deinit();
-
- try findBracedStrings(self.body.items, &braces, allocator);
-
- // References padding.
- if (braces.items.len > 0) {
- try self.append('\n');
- }
-
- var seen = std.BufSet.init(allocator);
-
- // Escape and add forward references to note.
- for (braces.items) |phrase| {
- var escaped = try EscapedString.init(phrase, allocator);
- defer escaped.escaped.deinit();
-
- escaped.escape(phrase);
- escaped.handleErrors();
-
- const str = escaped.escaped.items;
- if (!seen.contains(str)) {
- try seen.insert(str);
- try self.appendRef(str, allocator);
- }
- }
- }
-};
-
const help_text =
\\Usage: er [-h] [-d] [DIRECTORY_NAME]
\\
@@ 279,7 22,7 @@ const help_text =
// Read a note from disk, filtering out any lines
// inserted by the indexing process.
-fn readBody(entry_name: []const u8, contents: *CharBuffer) !void {
+fn readBody(entry_name: []const u8, contents: *util.CharBuffer) !void {
const file = try fs.cwd().openFile(entry_name, .{});
defer file.close();
@@ 291,10 34,10 @@ fn readBody(entry_name: []const u8, contents: *CharBuffer) !void {
var buf: [max_line_length]u8 = undefined;
var maybe_line = try util.nextLine(reader, &buf);
if (maybe_line) |line| {
- if (line.len > ref_prefix.len and mem.eql(
+ if (line.len > util.ref_prefix.len and mem.eql(
u8,
- ref_prefix,
- line[0..ref_prefix.len],
+ util.ref_prefix,
+ line[0..util.ref_prefix.len],
)) {
// Filter out a ref line.
continue;
@@ 367,34 110,35 @@ pub fn main() !void {
}
}
- // For each note, gather backlinks and references, and generate
- // contents.
+ // Populate all note metadata required to generate a new note body.
for (notes.items) |*note| {
- try note.appendBacklinks(¬es, allocator);
- try note.appendSlice(note.body.items);
- try note.appendReferences(allocator);
+ try note.populateNoteData(notes, allocator);
+ }
+
+ // Assemble new note bodies.
+ var note_bodies = std.ArrayList(struct { name: []const u8, body: util.CharBuffer }).init(allocator);
+ for (notes.items) |*note| {
+ var buf = util.CharBuffer.init(allocator);
+ try note.generateBody(&buf, allocator);
// Optional debugging.
if (debug_mode) {
std.debug.print("{s}:\n\n{s}\n", .{
note.name,
- note.new_body.items,
+ buf.items,
});
}
- }
- // Free the original note bodies.
- for (notes.items) |note| {
- note.body.deinit();
+ try note_bodies.append(.{ .name = note.name, .body = buf });
+ note.deinit();
}
// Write to disk.
- for (notes.items) |note| {
+ for (note_bodies.items) |new_note| {
const path = try fs.path.join(allocator, &[_][]const u8{
directory_name,
- note.name,
+ new_note.name,
});
- try fs.cwd().writeFile(path, note.new_body.items);
- note.deinit();
+ try fs.cwd().writeFile(path, new_note.body.items);
}
}
@@ 0,0 1,270 @@
+const std = @import("std");
+const util = @import("./util.zig");
+
+const mem = std.mem;
+const fmt = std.fmt;
+
+const EscapedString = struct {
+ escaped: util.CharBuffer,
+ escape_error: ?EscapeError = null,
+
+ const EscapeError = enum { TerminalPeriod };
+
+ fn init(to_escape: []const u8, allocator: mem.Allocator) !@This() {
+ return EscapedString{
+ .escaped = try util.CharBuffer.initCapacity(allocator, to_escape.len * 2),
+ };
+ }
+
+ // Process a string and escape any delimiters for reference creation.
+ fn escapeAndLower(self: *@This(), s: []const u8) void {
+ // Check for illegal characters. Right now the only illegal character is a
+ // terminal period, which confuses vim, so this is a bit particular.
+ if (s.len > 0 and s[s.len - 1] == '.') {
+ self.escape_error = EscapeError.TerminalPeriod;
+ return;
+ }
+ for (s) |char| {
+ switch (char) {
+ ' ' => {
+ self.escaped.appendAssumeCapacity('\\');
+ },
+ else => {},
+ }
+ const lower_char = std.ascii.toLower(char);
+ self.escaped.appendAssumeCapacity(lower_char);
+ }
+ }
+
+ fn handleErrors(self: @This()) void {
+ if (self.escape_error) |escape_error| {
+ switch (escape_error) {
+ EscapeError.TerminalPeriod => {
+ std.debug.print("Note references can't end with a period", .{});
+ std.os.exit(1);
+ },
+ }
+ }
+ }
+};
+
+const BufferAndBraces = struct {
+ buffer: util.CharBuffer,
+ offset: usize,
+ slice_begins: std.ArrayList(usize),
+
+ fn init(offset: usize, allocator: mem.Allocator) @This() {
+ return @This(){
+ .buffer = util.CharBuffer.init(allocator),
+ .offset = offset,
+ .slice_begins = std.ArrayList(usize).init(allocator),
+ };
+ }
+
+ fn newBrace(self: *@This(), body_offset: usize) !void {
+ try self.slice_begins.append(body_offset - self.offset);
+ }
+
+ fn currentBracesCount(self: @This()) usize {
+ return self.slice_begins.items.len;
+ }
+
+ fn pop(self: *@This()) usize {
+ return self.slice_begins.pop();
+ }
+
+ fn deinit(self: @This()) void {
+ self.buffer.deinit();
+ self.slice_begins.deinit();
+ }
+};
+
+const begin_braces = '{';
+const end_braces = '}';
+
+// Create a reference line and add it to the note.
+fn appendRef(buf: *util.CharBuffer, referent: []const u8, allocator: mem.Allocator) !void {
+ const ref = try fmt.allocPrint(allocator, "{s}:{s}", .{
+ util.ref_prefix,
+ referent,
+ });
+ try buf.appendSlice(ref);
+ try buf.append('\n');
+}
+
+pub const Note = struct {
+ name: []const u8,
+ body: util.CharBuffer,
+ backlinks: std.ArrayList([]const u8),
+ references: std.ArrayList([]const u8),
+
+ pub fn init(name: []const u8, allocator: mem.Allocator) @This() {
+ return @This(){
+ .name = name,
+ .body = util.CharBuffer.init(allocator),
+ .backlinks = std.ArrayList([]const u8).init(allocator),
+ .references = std.ArrayList([]const u8).init(allocator),
+ };
+ }
+
+ pub fn deinit(self: @This()) void {
+ self.body.deinit();
+ self.references.deinit();
+ }
+
+ // Iterate through the rest of the notes populate self.backlinks with names
+ // of notes containing "{<self.name>}"
+ pub fn populateBacklinks(
+ self: *@This(),
+ notes: std.ArrayList(Note),
+ allocator: mem.Allocator,
+ ) !void {
+ const self_lowered = try std.ascii.allocLowerString(allocator, self.name);
+ defer allocator.free(self_lowered);
+
+ for (notes.items) |other_note| {
+ for (other_note.references.items) |reference| {
+ const reference_lowered = try std.ascii.allocLowerString(allocator, reference);
+ defer allocator.free(reference_lowered);
+
+ if (mem.eql(u8, reference_lowered, self_lowered)) {
+ var escaped = try EscapedString.init(other_note.name, allocator);
+ defer escaped.escaped.deinit();
+
+ escaped.escapeAndLower(other_note.name);
+ escaped.handleErrors();
+
+ try self.backlinks.append(escaped.escaped.items);
+ break;
+ }
+ }
+ }
+ }
+
+ // Iterate through the note body and populate self.references with subslices
+ // capturing "{<reference>}"
+ pub fn populateReferences(self: *@This(), allocator: mem.Allocator) !void {
+ var brace_context: ?BufferAndBraces = null;
+
+ var i: usize = 0;
+ while (i < self.body.items.len) : (i += 1) {
+ const char = self.body.items[i];
+ switch (char) {
+ begin_braces => {
+ // Use a BufferAndBraces, which can keep track of multiple open
+ // braces, to handle nested braces. We'll copy the underlying
+ // contents of an outer brace into a buffer while noting (but
+ // not copying) any nested braces inside it.
+ if (brace_context == null) {
+ brace_context = BufferAndBraces.init(i, allocator);
+ }
+ if (brace_context) |*ctx| {
+ try ctx.newBrace(i);
+ }
+ },
+ end_braces => {
+ if (brace_context) |*ctx| {
+ // Subtract the number of current open braces, because
+ // those won't have been included in the current buffer.
+ const begin = ctx.pop() - ctx.currentBracesCount();
+ const reference_text = ctx.buffer.items[begin..];
+
+ const new_string = try allocator.alloc(u8, reference_text.len);
+ mem.copy(u8, new_string, reference_text);
+
+ try self.references.append(new_string);
+
+ if (ctx.slice_begins.items.len == 0) {
+ ctx.deinit();
+ brace_context = null;
+ }
+ }
+ },
+ '\n' => {
+ // References can't cross over lines.
+ if (brace_context) |ctx| {
+ ctx.deinit();
+ brace_context = null;
+ }
+ },
+ else => {
+ // While we're currently in open braces, collect the non-brace
+ // strings.
+ if (brace_context) |*ctx| {
+ try ctx.buffer.append(char);
+ }
+ },
+ }
+ }
+ }
+
+ // Backlinks: if any other note body refers to this one, add a reference to
+ // that body's name.
+ fn appendBacklinks(
+ self: *@This(),
+ buf: *util.CharBuffer,
+ allocator: mem.Allocator,
+ ) !void {
+ var found_one = false;
+
+ for (self.backlinks.items) |backlink| {
+ found_one = true;
+ try appendRef(buf, backlink, allocator);
+ }
+
+ // Backlinks padding.
+ if (found_one) {
+ try buf.append('\n');
+ }
+ }
+
+ pub fn appendBody(self: *@This(), buf: *util.CharBuffer) !void {
+ try buf.appendSlice(self.body.items);
+ }
+
+ // References: gather every reference in the body of the note.
+ pub fn appendReferences(
+ self: *@This(),
+ buf: *util.CharBuffer,
+ allocator: mem.Allocator,
+ ) !void {
+ // References padding.
+ if (self.references.items.len > 0) {
+ try buf.append('\n');
+ }
+
+ // Dedupe lowered and escaped references so we only link out once per
+ // reference.
+ var seen = std.BufSet.init(allocator);
+
+ // Escape and add forward references to note.
+ for (self.references.items) |phrase| {
+ var escaped = try EscapedString.init(phrase, allocator);
+ defer escaped.escaped.deinit();
+
+ escaped.escapeAndLower(phrase);
+ escaped.handleErrors();
+
+ const str = escaped.escaped.items;
+ if (!seen.contains(str)) {
+ try seen.insert(str);
+ try appendRef(buf, str, allocator);
+ }
+ }
+ }
+
+ pub fn populateNoteData(self: *@This(), notes: std.ArrayList(Note), allocator: mem.Allocator) !void {
+ try self.populateBacklinks(notes, allocator);
+ try self.populateReferences(allocator);
+ }
+
+ pub fn generateBody(
+ self: *@This(),
+ buf: *util.CharBuffer,
+ allocator: mem.Allocator,
+ ) !void {
+ try self.appendBacklinks(buf, allocator);
+ try self.appendBody(buf);
+ try self.appendReferences(buf, allocator);
+ }
+};