(feature): support file aliases (#182)

Closes #91
This commit is contained in:
Jethro Kuan
2020-02-26 00:11:38 +08:00
committed by GitHub
parent 4b38b07c41
commit 19f16e9c64
5 changed files with 109 additions and 105 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,2 @@
#+ROAM_ALIAS: "a1" "a 2"
#+TITLE: t1

View File

@ -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"))