Skip to content

Commit

Permalink
Clean up templates API
Browse files Browse the repository at this point in the history
  • Loading branch information
epwalsh committed Mar 18, 2024
1 parent 4e80ab4 commit 87267f9
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 60 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ mappings = {

### Using templates

To insert a template, run the command `:ObsidianTemplate`. This will open a list of available templates in your templates folder with your preferred picker. Select a template and hit `<CR>` to insert. Substitution of `{{date}}`, `{{time}}`, and `{{title}}` is supported.
To insert a template, run the command `:ObsidianTemplate`. This will open a list of available templates in your templates folder with your preferred picker. Select a template and hit `<CR>` to insert. Substitutions for `{{id}}`, `{{title}}`, `{{path}}`, `{{date}}`, and `{{time}}` are supported out-of-the-box.

For example, with the following configuration

Expand Down
13 changes: 8 additions & 5 deletions lua/obsidian/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,7 @@ end
--- representing the text to be written excluding frontmatter, and returns the lines that will
--- actually be written (again excluding frontmatter).
Client.write_note = function(self, note, opts)
local clone_template = require("obsidian.templates").clone_template
opts = opts or {}

local path = assert(opts.path or note.path, "A path must be provided")
Expand All @@ -1768,10 +1769,7 @@ Client.write_note = function(self, note, opts)
else
verb = "Created"
if opts.template ~= nil then
require("obsidian.templates").clone_template(opts.template, path, self, note.title or note:display_name())

-- Reload note.
note = Note.from_file(path)
note = clone_template { template_name = opts.template, path = path, client = self, note = note }
end
end

Expand Down Expand Up @@ -1801,10 +1799,15 @@ end
---
---@return boolean updated If the buffer was updated.
Client.write_note_to_buffer = function(self, note, opts)
local insert_template = require("obsidian.templates").insert_template
opts = opts or {}

if opts.template and util.buffer_is_empty(opts.bufnr) then
require("obsidian.templates").insert_template(opts.template, self, util.get_active_window_cursor_location())
note = insert_template {
template_name = opts.template,
client = self,
location = util.get_active_window_cursor_location(),
}
end

local frontmatter = nil
Expand Down
2 changes: 1 addition & 1 deletion lua/obsidian/commands/template.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ return function(client, data)
local insert_location = util.get_active_window_cursor_location()

local function insert_template(name)
templates.insert_template(name, client, insert_location)
templates.insert_template { template_name = name, client = client, location = insert_location }
end

if string.len(data.args) > 0 then
Expand Down
123 changes: 72 additions & 51 deletions lua/obsidian/templates.lua
Original file line number Diff line number Diff line change
@@ -1,15 +1,52 @@
local Path = require "obsidian.path"
local Note = require "obsidian.note"
local util = require "obsidian.util"

local M = {}

---Substitute Variables inside a given Text
--- Resolve a template name to a path.
---
---@param text string - name of a template in the configured templates folder
---@param template_name string|obsidian.Path
---@param client obsidian.Client
---@param title string|nil
---
---@return obsidian.Path
local resolve_template = function(template_name, client)
local templates_dir = client:templates_dir()
if templates_dir == nil then
error "Templates folder is not defined or does not exist"
end

---@type obsidian.Path|?
local template_path
local paths_to_check = { templates_dir / tostring(template_name), Path:new(template_name) }
for _, path in ipairs(paths_to_check) do
if path:is_file() then
template_path = path
break
elseif not vim.endswith(tostring(path), ".md") then
local path_with_suffix = Path:new(tostring(path) .. ".md")
if path_with_suffix:is_file() then
template_path = path_with_suffix
break
end
end
end

if template_path == nil then
error(string.format("Template '%s' not found", template_name))
end

return template_path
end

--- Substitute variables inside the given text.
---
---@param text string
---@param client obsidian.Client
---@param note obsidian.Note
---
---@return string
M.substitute_template_variables = function(text, client, title)
M.substitute_template_variables = function(text, client, note)
local methods = vim.deepcopy(client.opts.templates.substitutions or {})

if not methods["date"] then
Expand All @@ -26,8 +63,16 @@ M.substitute_template_variables = function(text, client, title)
end
end

if title then
methods["title"] = title
if not methods["title"] then
methods["title"] = note.title or note:display_name()
end

if not methods["id"] then
methods["id"] = tostring(note.id)
end

if not methods["path"] and note.path then
methods["path"] = tostring(note.path)
end

for key, subst in pairs(methods) do
Expand All @@ -48,21 +93,16 @@ M.substitute_template_variables = function(text, client, title)
return text
end

---Clone Template
--- Clone template to a new note.
---
---@param template_name string - name of a template in the configured templates folder
---@param note_path obsidian.Path
---@param client obsidian.Client
---@param title string
M.clone_template = function(template_name, note_path, client, title)
local templates_dir = client:templates_dir()
if templates_dir == nil then
error "Templates folder is not defined or does not exist"
end

---@param opts { template_name: string|obsidian.Path, path: obsidian.Path|string, client: obsidian.Client, note: obsidian.Note } Options.
---
---@return obsidian.Note
M.clone_template = function(opts)
local note_path = Path.new(opts.path)
assert(note_path:parent()):mkdir { parents = true, exist_ok = true }

local template_path = Path:new(templates_dir) / template_name
local template_path = resolve_template(opts.template_name, opts.client)
local template_file = io.open(tostring(template_path), "r")
if not template_file then
error(string.format("Unable to read template at '%s'", template_path))
Expand All @@ -74,53 +114,32 @@ M.clone_template = function(template_name, note_path, client, title)
end

for line in template_file:lines "L" do
note_file:write(M.substitute_template_variables(line, client, title))
note_file:write(M.substitute_template_variables(line, opts.client, opts.note))
end

template_file:close()
note_file:close()

return Note.from_file(note_path)
end

---Insert a template at the given location.
---
---@param name string name or path of a template in the configured templates folder
---@param client obsidian.Client
---@param location table a tuple with {bufnr, winnr, row, col}
M.insert_template = function(name, client, location)
local templates_dir = client:templates_dir()
if templates_dir == nil then
error "Templates folder is not defined or does not exist"
end

local buf, win, row, _ = unpack(location)
local title = require("obsidian.note").from_buffer(buf):display_name()

---@type obsidian.Path
local template_path
local paths_to_check = { templates_dir / name, Path:new(name) }
for _, path in ipairs(paths_to_check) do
if path:is_file() then
template_path = path
break
elseif not vim.endswith(tostring(path), ".md") then
local path_with_suffix = Path:new(tostring(path) .. ".md")
if path_with_suffix:is_file() then
template_path = path_with_suffix
break
end
end
end
---@param opts { template_name: string|obsidian.Path, client: obsidian.Client, location: { [1]: integer, [2]: integer, [3]: integer, [4]: integer } } Options.
---
---@return obsidian.Note
M.insert_template = function(opts)
local buf, win, row, _ = unpack(opts.location)
local note = Note.from_buffer(buf)

if template_path == nil then
error(string.format("Template '%s' not found", name))
end
local template_path = resolve_template(opts.template_name, opts.client)

local insert_lines = {}
local template_file = io.open(tostring(template_path), "r")
if template_file then
local lines = template_file:lines()
for line in lines do
local new_lines = M.substitute_template_variables(line, client, title)
local new_lines = M.substitute_template_variables(line, opts.client, note)
if string.find(new_lines, "[\r\n]") then
local line_start = 1
for line_end in util.gfind(new_lines, "[\r\n]") do
Expand All @@ -145,7 +164,9 @@ M.insert_template = function(name, client, location)
local new_cursor_row, _ = unpack(vim.api.nvim_win_get_cursor(win))
vim.api.nvim_win_set_cursor(0, { new_cursor_row, 0 })

client:update_ui(0)
opts.client:update_ui(0)

return Note.from_buffer(buf)
end

return M
5 changes: 3 additions & 2 deletions test/obsidian/templates_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local obsidian = require "obsidian"
local Path = require "obsidian.path"
local Note = require "obsidian.note"
local templates = require "obsidian.templates"

---Get a client in a temporary directory.
Expand All @@ -23,7 +24,7 @@ describe("templates.substitute_template_variables()", function()
local text = "today is {{date}} and the title of the note is {{title}}"
assert.equal(
string.format("today is %s and the title of the note is %s", os.date "%Y-%m-%d", "FOO"),
templates.substitute_template_variables(text, client, "FOO")
templates.substitute_template_variables(text, client, Note.new("FOO", { "FOO" }, {}))
)
end)

Expand All @@ -35,7 +36,7 @@ describe("templates.substitute_template_variables()", function()
end,
}
local text = "today is {{weekday}}"
assert.equal("today is Monday", templates.substitute_template_variables(text, client))
assert.equal("today is Monday", templates.substitute_template_variables(text, client, Note.new("foo", {}, {})))

-- Make sure the client opts has not been modified.
assert.equal(1, vim.tbl_count(client.opts.templates.substitutions))
Expand Down

0 comments on commit 87267f9

Please sign in to comment.