diff --git a/lua/zettelkasten/init.lua b/lua/zettelkasten/init.lua index d763e47..44a08ae 100644 --- a/lua/zettelkasten/init.lua +++ b/lua/zettelkasten/init.lua @@ -1,22 +1,24 @@ +-- feature wishlist +-- * note creation (new anchor) +-- * link creation (to existing note) +-- * link following (to existing anchor) +-- * note search (title / full-text) +-- * note listing (anchors / titles, no anchor) +-- * options +-- * zettel anchor regex +-- * zettel anchor separator +-- * zettel extension local ZK = {} -local zettel_extension, anchor_separator - -local api -if vim ~= nil then - api = vim.api - anchor_separator = vim.g["zettel_anchor_separator"] or vim.b["zettel_anchor_separator"] or "_" - zettel_extension = vim.g["zettel_extension"] or vim.b["zettel_extension"] or ".md" -end -package.loaded['zettelkasten'] = nil - -local anchor_separator = "_" +local ls = require'zettelkasten.list' function ZK.init(vimapi) vim = vimapi or vim - anchor_separator = vim.g["zettel_anchor_separator"] or vim.b["zettel_anchor_separator"] or "_" - zettel_extension = vim.g["zettel_extension"] or vim.b["zettel_extension"] or ".md" - zettel_root = vim.g["zettel_extension"] or vim.b["zettel_extension"] or ".md" + ZK.options = { + anchor_separator = vim.g["zettel_anchor_separator"] or vim.b["zettel_anchor_separator"] or "_", + zettel_extension = vim.g["zettel_extension"] or vim.b["zettel_extension"] or ".md", + zettel_root = vim.g["zettel_root"] or vim.b["zettel_root"] or "~/documents/notes", + } end -- entrypoint for pressing the zettel key when the cursor @@ -47,49 +49,23 @@ end -- and the text cleaned up to be useful in a link. function ZK.create_link(text, date) text = text or "" + local o = ZK.options if text == "" then text = "" .. ZK.create_anchor(date) else text = text:lower():gsub(" ", "-") - text = "" .. ZK.create_anchor(date) .. anchor_separator .. text + text = "" .. ZK.create_anchor(date) .. o.anchor_separator .. text end - return text .. (zettel_extension or ".md") + return text .. (o.zettel_extension or ".md") end -function ZK._get_anchors_and_paths(path, recursive) - -- TODO check for duplicates and warn user - local zettel = {} - local anchorreg = '^.*/?([%d][%d][%d][%d][%d][%d][%d][%d][%d][%d])[^/]*.md$' - - local handle = vim.loop.fs_scandir(path) - while handle do - local name, ftype = vim.loop.fs_scandir_next(handle) - if not name then break end - - if ftype == 'directory' and recursive then - local subdir = ZK._get_anchors_and_paths(path .. "/" .. name, true) - for k, v in pairs(subdir) do - zettel[tostring(k)] = v - end - end - - local anchor = string.match(name, anchorreg) - if anchor then - zettel[tostring(anchor)] = name - end - end - return zettel -end -- Returns all zettel in path as a -- { "anchor" = "path/to/zettel/anchor filename.md" } -- table. -- Recurses into subdirectories if recursive argument is true. function ZK.get_zettel_list(path, recursive) - -- TODO transform paths: - -- * to absolute value (e.g. ~ to home, scandir needs absolute) - -- * to ensure / at the end (or no /) gets taken into account - return ZK._get_anchors_and_paths(path, recursive or false) + return ls.get_anchors_and_paths(path, recursive or false) end return { diff --git a/lua/zettelkasten/init_spec.lua b/lua/zettelkasten/init_spec.lua new file mode 100644 index 0000000..7aa737c --- /dev/null +++ b/lua/zettelkasten/init_spec.lua @@ -0,0 +1,53 @@ +ZK = require'zettelkasten.init' +Test_date={ year=2019, month=10, day=29, hour=16, min=45 } + +describe("Zettelkasten", function() + before_each(function() + ZK.init({ g={}, b={} }) + end) + + describe("anchor creation", function() + it("should return zettel anchor from time passed in", function() + assert.same("1910291645", ZK.create_anchor(Test_date)) + end) + + it("should return zettel anchor from current moment if no argument passed in", function() + assert.same(os.date('%y%m%d%H%M'), ZK.create_anchor()) + end) + + it("should return nil if argument passed in is invalid", function() + assert.is_nil(ZK.create_anchor("My grandmother is lovely.")) + end) + end) + + describe("link creation", function() + it("should return a markdown link with only zettel anchor on no text passed in", function() + assert.same("1910291645.md", ZK.create_link(nil, Test_date)) + end) + + it("should text to link", function() + assert.same("1910291645_isappended.md", ZK.create_link("isappended", Test_date)) + end) + + it("should return lowercased link text", function() + assert.same("1910291645_yesiamshouting.md", ZK.create_link("YESIAMSHOUTING", Test_date)) + end) + + it("should return spaces in text replaced with dashes", function() + assert.same("1910291645_yes-indeed-a-space.md", ZK.create_link("yes indeed a space", Test_date)) + end) + + it("should place the contents of g:zettel_anchor_separator variable in link", function() + vim = { g = { zettel_anchor_separator = "SEP" }, b = {}} + ZK.init(vim) + assert.same("1910291645SEParated.md", ZK.create_link("arated", Test_date)) + end) + + it("should append the filetype set in g:zettel_extension", function() + vim = { g = { zettel_extension = ".something" }, b = {}} + ZK.init(vim) + assert.same("1910291645_theworld.something", ZK.create_link("theworld", Test_date)) + end) + end) + +end) diff --git a/lua/zettelkasten/list.lua b/lua/zettelkasten/list.lua new file mode 100644 index 0000000..66c8e5f --- /dev/null +++ b/lua/zettelkasten/list.lua @@ -0,0 +1,36 @@ +local ls = {} + +local function isDirectory(ftype) + if ftype == 'directory' then return true end + return false +end + + -- TODO transform paths: + -- * to absolute value (e.g. ~ to home, scandir needs absolute) + -- * to ensure / at the end (or no /) gets taken into account +function ls.get_anchors_and_paths(path, recursive) + -- TODO check for duplicates and warn user + local zettel = {} + local anchorreg = '^.*/?([%d][%d][%d][%d][%d][%d][%d][%d][%d][%d])[^/]*.md$' + + local handle = vim.loop.fs_scandir(path) + while handle do + local name, ftype = vim.loop.fs_scandir_next(handle) + if not name then break end + + if isDirectory(ftype) and recursive then + local subdir = ls.get_anchors_and_paths(path .. "/" .. name, true) + for k, v in pairs(subdir) do + zettel[tostring(k)] = v + end + end + + local anchor = string.match(name, anchorreg) + if anchor then + zettel[tostring(anchor)] = name + end + end + return zettel +end + +return ls diff --git a/lua/zettelkasten/list_spec.lua b/lua/zettelkasten/list_spec.lua new file mode 100644 index 0000000..8eac2f8 --- /dev/null +++ b/lua/zettelkasten/list_spec.lua @@ -0,0 +1,114 @@ +ZK = require'zettelkasten.init' +-- these tests, I suppose, only work on unix due to the file structure + +describe("zettel listing", function() + before_each(function() + get_api_mock = function(files) + return { + g = {}, + b = {}, + loop = { + fs_scandir = function() + if #files == 0 then + return false + else + return true + end + end, + fs_scandir_next = function() return table.remove(files) end + } + } + end + end) + + it("should return anchor-keyed table pointing to filename of zettel", function() + local file_list = { "1910291645 this-is-a-testfile.md" } + ZK.init(get_api_mock(file_list)) + + local expected = { ["1910291645"] = "1910291645 this-is-a-testfile.md", } + assert.same(expected, ZK.get_zettel_list("someDir")) + end) + + it("should ignore any malformed files", function() + local file_list = { + "2010261208 this-should-be-picked-up.md", + "1910291645 this-is-a-testfile.md", + "this-is-not-a-testfile.md", + "1910271456 this-is-wrong-extension.txt", + "1812 this-is-ignored.md", + } + ZK.init(get_api_mock(file_list)) + + local expected = { + ["1910291645"] = "1910291645 this-is-a-testfile.md", + ["2010261208"] = "2010261208 this-should-be-picked-up.md", + } + assert.same(expected, ZK.get_zettel_list("someDir")) + end) + + it("should recurse into directories if recursive argument passed in ", function() + local files = { + { "1910271456 testfile.md", "file" }, + { "more-notes-here", "directory" }, + { "2010261208 another-testfile.md", "file" }, + } + local vim_api_mock = { + g = {}, + b = {}, + loop = mock({ + fs_scandir = function() + if #files == 0 then + return false + else + return true + end + end, + fs_scandir_next = function() + if #files == 0 then return nil end + local fname, ftype = unpack(table.remove(files)) + return fname, ftype + end + }) + } + ZK.init(vim_api_mock) + + ZK.get_zettel_list("path/to/startingdir", true) + + assert.spy(vim_api_mock.loop.fs_scandir).was_called(2) + assert.spy(vim_api_mock.loop.fs_scandir).was_called_with("path/to/startingdir/more-notes-here") + end) + + it("should append all notes found in subdirectories when recursing", function() + local outer_files = { "subdir", "1234567890 myfile.md", "2345678901 another.md", } + local inner_files = { "2222222222 should-be-present.md", "3333333333 should-also-be-present.md" } + local files = outer_files + -- assert.is_true("not implemented") + local vim_api_mock = { + g = {}, + b = {}, + loop ={ + fs_scandir = function() + if #files == 0 then return false end + return true + end, + fs_scandir_next = function() + if #files == 0 then return nil end + local fname, ftype = table.remove(files), 'file' + if fname == "subdir" then + files = inner_files + ftype = 'directory' + end + return fname, ftype + end + }} + ZK.init(vim_api_mock) + local expected = { + ["1234567890"] = "1234567890 myfile.md", + ["2345678901"] = "2345678901 another.md", + ["2222222222"] = "2222222222 should-be-present.md", + ["3333333333"] = "3333333333 should-also-be-present.md", + } + assert.same(expected, ZK.get_zettel_list('mydirectory', true)) + end) + +end) diff --git a/lua/zettelkasten/zettelkasten_spec.lua b/lua/zettelkasten/zettelkasten_spec.lua deleted file mode 100644 index 0826c41..0000000 --- a/lua/zettelkasten/zettelkasten_spec.lua +++ /dev/null @@ -1,165 +0,0 @@ -ZK = require'lua.zettelkasten.init' -Test_date={ year=2019, month=10, day=29, hour=16, min=45 } - -describe("Zettelkasten", function() - before_each(function() - ZK.init({ g={}, b={} }) - end) - - describe("anchor creation", function() - it("should return zettel anchor from time passed in", function() - assert.same("1910291645", ZK.create_anchor(Test_date)) - end) - - it("should return zettel anchor from current moment if no argument passed in", function() - assert.same(os.date('%y%m%d%H%M'), ZK.create_anchor()) - end) - - it("should return nil if argument passed in is invalid", function() - assert.is_nil(ZK.create_anchor("My grandmother is lovely.")) - end) - end) - - describe("link creation", function() - it("should return a markdown link with only zettel anchor on no text passed in", function() - assert.same("1910291645.md", ZK.create_link(nil, Test_date)) - end) - - it("should text to link", function() - assert.same("1910291645_isappended.md", ZK.create_link("isappended", Test_date)) - end) - - it("should return lowercased link text", function() - assert.same("1910291645_yesiamshouting.md", ZK.create_link("YESIAMSHOUTING", Test_date)) - end) - - it("should return spaces in text replaced with dashes", function() - assert.same("1910291645_yes-indeed-a-space.md", ZK.create_link("yes indeed a space", Test_date)) - end) - - it("should place the contents of g:zettel_anchor_separator variable in link", function() - vim = { g = { zettel_anchor_separator = "SEP" }, b = {}} - ZK.init(vim) - assert.same("1910291645SEParated.md", ZK.create_link("arated", Test_date)) - end) - - it("should append the filetype set in g:zettel_extension", function() - vim = { g = { zettel_extension = ".something" }, b = {}} - ZK.init(vim) - assert.same("1910291645_theworld.something", ZK.create_link("theworld", Test_date)) - end) - end) - - -- these tests, I suppose, only work on unix due to the file structure - describe("zettel listing", function() - before_each(function() - get_api_mock = function(files) - return { - g = {}, - b = {}, - loop = { - fs_scandir = function() - if #files == 0 then - return false - else - return true - end - end, - fs_scandir_next = function() return table.remove(files) end - } - } - end - end) - - it("should return anchor-keyed table pointing to filename of zettel", function() - local file_list = { "1910291645 this-is-a-testfile.md" } - ZK.init(get_api_mock(file_list)) - - local expected = { ["1910291645"] = "1910291645 this-is-a-testfile.md", } - assert.same(expected, ZK.get_zettel_list("someDir")) - end) - - it("should ignore any malformed files", function() - local file_list = { - "2010261208 this-should-be-picked-up.md", - "1910291645 this-is-a-testfile.md", - "this-is-not-a-testfile.md", - "1910271456 this-is-wrong-extension.txt", - "1812 this-is-ignored.md", - } - ZK.init(get_api_mock(file_list)) - - local expected = { - ["1910291645"] = "1910291645 this-is-a-testfile.md", - ["2010261208"] = "2010261208 this-should-be-picked-up.md", - } - assert.same(expected, ZK.get_zettel_list("someDir")) - end) - - it("should recurse into directories if recursive argument passed in ", function() - local files = { - { "1910271456 testfile.md", "file" }, - { "more-notes-here", "directory" }, - { "2010261208 another-testfile.md", "file" }, - } - local vim_api_mock = { - g = {}, - b = {}, - loop = mock({ - fs_scandir = function() - if #files == 0 then - return false - else - return true - end - end, - fs_scandir_next = function() - if #files == 0 then return nil end - local fname, ftype = unpack(table.remove(files)) - return fname, ftype - end - }) - } - ZK.init(vim_api_mock) - - ZK.get_zettel_list("path/to/startingdir", true) - - assert.spy(vim_api_mock.loop.fs_scandir).was_called(2) - assert.spy(vim_api_mock.loop.fs_scandir).was_called_with("path/to/startingdir/more-notes-here") - end) - - it("should append all notes found in subdirectories when recursing", function() - local outer_files = { "subdir", "1234567890 myfile.md", "2345678901 another.md", } - local inner_files = { "2222222222 should-be-present.md", "3333333333 should-also-be-present.md" } - local files = outer_files - -- assert.is_true("not implemented") - local vim_api_mock = { - g = {}, - b = {}, - loop ={ - fs_scandir = function() - if #files == 0 then return false end - return true - end, - fs_scandir_next = function() - if #files == 0 then return nil end - local fname, ftype = table.remove(files), 'file' - if fname == "subdir" then - files = inner_files - ftype = 'directory' - end - return fname, ftype - end - }} - ZK.init(vim_api_mock) - local expected = { - ["1234567890"] = "1234567890 myfile.md", - ["2345678901"] = "2345678901 another.md", - ["2222222222"] = "2222222222 should-be-present.md", - ["3333333333"] = "3333333333 should-also-be-present.md", - } - assert.same(expected, ZK.get_zettel_list('mydirectory', true)) - end) - - end) -end)