Skip to content

Commit d2bf94b

Browse files
committed
feat: support per-entry aliases in journal templates
Add an :aliases key to vulpea-journal-template-daily and vulpea-journal-template-monthly. Each element is a strftime string expanded on the entry's date (or a function of the date), written to vulpea-buffer-alias-property so an entry can be linked by a short stamp instead of its heading title. Static strings pass through; multi-word aliases are quoted. Also fix the monthly heading path dropping the template :properties: it now merges them with the journal-managed CREATED property instead of writing only CREATED (same family as the earlier :body drop). The spec helpers shared with :entry-groups are generalized (--normalize-specs, --resolve-spec) and reused for :aliases. Refs #16
1 parent 557e894 commit d2bf94b

4 files changed

Lines changed: 147 additions & 21 deletions

File tree

CHANGELOG.org

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
- New =vulpea-journal-tag= customization variable for configuring the journal tag. Template builders use it as the default, and =vulpea-journal-note-p= checks against it directly. ([[https://github.com/d12frosted/vulpea-journal/pull/12][#12]])
1212
- Monthly journal granularity: one file per month with daily entries as headings. Use =vulpea-journal-template-monthly= to create a monthly template. ([[https://github.com/d12frosted/vulpea-journal/issues/2][#2]])
1313
- Monthly templates can nest daily entries under date-derived grouping headings via the new =:entry-groups= key of =vulpea-journal-template-monthly=. Each element (a strftime string such as ="week %V"= or a function of the date) adds one heading level between the file and the entry; group headings are created and reused on demand, and =:entry-level= is derived from the number of groups so lookup and creation agree. ([[https://github.com/d12frosted/vulpea-journal/issues/16][#16]])
14+
- Journal templates can attach computed or static aliases to each entry via the new =:aliases= key of =vulpea-journal-template-daily= and =vulpea-journal-template-monthly=. Each element is a strftime string expanded for the entry's date (e.g. ="%Y-%m-%d"=) or a function of the date; aliases are written to the property named by =vulpea-buffer-alias-property= so an entry can be linked by a short stamp instead of its heading title. ([[https://github.com/d12frosted/vulpea-journal/issues/16][#16]])
1415
- Template builder functions: =vulpea-journal-template-daily= and =vulpea-journal-template-monthly= with sensible defaults and override support.
1516
- Active date tracking for sidebar widgets, ensuring correct behavior when navigating between entries in the same file.
1617
- Migration script now adds journal filetag to migrated files using =vulpea-buffer-tags-add=. ([[https://github.com/d12frosted/vulpea-journal/pull/12][#12]])
1718

1819
** Fixed
1920

2021
- Fix =:body= from monthly templates being silently dropped for heading-level entries. =vulpea-journal--create-heading-note= now passes the template =:body= through to =vulpea-create=, matching the file-level (daily) path. ([[https://github.com/d12frosted/vulpea-journal/issues/16][#16]])
22+
- Fix template =:properties= being dropped for monthly (heading-level) entries. =vulpea-journal--create-heading-note= now merges the template =:properties= with the journal-managed =CREATED= property instead of writing only =CREATED=, matching the file-level (daily) path. ([[https://github.com/d12frosted/vulpea-journal/issues/16][#16]])
2123
- Fix selected day not being highlighted in the calendar widget when the optional =hl-line= library is not loaded. =vulpea-journal-ui-calendar-selected= now inherits from the always-available =highlight= face instead of =hl-line=. ([[https://github.com/d12frosted/vulpea-journal/issues/14][#14]])
2224
- Fix outdated =vulpea-db-sync= references in README — the function was renamed to =vulpea-db-sync-full-scan= in vulpea. ([[https://github.com/d12frosted/vulpea-journal/issues/11][#11]])
2325
- Fix journal entries not found after database rebuild (=vulpea-db-sync-full-scan=). Root cause: when =vulpea-db-sync-directories= contains =~= paths (e.g., =~/notes=), the sync stored them unexpanded in the database while the journal always expanded them (e.g., =/home/user/notes=), causing exact-match lookups to fail. Fixed in vulpea by expanding paths in =vulpea-db-sync--list-org-files=. Also adds =abbreviate-file-name= and =file-truename= fallbacks in =vulpea-journal-find-note= for defense-in-depth. ([[https://github.com/d12frosted/vulpea-journal/issues/5][#5]])

README.org

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ Monthly template parameters:
182182
| =:entry-level= | =1= | Heading level for daily entries (derived when =:entry-groups= is set) |
183183
| =:entry-title= | =%d %A= | strftime format for heading |
184184
| =:entry-groups= | =nil= | Optional grouping headings (see below) |
185+
| =:aliases= | =nil= | Per-entry aliases, computed or static (see below) |
185186
| =:tags= | =("journal")= | Tags (first one identifies journals) |
186187

187188
By default, daily entries are placed directly under the monthly file. To nest them under intermediate grouping headings (for example, one heading per ISO week), use =:entry-groups=:
@@ -214,6 +215,20 @@ Each element of =:entry-groups= adds one heading level between the file and the
214215

215216
Because =:body= is inserted verbatim at fixed heading levels, nest its subheadings one level below the entry (=***= for entries at level 2, as above).
216217

218+
*** Entry aliases
219+
220+
Give each entry an alias (or several) with =:aliases=, so you can link to it by a short stamp instead of its full heading title. Each element is a =strftime= string expanded for the entry's date, or a function of the date; a single spec needs no list. Works for daily and monthly templates.
221+
222+
#+begin_src emacs-lisp
223+
(setq vulpea-journal-default-template
224+
(vulpea-journal-template-monthly
225+
:entry-title "journal <%Y-%m-%d %a %H:%M>"
226+
:entry-groups '("week %V")
227+
:aliases '("%Y-%m-%d")))
228+
#+end_src
229+
230+
Every entry then carries a =2026-06-08= style alias (computed from the entry's date, not today), written to the property named by =vulpea-buffer-alias-property= (=ALIASES= by default, =ROAM_ALIASES= if you configure it). See the vulpea user guide, "Managing Aliases", for how aliases are searched and linked.
231+
217232
*** Raw template plist
218233

219234
You can also provide a raw plist directly:

test/vulpea-journal-test.el

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,68 @@ after a full scan rebuild."
517517
(should (string= (vulpea-note-id (vulpea-journal-find-note d2))
518518
(vulpea-note-id n2)))))))
519519

520+
;;; Entry Alias Tests
521+
522+
(ert-deftest vulpea-journal-template-aliases ()
523+
"Test :aliases is normalized and stored by the template builders."
524+
;; A single spec is normalized to a one-element list.
525+
(let ((tpl (vulpea-journal-template-monthly :aliases "%Y-%m-%d")))
526+
(should (equal (plist-get tpl :aliases) '("%Y-%m-%d"))))
527+
;; A list is stored as-is, and daily accepts it too.
528+
(let ((tpl (vulpea-journal-template-daily :aliases '("%Y-%m-%d" "alt"))))
529+
(should (equal (plist-get tpl :aliases) '("%Y-%m-%d" "alt")))))
530+
531+
(ert-deftest vulpea-journal-monthly-entry-aliases-computed ()
532+
"Test monthly entries get an alias computed on the entry's date."
533+
(vulpea-test--with-temp-db
534+
(let* ((vulpea-journal-default-template
535+
(vulpea-journal-template-monthly :aliases '("%Y-%m-%d")))
536+
(date (encode-time 0 0 12 8 6 2026)))
537+
(let ((note (vulpea-journal-note date)))
538+
;; Alias reflects the entry's date, not the current date.
539+
(should (member "2026-06-08" (vulpea-note-aliases note)))))))
540+
541+
(ert-deftest vulpea-journal-daily-entry-aliases-computed ()
542+
"Test daily entries get an alias computed on the entry's date."
543+
(vulpea-test--with-temp-db
544+
(let* ((vulpea-journal-default-template
545+
(vulpea-journal-template-daily :aliases '("%Y-%m-%d")))
546+
(date (encode-time 0 0 12 8 6 2026)))
547+
(let ((note (vulpea-journal-note date)))
548+
(should (member "2026-06-08" (vulpea-note-aliases note)))))))
549+
550+
(ert-deftest vulpea-journal-entry-aliases-static-multiword ()
551+
"Test a static multi-word alias round-trips as a single alias."
552+
(vulpea-test--with-temp-db
553+
(let* ((vulpea-journal-default-template
554+
(vulpea-journal-template-daily :aliases '("Daily Log")))
555+
(date (encode-time 0 0 12 8 6 2026)))
556+
(let ((note (vulpea-journal-note date)))
557+
(should (member "Daily Log" (vulpea-note-aliases note)))))))
558+
559+
(ert-deftest vulpea-journal-entry-aliases-custom-property ()
560+
"Test aliases honor a custom `vulpea-buffer-alias-property'."
561+
(vulpea-test--with-temp-db
562+
(let* ((vulpea-buffer-alias-property "ROAM_ALIASES")
563+
(vulpea-journal-default-template
564+
(vulpea-journal-template-daily :aliases '("%Y-%m-%d")))
565+
(date (encode-time 0 0 12 8 6 2026)))
566+
(let ((note (vulpea-journal-note date)))
567+
(should (member "2026-06-08" (vulpea-note-aliases note)))
568+
(should (assoc "ROAM_ALIASES" (vulpea-note-properties note)))))))
569+
570+
(ert-deftest vulpea-journal-monthly-entry-properties ()
571+
"Test monthly entries honor template :properties alongside CREATED."
572+
(vulpea-test--with-temp-db
573+
(let* ((vulpea-journal-default-template
574+
(vulpea-journal-template-monthly
575+
:properties '(("FOO" . "bar"))))
576+
(date (encode-time 0 0 12 8 6 2026)))
577+
(let ((note (vulpea-journal-note date)))
578+
(should (equal (cdr (assoc "FOO" (vulpea-note-properties note))) "bar"))
579+
;; CREATED is still written by the journal.
580+
(should (assoc "CREATED" (vulpea-note-properties note)))))))
581+
520582
(ert-deftest vulpea-journal-monthly-find-note ()
521583
"Test finding a monthly journal note by date."
522584
(vulpea-test--with-temp-db

vulpea-journal.el

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -137,22 +137,22 @@ Or as a function for dynamic configuration:
137137

138138
;;; Template Builders
139139

140-
(defun vulpea-journal--normalize-groups (groups)
141-
"Normalize GROUPS into a list of group specs.
142-
GROUPS may be nil, a single spec (a strftime string or a function
140+
(defun vulpea-journal--normalize-specs (specs)
141+
"Normalize SPECS into a list of template specs.
142+
SPECS may be nil, a single spec (a strftime string or a function
143143
of one argument DATE), or a list of such specs. A single spec is
144-
wrapped in a one-element list."
144+
wrapped in a one-element list. Shared by :entry-groups and :aliases."
145145
(cond
146-
((null groups) nil)
147-
((or (stringp groups) (functionp groups)) (list groups))
148-
((listp groups) groups)
149-
(t (error "Invalid :entry-groups value: %S" groups))))
146+
((null specs) nil)
147+
((or (stringp specs) (functionp specs)) (list specs))
148+
((listp specs) specs)
149+
(t (error "Invalid template spec list: %S" specs))))
150150

151151
(cl-defun vulpea-journal-template-daily (&key
152152
(file-name "journal/%Y-%m-%d.org")
153153
(title "%Y-%m-%d %A")
154154
(tags (list vulpea-journal-tag))
155-
head body properties meta context)
155+
aliases head body properties meta context)
156156
"Create a daily journal template (one file per day).
157157
158158
Returns a plist suitable for `vulpea-journal-default-template'.
@@ -161,9 +161,16 @@ All parameters are optional with sensible defaults:
161161
- FILE-NAME: strftime format for file path (default: journal/%Y-%m-%d.org)
162162
- TITLE: strftime format for note title (default: %Y-%m-%d %A)
163163
- TAGS: list of tags (default: (\"journal\"))
164+
- ALIASES: optional note aliases, computed per entry. Each element is
165+
a strftime string expanded for the entry's date (e.g. \"%Y-%m-%d\")
166+
or a function of one argument (DATE) returning the alias; a single
167+
spec may be given without a list. Written to the property named by
168+
`vulpea-buffer-alias-property' so the entry can be linked by a short
169+
stamp instead of its title.
164170
- HEAD, BODY, PROPERTIES, META, CONTEXT: passed to `vulpea-create'"
165171
(append
166172
(list :file-name file-name :title title :tags tags)
173+
(when aliases (list :aliases (vulpea-journal--normalize-specs aliases)))
167174
(when head (list :head head))
168175
(when body (list :body body))
169176
(when properties (list :properties properties))
@@ -177,7 +184,7 @@ All parameters are optional with sensible defaults:
177184
(entry-level 1)
178185
(entry-title "%d %A")
179186
entry-groups
180-
head body properties meta context)
187+
aliases head body properties meta context)
181188
"Create a monthly journal template (one file per month).
182189
183190
Daily entries are created as headings inside the monthly file.
@@ -200,13 +207,20 @@ All parameters are optional with sensible defaults:
200207
1 + number of groups so lookup and creation agree. For example,
201208
:entry-groups \\='(\"week %V\") nests each day under a \"week NN\"
202209
heading and entries live at level 2.
210+
- ALIASES: optional note aliases, computed per entry. Each element is
211+
a strftime string expanded for the entry's date (e.g. \"%Y-%m-%d\")
212+
or a function of one argument (DATE) returning the alias; a single
213+
spec may be given without a list. Written to the property named by
214+
`vulpea-buffer-alias-property' so the entry can be linked by a short
215+
stamp instead of its title.
203216
- HEAD, BODY, PROPERTIES, META, CONTEXT: passed to `vulpea-create'"
204-
(let* ((groups (vulpea-journal--normalize-groups entry-groups))
217+
(let* ((groups (vulpea-journal--normalize-specs entry-groups))
205218
(entry-level (if groups (1+ (length groups)) entry-level)))
206219
(append
207220
(list :file-name file-name :title title :tags tags
208221
:entry-level entry-level :entry-title entry-title)
209222
(when groups (list :entry-groups groups))
223+
(when aliases (list :aliases (vulpea-journal--normalize-specs aliases)))
210224
(when head (list :head head))
211225
(when body (list :body body))
212226
(when properties (list :properties properties))
@@ -426,19 +440,51 @@ an ID property and running `vulpea-db-sync-full-scan'" file))
426440
:tags (plist-get tpl :tags)
427441
:head (plist-get tpl :head)
428442
:body (plist-get tpl :body)
429-
:properties (plist-get tpl :properties)
443+
:properties (vulpea-journal--entry-properties tpl date nil)
430444
:meta (plist-get tpl :meta)
431445
:context (plist-get tpl :context))
432446
(vulpea-db-get-by-id id)))
433447

434-
(defun vulpea-journal--resolve-group-title (spec date)
435-
"Resolve group SPEC to a heading title for DATE.
436-
SPEC is either a strftime format string or a function of one
437-
argument (DATE) returning a string."
448+
(defun vulpea-journal--resolve-spec (spec date)
449+
"Resolve template SPEC for DATE to a string.
450+
SPEC is either a strftime format string (expanded for DATE) or a
451+
function of one argument (DATE) returning a string. Shared by
452+
:entry-groups and :aliases."
438453
(cond
439454
((functionp spec) (funcall spec date))
440455
((stringp spec) (format-time-string spec date))
441-
(t (error "Invalid :entry-groups spec: %S" spec))))
456+
(t (error "Invalid template spec: %S" spec))))
457+
458+
(defun vulpea-journal--resolve-aliases (tpl date)
459+
"Return the list of resolved alias strings for DATE from TPL.
460+
Each TPL :aliases spec is resolved via `vulpea-journal--resolve-spec'.
461+
Returns nil when TPL has no :aliases."
462+
(mapcar (lambda (spec) (vulpea-journal--resolve-spec spec date))
463+
(vulpea-journal--normalize-specs (plist-get tpl :aliases))))
464+
465+
(defun vulpea-journal--alias-property (aliases)
466+
"Return a one-element alist setting the alias property for ALIASES.
467+
The property name is `vulpea-buffer-alias-property'. An alias that
468+
contains whitespace or a double quote is wrapped in double quotes,
469+
matching how vulpea stores and parses aliases. Returns nil when
470+
ALIASES is empty."
471+
(when aliases
472+
(list (cons vulpea-buffer-alias-property
473+
(mapconcat
474+
(lambda (alias)
475+
(if (string-match-p "[ \t\"]" alias)
476+
(format "%S" alias)
477+
alias))
478+
aliases " ")))))
479+
480+
(defun vulpea-journal--entry-properties (tpl date base)
481+
"Build the property alist for a journal entry on DATE.
482+
BASE is an alist of journal-managed properties (e.g. CREATED).
483+
Appends TPL :properties and the resolved :aliases property."
484+
(append base
485+
(plist-get tpl :properties)
486+
(vulpea-journal--alias-property
487+
(vulpea-journal--resolve-aliases tpl date))))
442488

443489
(defun vulpea-journal--find-child-heading (file level title outline-path)
444490
"Find heading note in FILE at LEVEL titled TITLE under OUTLINE-PATH.
@@ -452,15 +498,15 @@ first. Returns the matching `vulpea-note' or nil."
452498
(defun vulpea-journal--ensure-group-path (container date groups)
453499
"Ensure the chain of GROUPS headings under CONTAINER for DATE.
454500
CONTAINER is the file-level container `vulpea-note'. GROUPS is a
455-
list of group specs (see `vulpea-journal--resolve-group-title').
501+
list of group specs (see `vulpea-journal--resolve-spec').
456502
Missing group headings are created on demand and existing ones are
457503
reused. Returns the deepest container note that should parent the
458504
entry, which is CONTAINER itself when GROUPS is empty."
459505
(let ((parent container)
460506
(file (vulpea-note-path container))
461507
(ancestry nil))
462-
(dolist (spec (vulpea-journal--normalize-groups groups) parent)
463-
(let* ((title (vulpea-journal--resolve-group-title spec date))
508+
(dolist (spec (vulpea-journal--normalize-specs groups) parent)
509+
(let* ((title (vulpea-journal--resolve-spec spec date))
464510
(level (1+ (vulpea-note-level parent)))
465511
(existing (vulpea-journal--find-child-heading
466512
file level title (reverse ancestry))))
@@ -492,7 +538,8 @@ chain of date-derived grouping headings (created on demand)."
492538
nil
493539
:parent parent
494540
:body (plist-get tpl :body)
495-
:properties `(("CREATED" . ,date-str))
541+
:properties (vulpea-journal--entry-properties
542+
tpl date `(("CREATED" . ,date-str)))
496543
:after 'last)))
497544

498545
(defun vulpea-journal--ensure-container (file date tpl)

0 commit comments

Comments
 (0)