From c6797cbd755befb9c124a63f8f61d46b1bc5a24e Mon Sep 17 00:00:00 2001 From: Kisaragi Hiu Date: Sat, 7 Nov 2020 16:33:31 +0900 Subject: [PATCH] (feat): Allow one file to have multiple roam_key statements (#1215) --- CHANGELOG.md | 1 + org-roam-buffer.el | 15 +++--- org-roam-db.el | 53 ++++++++++----------- org-roam.el | 74 +++++++++++++++++++----------- tests/roam-files/cite_ref.org | 1 + tests/roam-files/multiple-refs.org | 2 + tests/test-org-roam-perf.el | 2 +- tests/test-org-roam.el | 34 ++++++++++++++ 8 files changed, 120 insertions(+), 62 deletions(-) create mode 100644 tests/roam-files/cite_ref.org create mode 100644 tests/roam-files/multiple-refs.org diff --git a/CHANGELOG.md b/CHANGELOG.md index 240b8ab..c2c7f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/org-roam-buffer.el b/org-roam-buffer.el index 7b2ecf8..d8e69d3 100644 --- a/org-roam-buffer.el +++ b/org-roam-buffer.el @@ -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))) diff --git a/org-roam-db.el b/org-roam-db.el index e09ab42..91dee39 100644 --- a/org-roam-db.el +++ b/org-roam-db.el @@ -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) diff --git a/org-roam.el b/org-roam.el index 73aa1ba..8ecd62e 100644 --- a/org-roam.el +++ b/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) diff --git a/tests/roam-files/cite_ref.org b/tests/roam-files/cite_ref.org new file mode 100644 index 0000000..ea18813 --- /dev/null +++ b/tests/roam-files/cite_ref.org @@ -0,0 +1 @@ +#+roam_key: cite:mitsuha2007 diff --git a/tests/roam-files/multiple-refs.org b/tests/roam-files/multiple-refs.org new file mode 100644 index 0000000..dea8828 --- /dev/null +++ b/tests/roam-files/multiple-refs.org @@ -0,0 +1,2 @@ +#+roam_key: https://www.orgroam.com/ +#+roam_key: cite:orgroam2020 diff --git a/tests/test-org-roam-perf.el b/tests/test-org-roam-perf.el index 739d9f7..667203d 100644 --- a/tests/test-org-roam-perf.el +++ b/tests/test-org-roam-perf.el @@ -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) diff --git a/tests/test-org-roam.el b/tests/test-org-roam.el index 8f5b7a6..0ef095e 100644 --- a/tests/test-org-roam.el +++ b/tests/test-org-roam.el @@ -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