Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(templates): provide note to custom variable substitution functions #709

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Added `opts.follow_img_func` option for customizing how to handle image paths.
- Added better handling for undefined template fields, which will now be prompted for.
- Added the related `Note` to the custom variable substitution functions.

### Changed

Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,20 +712,34 @@ Date created: 2023-03-01-Wed

above the cursor position.

You can also define custom template substitutions with the configuration field `templates.substitutions`. For example, to automatically substitute the template variable `{{yesterday}}` when inserting a template, you could add this to your config:
You can also define custom template substitutions with the configuration field `templates.substitutions`.

For example, to automatically substitute the template variable `{{watermark}}`, `{{yesterday}}`, `{{normalized_title}}`
when inserting a template, you could add this to your config:

```lua
{
-- other fields ...
templates = {
substitutions = {
watermark = "Obsidian.nvim",
yesterday = function()
return os.date("%Y-%m-%d", os.time() - 86400)
end
end,
---Format `id` field of `Note` to a more human-readable string.
---For an `id` "17823674-My-note-title", it will returns "My note title"
---@param note obsidian.Note
normalized_title = function(note)
return note.id:gsub("%d+-?", ""):gsub("-", " ")
end,
}
}
```

> [!NOTE]
> You could set substitution keys to `string` or `function`.
> The `function` receives the related `Note` as the first parameter.

### Usage outside of a workspace or vault

It's possible to configure obsidian.nvim to work on individual markdown files outside of a regular workspace / Obsidian vault by configuring a "dynamic" workspace. To do so you just need to add a special workspace with a function for the `path` field (instead of a string), which should return a *parent* directory of the current buffer. This tells obsidian.nvim to use that directory as the workspace `path` and `root` (vault root) when the buffer is not located inside another fixed workspace.
Expand Down
2 changes: 1 addition & 1 deletion lua/obsidian/templates.lua
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ M.substitute_template_variables = function(text, client, note)
if type(subst) == "string" then
value = subst
else
value = subst()
value = subst(note)
-- cache the result
methods[key] = value
end
Expand Down
59 changes: 45 additions & 14 deletions test/obsidian/templates_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,58 @@ local tmp_client = function()
end

describe("templates.substitute_template_variables()", function()
---@type obsidian.Client
local client

before_each(function()
client = tmp_client()
end)

it("should substitute built-in variables", function()
local client = tmp_client()
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, Note.new("FOO", { "FOO" }, {}))
)
end)

it("should substitute custom variables", function()
local client = tmp_client()
client.opts.templates.substitutions = {
weekday = function()
return "Monday"
end,
}
local text = "today is {{weekday}}"
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))
assert.equal("function", type(client.opts.templates.substitutions.weekday))
describe("when substituting custom variable", function()
it("should substitute using a string", function()
client.opts.templates.substitutions = {
weekday = "Monday",
}
local text = "today is {{weekday}}"
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))
assert.equal("string", type(client.opts.templates.substitutions.weekday))
end)

it("should substitute using a function", function()
client.opts.templates.substitutions = {
weekday = function()
return "Monday"
end,
}
local text = "today is {{weekday}}"
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))
assert.equal("function", type(client.opts.templates.substitutions.weekday))
end)

it("should substitute using values from the note", function()
client.opts.templates.substitutions = {
---@param note obsidian.Note
id_uppercase = function(note)
return string.upper(note.id)
end,
}

local text = "formatted id: {{id_uppercase}}"
assert.equal("formatted id: FOO", templates.substitute_template_variables(text, client, Note.new("foo", {}, {})))
end)
end)
end)