mirror of
https://github.com/org-roam/org-roam
synced 2025-08-01 12:17:21 -05:00
(feat): Allow one file to have multiple roam_key statements (#1215)
This commit is contained in:
@ -5,6 +5,7 @@
|
||||
### Features
|
||||
|
||||
- [#1183](https://github.com/org-roam/org-roam/pull/1183) add interactive functions for managing aliases and tags in Org-roam file, namely `org-roam-alias-add`, `org-roam-alias-delete`, `org-roam-tag-add`, and `org-roam-tag-delete`.
|
||||
- [#1215](https://github.com/org-roam/org-roam/pull/1215) Multiple `ROAM_KEY` keywords can now be specified in one file. This allows bibliographical entries to share the same note file.
|
||||
- [#1238](https://github.com/org-roam/org-roam/pull/1238) add `org-roam-prefer-id-links` variable to select linking method
|
||||
|
||||
### Bugfixes
|
||||
|
@ -47,7 +47,7 @@
|
||||
(defvar org-roam--org-link-bracket-typed-re)
|
||||
|
||||
(declare-function org-roam-db--ensure-built "org-roam-db")
|
||||
(declare-function org-roam--extract-ref "org-roam")
|
||||
(declare-function org-roam--extract-refs "org-roam")
|
||||
(declare-function org-roam--extract-titles "org-roam")
|
||||
(declare-function org-roam--get-title-or-slug "org-roam")
|
||||
(declare-function org-roam--get-backlinks "org-roam")
|
||||
@ -149,10 +149,11 @@ ORIG-PATH is the path where the CONTENT originated."
|
||||
|
||||
(defun org-roam-buffer--insert-ref-links ()
|
||||
"Insert ref backlinks for the current buffer."
|
||||
(when-let ((path (cdr (with-temp-buffer
|
||||
(insert-buffer-substring org-roam-buffer--current)
|
||||
(org-roam--extract-ref)))))
|
||||
(if-let* ((key-backlinks (org-roam--get-backlinks path))
|
||||
(when-let* ((refs (with-temp-buffer
|
||||
(insert-buffer-substring org-roam-buffer--current)
|
||||
(org-roam--extract-refs)))
|
||||
(paths (mapcar #'cdr refs)))
|
||||
(if-let* ((key-backlinks (mapcan #'org-roam--get-backlinks paths))
|
||||
(grouped-backlinks (--group-by (nth 0 it) key-backlinks)))
|
||||
(progn
|
||||
(insert (let ((l (length key-backlinks)))
|
||||
@ -163,8 +164,8 @@ ORIG-PATH is the path where the CONTENT originated."
|
||||
(bls (cdr group)))
|
||||
(insert (format "** %s\n"
|
||||
(org-roam-format-link file-from
|
||||
(org-roam--get-title-or-slug file-from)
|
||||
"file")))
|
||||
(org-roam--get-title-or-slug file-from)
|
||||
"file")))
|
||||
(dolist (backlink bls)
|
||||
(pcase-let ((`(,file-from _ ,props) backlink))
|
||||
(insert (if-let ((content (plist-get props :content)))
|
||||
|
@ -50,7 +50,7 @@
|
||||
|
||||
(declare-function org-roam--org-roam-file-p "org-roam")
|
||||
(declare-function org-roam--extract-titles "org-roam")
|
||||
(declare-function org-roam--extract-ref "org-roam")
|
||||
(declare-function org-roam--extract-refs "org-roam")
|
||||
(declare-function org-roam--extract-tags "org-roam")
|
||||
(declare-function org-roam--extract-ids "org-roam")
|
||||
(declare-function org-roam--extract-links "org-roam")
|
||||
@ -274,34 +274,35 @@ Returns the number of rows inserted."
|
||||
rows)
|
||||
(length rows)))
|
||||
|
||||
(defun org-roam-db--insert-ref (&optional update-p)
|
||||
"Update the ref of the current buffer into the cache.
|
||||
(defun org-roam-db--insert-refs (&optional update-p)
|
||||
"Update the refs of the current buffer into the cache.
|
||||
If UPDATE-P is non-nil, first remove the ref for the file in the database."
|
||||
(let ((file (or org-roam-file-name (buffer-file-name))))
|
||||
(let ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(count 0))
|
||||
(when update-p
|
||||
(org-roam-db-query [:delete :from refs
|
||||
:where (= file $s1)]
|
||||
file))
|
||||
(if-let ((ref (org-roam--extract-ref)))
|
||||
(let ((key (cdr ref))
|
||||
(type (car ref)))
|
||||
(condition-case nil
|
||||
(progn
|
||||
(org-roam-db-query
|
||||
[:insert :into refs :values $v1]
|
||||
(list (vector key file type)))
|
||||
1)
|
||||
(error
|
||||
(lwarn '(org-roam) :error
|
||||
(format "Duplicate ref %s in:\n\nA: %s\nB: %s\n\nskipping..."
|
||||
key
|
||||
file
|
||||
(caar (org-roam-db-query
|
||||
[:select file :from refs
|
||||
:where (= ref $v1)]
|
||||
(vector key)))))
|
||||
0)))
|
||||
0)))
|
||||
(when-let ((refs (org-roam--extract-refs)))
|
||||
(dolist (ref refs)
|
||||
(let ((key (cdr ref))
|
||||
(type (car ref)))
|
||||
(condition-case nil
|
||||
(progn
|
||||
(org-roam-db-query
|
||||
[:insert :into refs :values $v1]
|
||||
(list (vector key file type)))
|
||||
(cl-incf count))
|
||||
(error
|
||||
(lwarn '(org-roam) :error
|
||||
(format "Duplicate ref %s in:\n\nA: %s\nB: %s\n\nskipping..."
|
||||
key
|
||||
file
|
||||
(caar (org-roam-db-query
|
||||
[:select file :from refs
|
||||
:where (= ref $v1)]
|
||||
(vector key))))))))))
|
||||
count))
|
||||
|
||||
(defun org-roam-db--insert-links (&optional update-p)
|
||||
"Update the file links of the current buffer in the cache.
|
||||
@ -473,7 +474,7 @@ If the file exists, update the cache with information."
|
||||
(org-roam-db--insert-meta 'update)
|
||||
(org-roam-db--insert-tags 'update)
|
||||
(org-roam-db--insert-titles 'update)
|
||||
(org-roam-db--insert-ref 'update)
|
||||
(org-roam-db--insert-refs 'update)
|
||||
(when org-roam-enable-headline-linking
|
||||
(org-roam-db--insert-ids 'update))
|
||||
(org-roam-db--insert-links 'update)))))
|
||||
@ -538,7 +539,7 @@ If FORCE, force a rebuild of the cache from scratch."
|
||||
(setq link-count (+ link-count (org-roam-db--insert-links)))
|
||||
(setq tag-count (+ tag-count (org-roam-db--insert-tags)))
|
||||
(setq title-count (+ title-count (org-roam-db--insert-titles)))
|
||||
(setq ref-count (+ ref-count (org-roam-db--insert-ref))))
|
||||
(setq ref-count (+ ref-count (org-roam-db--insert-refs))))
|
||||
(file-error
|
||||
(setq org-roam-files (remove file org-roam-files))
|
||||
(org-roam-db--clear-file file)
|
||||
|
74
org-roam.el
74
org-roam.el
@ -512,22 +512,30 @@ Use external shell commands if defined in `org-roam-list-files-commands'."
|
||||
|
||||
;;;; Org extraction functions
|
||||
(defun org-roam--extract-global-props (props)
|
||||
"Extract PROPS from the current org buffer.
|
||||
The search terminates when the first property is encountered."
|
||||
(if (functionp 'org-collect-keywords)
|
||||
(->> (org-collect-keywords props)
|
||||
;; convert (("TITLE" "my title")) to (("TITLE" . "my title"))
|
||||
(mapcar (pcase-lambda (`(,k ,v)) (cons k v))))
|
||||
(let ((buf (org-element-parse-buffer))
|
||||
res)
|
||||
(dolist (prop props)
|
||||
(let ((p (org-element-map buf 'keyword
|
||||
(lambda (kw)
|
||||
(when (string-equal (org-element-property :key kw) prop)
|
||||
(org-element-property :value kw)))
|
||||
:first-match t)))
|
||||
(push (cons prop p) res)))
|
||||
res)))
|
||||
"Extract PROPS from the current org buffer."
|
||||
(let ((collected
|
||||
;; Collect the raw props first
|
||||
;; It'll be returned in the form of
|
||||
;; (("PROP" "value" ...) ("PROP2" "value" ...))
|
||||
(if (functionp 'org-collect-keywords)
|
||||
(org-collect-keywords props)
|
||||
(let ((buf (org-element-parse-buffer))
|
||||
res)
|
||||
(dolist (prop props)
|
||||
(let ((p (org-element-map buf 'keyword
|
||||
(lambda (kw)
|
||||
(when (string-equal (org-element-property :key kw) prop)
|
||||
(org-element-property :value kw)))
|
||||
:first-match nil)))
|
||||
(push (cons prop p) res)))
|
||||
res))))
|
||||
;; convert (("TITLE" "a" "b") ("Another" "c"))
|
||||
;; to (("TITLE" . "a") ("TITLE" . "b") ("Another" . "c"))
|
||||
(let (ret)
|
||||
(pcase-dolist (`(,key . ,values) collected)
|
||||
(dolist (value values)
|
||||
(push (cons key value) ret)))
|
||||
ret)))
|
||||
|
||||
(defun org-roam--get-outline-path ()
|
||||
"Return the outline path to the current entry.
|
||||
@ -763,20 +771,30 @@ backlinks."
|
||||
"website")
|
||||
(t type)))
|
||||
|
||||
(defun org-roam--extract-refs ()
|
||||
"Extract all refs (ROAM_KEY statements) from the current buffer.
|
||||
|
||||
Each ref is returned as a cons of its type and its key."
|
||||
(let (refs)
|
||||
(pcase-dolist
|
||||
(`(,_ . ,roam-key)
|
||||
(org-roam--extract-global-props '("ROAM_KEY")))
|
||||
(let (type path)
|
||||
(pcase roam-key
|
||||
('nil nil)
|
||||
((pred string-empty-p)
|
||||
(user-error "Org property #+roam_key cannot be empty"))
|
||||
(ref
|
||||
(when (string-match org-link-plain-re ref)
|
||||
(setq type (org-roam--collate-types (match-string 1 ref))
|
||||
path (match-string 2 ref)))))
|
||||
(when (and type path)
|
||||
(push (cons type path) refs))))
|
||||
refs))
|
||||
|
||||
(defun org-roam--extract-ref ()
|
||||
"Extract the ref from current buffer and return the type and the key of the ref."
|
||||
(let (type path)
|
||||
(pcase (cdr (assoc "ROAM_KEY"
|
||||
(org-roam--extract-global-props '("ROAM_KEY"))))
|
||||
('nil nil)
|
||||
((pred string-empty-p)
|
||||
(user-error "Org property #+roam_key cannot be empty"))
|
||||
(ref
|
||||
(when (string-match org-link-plain-re ref)
|
||||
(setq type (org-roam--collate-types (match-string 1 ref))
|
||||
path (match-string 2 ref)))))
|
||||
(when (and type path)
|
||||
(cons type path))))
|
||||
(car (org-roam--extract-refs)))
|
||||
|
||||
;;;; Title/Path/Slug conversion
|
||||
(defun org-roam--path-to-slug (path)
|
||||
|
1
tests/roam-files/cite_ref.org
Normal file
1
tests/roam-files/cite_ref.org
Normal file
@ -0,0 +1 @@
|
||||
#+roam_key: cite:mitsuha2007
|
2
tests/roam-files/multiple-refs.org
Normal file
2
tests/roam-files/multiple-refs.org
Normal file
@ -0,0 +1,2 @@
|
||||
#+roam_key: https://www.orgroam.com/
|
||||
#+roam_key: cite:orgroam2020
|
@ -45,7 +45,7 @@
|
||||
(pcase (benchmark-run 1 (org-roam-db-build-cache t))
|
||||
(`(,time ,gcs ,time-in-gc)
|
||||
(message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs)
|
||||
(expect time :to-be-less-than 100))))
|
||||
(expect time :to-be-less-than 110))))
|
||||
(it "builds quickly without change"
|
||||
(pcase (benchmark-run 1 (org-roam-db-build-cache))
|
||||
(`(,time ,gcs ,time-in-gc)
|
||||
|
@ -71,6 +71,40 @@
|
||||
(expect (org-roam--str-to-list "\"hello")
|
||||
:to-throw)))
|
||||
|
||||
(describe "Ref extraction"
|
||||
(before-all
|
||||
(test-org-roam--init))
|
||||
|
||||
(after-all
|
||||
(test-org-roam--teardown))
|
||||
|
||||
(cl-flet
|
||||
((test (fn file)
|
||||
(let* ((fname (test-org-roam--abs-path file))
|
||||
(buf (find-file-noselect fname)))
|
||||
(with-current-buffer buf
|
||||
;; Unlike tag extraction, it doesn't make sense to
|
||||
;; pass a filename.
|
||||
(funcall fn)))))
|
||||
;; Enable "cite:" link parsing
|
||||
(org-link-set-parameters "cite")
|
||||
(it "extracts web keys"
|
||||
(expect (test #'org-roam--extract-ref
|
||||
"web_ref.org")
|
||||
:to-equal
|
||||
'("website" . "//google.com/")))
|
||||
(it "extracts cite keys"
|
||||
(expect (test #'org-roam--extract-ref
|
||||
"cite_ref.org")
|
||||
:to-equal
|
||||
'("cite" . "mitsuha2007")))
|
||||
(it "extracts all keys"
|
||||
(expect (test #'org-roam--extract-refs
|
||||
"multiple-refs.org")
|
||||
:to-have-same-items-as
|
||||
'(("cite" . "orgroam2020")
|
||||
("website" . "//www.orgroam.com/"))))))
|
||||
|
||||
(describe "Title extraction"
|
||||
:var (org-roam-title-sources)
|
||||
(before-all
|
||||
|
Reference in New Issue
Block a user