mirror of
https://github.com/org-roam/org-roam
synced 2025-08-01 12:17:21 -05:00
@ -2,6 +2,9 @@
|
||||
|
||||
## 0.1.3 (TBD)
|
||||
|
||||
### New Features
|
||||
* [#182][gh-182] Support file name aliases via `#+ROAM_ALIAS`.
|
||||
|
||||
### Features
|
||||
* [#165][gh-165] Add templating functionality via `org-roam-templates`.
|
||||
|
||||
@ -93,6 +96,7 @@ Mostly a documentation/cleanup release.
|
||||
[gh-142]: https://github.com/jethrokuan/org-roam/pull/142
|
||||
[gh-143]: https://github.com/jethrokuan/org-roam/pull/143
|
||||
[gh-165]: https://github.com/jethrokuan/org-roam/pull/165
|
||||
[gh-182]: https://github.com/jethrokuan/org-roam/pull/182
|
||||
|
||||
# Local Variables:
|
||||
# eval: (auto-fill-mode -1)
|
||||
|
@ -35,6 +35,7 @@
|
||||
|
||||
(require 'org)
|
||||
(require 'org-element)
|
||||
(require 'ob-core) ;for org-babel-parse-header-arguments
|
||||
(require 'subr-x)
|
||||
(require 'cl-lib)
|
||||
|
||||
@ -127,15 +128,56 @@ ITEM is of the form: (:from from-path :to to-path :properties (:content preview-
|
||||
(puthash p-from (list props) contents-hash)
|
||||
(puthash p-to contents-hash backward))))))
|
||||
|
||||
(defun org-roam--extract-title ()
|
||||
"Extract the title from `BUFFER'."
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'keyword
|
||||
(lambda (kw)
|
||||
(when (string= (org-element-property :key kw) "TITLE")
|
||||
(org-element-property :value kw)))
|
||||
:first-match t))
|
||||
(defun org-roam--extract-global-props (props)
|
||||
"Extract PROPS from the current buffer."
|
||||
(let ((buf (org-element-parse-buffer))
|
||||
(res '()))
|
||||
(dolist (prop props)
|
||||
(let ((p (org-element-map
|
||||
buf
|
||||
'keyword
|
||||
(lambda (kw)
|
||||
(when (string= (org-element-property :key kw) prop)
|
||||
(org-element-property :value kw)))
|
||||
:first-match t)))
|
||||
(setq res (cons (cons prop p) res))))
|
||||
res))
|
||||
|
||||
(defun org-roam--aliases-str-to-list (str)
|
||||
"Function to transform string STR into list of alias titles.
|
||||
|
||||
This snippet is obtained from ox-hugo:
|
||||
https://github.com/kaushalmodi/ox-hugo/blob/a80b250987bc770600c424a10b3bca6ff7282e3c/ox-hugo.el#L3131"
|
||||
(when (stringp str)
|
||||
(let* ((str (org-trim str))
|
||||
(str-list (split-string str "\n"))
|
||||
ret)
|
||||
(dolist (str-elem str-list)
|
||||
(let* ((format-str ":dummy '(%s)") ;The :dummy key is discarded in the `lst' var below.
|
||||
(alist (org-babel-parse-header-arguments (format format-str str-elem)))
|
||||
(lst (cdr (car alist)))
|
||||
(str-list2 (mapcar (lambda (elem)
|
||||
(cond
|
||||
((symbolp elem)
|
||||
(symbol-name elem))
|
||||
(t
|
||||
elem)))
|
||||
lst)))
|
||||
(setq ret (append ret str-list2))))
|
||||
ret)))
|
||||
|
||||
(defun org-roam--extract-titles ()
|
||||
"Extract the titles from current buffer.
|
||||
|
||||
Titles are obtained via the #+TITLE property, or aliases
|
||||
specified via the #+ROAM_ALIAS property."
|
||||
(let* ((props (org-roam--extract-global-props '("TITLE" "ROAM_ALIAS")))
|
||||
(aliases (cdr (assoc "ROAM_ALIAS" props)))
|
||||
(title (cdr (assoc "TITLE" props)))
|
||||
(alias-list (org-roam--aliases-str-to-list aliases)))
|
||||
(if title
|
||||
(cons title alias-list)
|
||||
alias-list)))
|
||||
|
||||
(defun org-roam--build-cache (dir)
|
||||
"Build the org-roam caches in DIR."
|
||||
@ -156,8 +198,8 @@ ITEM is of the form: (:from from-path :to to-path :properties (:content preview-
|
||||
(dolist (file org-roam-files)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(when-let ((title (org-roam--extract-title)))
|
||||
(puthash file title file-titles)))
|
||||
(when-let ((titles (org-roam--extract-titles)))
|
||||
(puthash file titles file-titles)))
|
||||
org-roam-files))
|
||||
(list
|
||||
:directory dir
|
||||
|
59
org-roam.el
59
org-roam.el
@ -243,14 +243,18 @@ as a unique key."
|
||||
(f-descendant-of-p (file-truename path)
|
||||
(file-truename org-roam-directory)))))
|
||||
|
||||
(defun org-roam--get-title-from-cache (file)
|
||||
"Return title of `FILE' from the cache."
|
||||
(defun org-roam--get-titles-from-cache (file)
|
||||
"Return titles and aliases of `FILE' from the cache."
|
||||
(or (gethash file (org-roam--titles-cache))
|
||||
(progn
|
||||
(unless (org-roam--cache-initialized-p)
|
||||
(user-error "The Org-Roam caches aren't built! Please run org-roam--build-cache-async"))
|
||||
nil)))
|
||||
|
||||
(defun org-roam--get-title-from-cache (file)
|
||||
"Return the title of `FILE' from the cache."
|
||||
(car (org-roam--get-titles-from-cache file)))
|
||||
|
||||
(defun org-roam--find-all-files ()
|
||||
"Return all org-roam files."
|
||||
(org-roam--find-files (file-truename org-roam-directory)))
|
||||
@ -271,12 +275,17 @@ If `ABSOLUTE', return an absolute file-path. Else, return a relative file-path."
|
||||
(file-relative-name absolute-file-path
|
||||
(file-truename org-roam-directory)))))
|
||||
|
||||
(defun org-roam--get-title-or-slug (file-path)
|
||||
"Convert `FILE-PATH' to the file title, if it exists. Else, return the path."
|
||||
(or (org-roam--get-title-from-cache file-path)
|
||||
(-> file-path
|
||||
(file-relative-name (file-truename org-roam-directory))
|
||||
(file-name-sans-extension))))
|
||||
(defun org-roam--path-to-slug (path)
|
||||
"Return a slug from PATH."
|
||||
(-> path
|
||||
(file-relative-name (file-truename org-roam-directory))
|
||||
(file-name-sans-extension)))
|
||||
|
||||
(defun org-roam--get-title-or-slug (path)
|
||||
"Convert `PATH' to the file title, if it exists. Else, return the path."
|
||||
(if-let (titles (org-roam--get-titles-from-cache path))
|
||||
(car titles)
|
||||
(org-roam--path-to-slug path)))
|
||||
|
||||
(defun org-roam--title-to-slug (title)
|
||||
"Convert TITLE to a filename-suitable slug."
|
||||
@ -352,13 +361,10 @@ If PREFIX, downcase the title before insertion."
|
||||
(region-text (when region
|
||||
(buffer-substring-no-properties
|
||||
(car region) (cdr region))))
|
||||
(completions (mapcar (lambda (file)
|
||||
(list (org-roam--get-title-or-slug file)
|
||||
file))
|
||||
(org-roam--find-all-files)))
|
||||
(completions (org-roam--get-title-path-completions))
|
||||
(title (completing-read "File: " completions nil nil region-text))
|
||||
(region-or-title (or region-text title))
|
||||
(absolute-file-path (or (cadr (assoc title completions))
|
||||
(absolute-file-path (or (cdr (assoc title completions))
|
||||
(org-roam--make-new-file title)))
|
||||
(current-file-path (-> (or (buffer-base-buffer)
|
||||
(current-buffer))
|
||||
@ -376,14 +382,23 @@ If PREFIX, downcase the title before insertion."
|
||||
region-or-title))))))
|
||||
|
||||
;;; Finding org-roam files
|
||||
(defun org-roam--get-title-path-completions ()
|
||||
"Return a list of cons pairs for titles to absolute path of Org-roam files."
|
||||
(let ((files (org-roam--find-all-files))
|
||||
(res '()))
|
||||
(dolist (file files)
|
||||
(if-let (titles (org-roam--get-titles-from-cache file))
|
||||
(dolist (title titles)
|
||||
(setq res (cons (cons title file) res)))
|
||||
(setq res (cons (cons (org-roam--path-to-slug file) file) res))))
|
||||
res))
|
||||
|
||||
(defun org-roam-find-file ()
|
||||
"Find and open an org-roam file."
|
||||
(interactive)
|
||||
(let* ((completions (mapcar (lambda (file)
|
||||
(list (org-roam--get-title-or-slug file) file))
|
||||
(org-roam--find-all-files)))
|
||||
(let* ((completions (org-roam--get-title-path-completions))
|
||||
(title-or-slug (completing-read "File: " completions))
|
||||
(absolute-file-path (or (cadr (assoc title-or-slug completions))
|
||||
(absolute-file-path (or (cdr (assoc title-or-slug completions))
|
||||
(org-roam--make-new-file title-or-slug))))
|
||||
(find-file absolute-file-path)))
|
||||
|
||||
@ -399,7 +414,7 @@ If PREFIX, downcase the title before insertion."
|
||||
(interactive)
|
||||
(let* ((roam-buffers (org-roam--get-roam-buffers))
|
||||
(names-and-buffers (mapcar (lambda (buffer)
|
||||
(cons (or (org-roam--get-title-from-cache
|
||||
(cons (or (org-roam--get-title-or-slug
|
||||
(buffer-file-name buffer))
|
||||
(buffer-name buffer))
|
||||
buffer))
|
||||
@ -473,11 +488,11 @@ This is equivalent to removing the node from the graph."
|
||||
;; Step 2: Remove from the title cache
|
||||
(remhash file (org-roam--titles-cache))))
|
||||
|
||||
(defun org-roam--update-cache-title ()
|
||||
(defun org-roam--update-cache-titles ()
|
||||
"Insert the title of the current buffer into the cache."
|
||||
(when-let ((title (org-roam--extract-title)))
|
||||
(when-let ((titles (org-roam--extract-titles)))
|
||||
(puthash (file-truename (buffer-file-name (current-buffer)))
|
||||
title
|
||||
titles
|
||||
(org-roam--titles-cache))))
|
||||
|
||||
(defun org-roam--update-cache ()
|
||||
@ -485,7 +500,7 @@ This is equivalent to removing the node from the graph."
|
||||
(save-excursion
|
||||
(org-roam--clear-file-from-cache)
|
||||
;; Insert into title cache
|
||||
(org-roam--update-cache-title)
|
||||
(org-roam--update-cache-titles)
|
||||
;; Insert new items
|
||||
(let ((items (org-roam--parse-content)))
|
||||
(dolist (item items)
|
||||
|
2
tests/roam-files/alias.org
Normal file
2
tests/roam-files/alias.org
Normal file
@ -0,0 +1,2 @@
|
||||
#+ROAM_ALIAS: "a1" "a 2"
|
||||
#+TITLE: t1
|
@ -76,67 +76,6 @@
|
||||
|
||||
;;; Tests
|
||||
(describe "org-roam--build-cache-async"
|
||||
(it "initializes correctly"
|
||||
(org-roam--test-init)
|
||||
(expect (org-roam--cache-initialized-p) :to-be nil)
|
||||
(expect (hash-table-count (org-roam--forward-links-cache)) :to-be 0)
|
||||
(expect (hash-table-count (org-roam--backward-links-cache)) :to-be 0)
|
||||
(expect (hash-table-count (org-roam--titles-cache)) :to-be 0)
|
||||
|
||||
(org-roam--build-cache-async)
|
||||
(sleep-for 3) ;; Because it's async
|
||||
|
||||
;; Caches should be populated
|
||||
(expect (org-roam--cache-initialized-p) :to-be t)
|
||||
(expect (hash-table-count (org-roam--forward-links-cache)) :to-be 4)
|
||||
(expect (hash-table-count (org-roam--backward-links-cache)) :to-be 5)
|
||||
(expect (hash-table-count (org-roam--titles-cache)) :to-be 5)
|
||||
|
||||
;; Forward cache
|
||||
(let ((f1 (gethash (abs-path "f1.org") (org-roam--forward-links-cache)))
|
||||
(f2 (gethash (abs-path "f2.org") (org-roam--forward-links-cache)))
|
||||
(nested-f1 (gethash (abs-path "nested/f1.org")
|
||||
(org-roam--forward-links-cache)))
|
||||
(nested-f2 (gethash (abs-path "nested/f2.org")
|
||||
(org-roam--forward-links-cache)))
|
||||
(expected-f1 (list (abs-path "nested/f1.org")
|
||||
(abs-path "f2.org")))
|
||||
(expected-nested-f1 (list (abs-path "nested/f2.org")
|
||||
(abs-path "f1.org")))
|
||||
(expected-nested-f2 (list (abs-path "nested/f1.org"))))
|
||||
|
||||
(expect f1 :to-have-same-items-as expected-f1)
|
||||
(expect f2 :to-be nil)
|
||||
(expect nested-f1 :to-have-same-items-as expected-nested-f1)
|
||||
(expect nested-f2 :to-have-same-items-as expected-nested-f2))
|
||||
|
||||
;; Backward cache
|
||||
(let ((f1 (hash-table-keys (gethash (abs-path "f1.org")
|
||||
(org-roam--backward-links-cache))))
|
||||
(f2 (hash-table-keys (gethash (abs-path "f2.org")
|
||||
(org-roam--backward-links-cache))))
|
||||
(nested-f1 (hash-table-keys(gethash (abs-path "nested/f1.org")
|
||||
(org-roam--backward-links-cache))))
|
||||
(nested-f2 (hash-table-keys (gethash (abs-path "nested/f2.org")
|
||||
(org-roam--backward-links-cache))))
|
||||
(expected-f1 (list (abs-path "nested/f1.org")))
|
||||
(expected-f2 (list (abs-path "f1.org")))
|
||||
(expected-nested-f1 (list (abs-path "nested/f2.org")
|
||||
(abs-path "f1.org")))
|
||||
(expected-nested-f2 (list (abs-path "nested/f1.org"))))
|
||||
(expect f1 :to-have-same-items-as expected-f1)
|
||||
(expect f2 :to-have-same-items-as expected-f2)
|
||||
(expect nested-f1 :to-have-same-items-as expected-nested-f1)
|
||||
(expect nested-f2 :to-have-same-items-as expected-nested-f2))
|
||||
|
||||
;; Titles Cache
|
||||
(expect (gethash (abs-path "f1.org") (org-roam--titles-cache)) :to-equal "File 1")
|
||||
(expect (gethash (abs-path "f2.org") (org-roam--titles-cache)) :to-equal "File 2")
|
||||
(expect (gethash (abs-path "nested/f1.org") (org-roam--titles-cache)) :to-equal "Nested File 1")
|
||||
(expect (gethash (abs-path "nested/f2.org") (org-roam--titles-cache)) :to-equal "Nested File 2")
|
||||
(expect (gethash (abs-path "no-title.org") (org-roam--titles-cache)) :to-be nil)))
|
||||
|
||||
(describe "org-roam--build-cache-async-multi"
|
||||
(it "initializes correctly"
|
||||
(org-roam--clear-cache)
|
||||
(org-roam--test-multi-init)
|
||||
@ -152,7 +91,7 @@
|
||||
(expect (org-roam--cache-initialized-p) :to-be t)
|
||||
(expect (hash-table-count (org-roam--forward-links-cache)) :to-be 4)
|
||||
(expect (hash-table-count (org-roam--backward-links-cache)) :to-be 5)
|
||||
(expect (hash-table-count (org-roam--titles-cache)) :to-be 5)
|
||||
(expect (hash-table-count (org-roam--titles-cache)) :to-be 6)
|
||||
|
||||
;; Forward cache
|
||||
(let ((f1 (gethash (abs-path "f1.org")
|
||||
@ -197,13 +136,15 @@
|
||||
|
||||
;; Titles Cache
|
||||
(expect (gethash (abs-path "f1.org")
|
||||
(org-roam--titles-cache)) :to-equal "File 1")
|
||||
(org-roam--titles-cache)) :to-equal (list "File 1"))
|
||||
(expect (gethash (abs-path "f2.org")
|
||||
(org-roam--titles-cache)) :to-equal "File 2")
|
||||
(org-roam--titles-cache)) :to-equal (list "File 2"))
|
||||
(expect (gethash (abs-path "nested/f1.org")
|
||||
(org-roam--titles-cache)) :to-equal "Nested File 1")
|
||||
(org-roam--titles-cache)) :to-equal (list "Nested File 1"))
|
||||
(expect (gethash (abs-path "nested/f2.org")
|
||||
(org-roam--titles-cache)) :to-equal "Nested File 2")
|
||||
(org-roam--titles-cache)) :to-equal (list "Nested File 2"))
|
||||
(expect (gethash (abs-path "alias.org")
|
||||
(org-roam--titles-cache)) :to-equal (list "t1" "a1" "a 2"))
|
||||
(expect (gethash (abs-path "no-title.org")
|
||||
(org-roam--titles-cache)) :to-be nil)
|
||||
|
||||
@ -264,16 +205,16 @@
|
||||
;; Titles Cache
|
||||
(expect (gethash (abs-path "mf1.org")
|
||||
(org-roam--titles-cache))
|
||||
:to-equal "Multi-File 1")
|
||||
:to-equal (list "Multi-File 1"))
|
||||
(expect (gethash (abs-path "mf2.org")
|
||||
(org-roam--titles-cache))
|
||||
:to-equal "Multi-File 2")
|
||||
:to-equal (list "Multi-File 2"))
|
||||
(expect (gethash (abs-path "nested/mf1.org")
|
||||
(org-roam--titles-cache))
|
||||
:to-equal "Nested Multi-File 1")
|
||||
:to-equal (list "Nested Multi-File 1"))
|
||||
(expect (gethash (abs-path "nested/mf2.org")
|
||||
(org-roam--titles-cache))
|
||||
:to-equal "Nested Multi-File 2")
|
||||
:to-equal (list "Nested Multi-File 2"))
|
||||
(expect (gethash (abs-path "no-title.org")
|
||||
(org-roam--titles-cache))
|
||||
:to-be nil))))
|
||||
@ -326,7 +267,7 @@
|
||||
(rename-file (abs-path "f1.org")
|
||||
(abs-path "new_f1.org"))
|
||||
;; Cache should be cleared of old file
|
||||
(expect (gethash (abs-path "f1.org") (org-roam--forward-links-cache)) :to-be nil)
|
||||
(expect (gethash (abs-path "f1.org") (org-roam--forward-links-cache)) :to-be nil)
|
||||
(expect (->> (org-roam--backward-links-cache)
|
||||
(gethash (abs-path "nested/f1.org"))
|
||||
(hash-table-keys)
|
||||
@ -338,12 +279,12 @@
|
||||
(expect (->> (org-roam--forward-links-cache)
|
||||
(gethash (abs-path "new_f1.org"))
|
||||
(member (abs-path "nested/f1.org"))) :not :to-be nil)
|
||||
|
||||
;; Links are updated
|
||||
(expect (with-temp-buffer
|
||||
(insert-file-contents (abs-path "nested/f1.org"))
|
||||
(buffer-string)) :to-match (regexp-quote "[[file:../new_f1.org][File 1]]")))
|
||||
|
||||
|
||||
(it "f1 -> f1 with spaces"
|
||||
(rename-file (abs-path "f1.org")
|
||||
(abs-path "f1 with spaces.org"))
|
||||
|
Reference in New Issue
Block a user