(internal): modularize features (#363)

Addresses #357
This commit is contained in:
N V
2020-03-28 09:16:28 -04:00
committed by GitHub
parent df29da1b6d
commit 22b9d4bd22
9 changed files with 1324 additions and 1102 deletions

View File

@ -34,7 +34,6 @@
;; (company-org-roam-init)
;;; Code:
(require 'cl-lib)
(require 'company)
(require 'org-roam)
@ -89,7 +88,7 @@ The string match is case-insensitive."
Entries with no title do not appear in the completions."
(let ((dir (file-truename org-roam-directory))
(ht (make-hash-table :test #'equal)))
(dolist (row (org-roam-sql [:select [titles file] :from titles]))
(dolist (row (org-roam-db-query [:select [titles file] :from titles]))
(let ((titles (car row))
(file (cadr row)))
(dolist (title titles)
@ -133,7 +132,7 @@ COMMAND and ARG are as per the documentation of `company-backends'."
"Conditional enabling of the `company-org-roam' backend."
(when (org-roam--org-roam-file-p (buffer-file-name (buffer-base-buffer)))
(setq-local company-backends
(cons'company-org-roam company-backends))))
(cons 'company-org-roam company-backends))))
;;;###autoload
(defun company-org-roam-init ()

306
org-roam-capture.el Normal file
View File

@ -0,0 +1,306 @@
;;; org-roam-capture.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.0.0-rc1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite "1.0.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This library provides capture functionality for org-roam
;;; Code:
;;;; Library Requires
(require 'org-capture)
(require 'dash)
(require 's)
;; Declarations
(defvar org-roam-encrypt-files)
(defvar org-roam-directory)
(declare-function org-roam--file-path-from-id "org-roam")
(declare-function org-roam--get-ref-path-completions "org-roam")
(declare-function org-roam--format-link "org-roam")
(defvar org-roam-capture--file-name-default "%<%Y%m%d%H%M%S>"
"The default file name format for Org-roam templates.")
(defvar org-roam-capture--header-default "#+TITLE: ${title}\n"
"The default capture header for Org-roam templates.")
(defvar org-roam-capture--file-path nil
"The file path for the Org-roam capture.
This variable is set during the Org-roam capture process.")
(defvar org-roam-capture--info nil
"An alist of additional information passed to the Org-roam template.
This variable is populated dynamically, and is only non-nil
during the Org-roam capture process.")
(defvar org-roam-capture--context nil
"A symbol, that reflects the context for obtaining the exact point in a file.
This variable is populated dynamically, and is only active during
an Org-roam capture process.
The `title' context is used in `org-roam-insert' and
`org-roam-find-file', where the capture process is triggered upon
trying to create a new file without that `title'.
The `ref' context is used by `org-roam-protocol', where the
capture process is triggered upon trying to find or create a new
note with the given `ref'.")
(defvar org-roam-capture--in-process nil
"Boolean tracking whether Org-roam captures are in-process.")
(defvar org-roam-capture-additional-template-props nil
"Additional props to be added to the Org-roam template.")
(defconst org-roam-capture--template-keywords '(:file-name :head)
"Keywords used in `org-roam-capture-templates' specific to Org-roam.")
(defvar org-roam-capture-templates
'(("d" "default" plain (function org-roam-capture--get-point)
"%?"
:file-name "%<%Y%m%d%H%M%S>-${slug}"
:head "#+TITLE: ${title}\n"
:unnarrowed t))
"Capture templates for Org-roam.
The capture templates are an extension of
`org-capture-templates', and the documentation there also
applies.
`org-capture-templates' are extended in 3 ways:
1. Template expansion capabilities are extended with additional custom syntax.
See `org-roam-capture--fill-template' for more details.
2. The `:file-name' key is added, which expands to the file-name
of the note if it creates a new file. This file-name is
relative to `org-roam-directory', and is without the
file-extension.
3. The `:head' key is added, which contains the template that is
inserted on initial creation (added only once). This is where
insertion of any note metadata should go.")
(defvar org-roam-capture-ref-templates
'(("r" "ref" plain (function org-roam-capture--get-point)
""
:file-name "${slug}"
:head "#+TITLE: ${title}
#+ROAM_KEY: ${ref}\n"
:unnarrowed t))
"The Org-roam templates used during a capture from the roam-ref protocol.
Details on how to specify for the template is given in `org-roam-capture-templates'.")
(defun org-roam-capture--get (keyword)
"Gets the value for KEYWORD from the `org-roam-capture-template'."
(plist-get (plist-get org-capture-plist :org-roam) keyword))
(defun org-roam-capture--put (&rest stuff)
"Puts properties from STUFF into the `org-roam-capture-template'."
(let ((p (plist-get org-capture-plist :org-roam)))
(while stuff
(setq p (plist-put p
(pop stuff) (pop stuff))))
(setq org-capture-plist
(plist-put org-capture-plist :org-roam p))))
(defun org-roam-capture--fill-template (str &optional info)
"Expands the template STR, returning the string.
This is an extension of org-capture's template expansion.
First, it expands ${var} occurrences in STR, using the INFO alist.
If there is a ${var} with no matching var in the alist, the value
of var is prompted for via `completing-read'.
Next, it expands the remaining template string using
`org-capture-fill-template'."
(-> str
(s-format (lambda (key)
(or (s--aget info key)
(completing-read (format "%s: " key ) nil))) nil)
(org-capture-fill-template)))
(defun org-roam-capture--find-file-h ()
"Opens the newly created template file.
This is added as a hook to `org-capture-after-finalize-hook'."
(when-let ((file-path (org-roam-capture--get :file-path)))
(unless org-note-abort
(find-file file-path)))
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--find-file-h))
(defun org-roam-capture--insert-link-h ()
"Insert the link into the original buffer, after the capture process is done.
This is added as a hook to `org-capture-after-finalize-hook'."
(when (and (not org-note-abort)
(eq (org-roam-capture--get :capture-fn)
'org-roam-insert))
(when-let ((region (org-roam-capture--get :region))) ;; Remove previously selected text.
(delete-region (car region) (cdr region)))
(insert (org-roam--format-link (org-roam-capture--get :file-path)
(org-roam-capture--get :link-description))))
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--insert-link-h))
(defun org-roam-capture--save-file-maybe-h ()
"Save the file conditionally.
The file is saved if the original value of :no-save is not t and
`org-note-abort' is not t. It is added to
`org-capture-after-finalize-hook'."
(cond
((and (org-roam-capture--get :new-file)
org-note-abort)
(with-current-buffer (org-capture-get :buffer)
(set-buffer-modified-p nil)
(kill-buffer)))
((and (not (org-roam-capture--get :orig-no-save))
(not org-note-abort))
(with-current-buffer (org-capture-get :buffer)
(save-buffer))))
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h))
(defun org-roam-capture--new-file ()
"Return the path to the new file during an Org-roam capture.
This function reads the file-name attribute of the currently
active Org-roam template.
If the file path already exists, it throw an error.
Else, to insert the header content in the file, `org-capture'
prepends the `:head' property of the Org-roam capture template.
To prevent the creation of a new file if the capture process is
aborted, we do the following:
1. Save the original value of the capture template's :no-save.
2. Set the capture template's :no-save to t.
3. Add a function on `org-capture-after-finalize-hook' that saves
the file if the original value of :no-save is not t and
`org-note-abort' is not t."
(let* ((name-templ (or (org-roam-capture--get :file-name)
org-roam-capture--file-name-default))
(new-id (s-trim (org-roam-capture--fill-template
name-templ
org-roam-capture--info)))
(file-path (org-roam--file-path-from-id new-id))
(roam-head (or (org-roam-capture--get :head)
org-roam-capture--header-default))
(org-template (org-capture-get :template))
(roam-template (concat roam-head org-template)))
(when (file-exists-p file-path)
(error (format "File exists at %s, aborting" file-path)))
(org-roam-capture--put :orig-no-save (org-capture-get :no-save)
:new-file t)
(org-capture-put :template
;; Fixes org-capture-place-plain-text throwing 'invalid search bound'
;; when both :unnarowed t and "%?" is missing from the template string;
;; may become unnecessary when the upstream bug is fixed
(if (s-contains-p "%?" roam-template)
roam-template
(concat roam-template "%?"))
:type 'plain
:no-save t)
file-path))
(defun org-roam-capture--expand-template ()
"Expand capture template with information from `org-roam-capture--info'."
(org-capture-put :template
(s-format (org-capture-get :template)
(lambda (key)
(or (s--aget org-roam-capture--info key)
(completing-read (format "%s: " key ) nil))) nil)))
(defun org-roam-capture--get-point ()
"Return exact point to file for org-capture-template.
The file to use is dependent on the context:
If the search is via title, it is assumed that the file does not
yet exist, and Org-roam will attempt to create new file.
If the search is via ref, it is matched against the Org-roam database.
If there is no file with that ref, a file with that ref is created.
This function is used solely in Org-roam's capture templates: see
`org-roam-capture-templates'."
(let ((file-path (pcase org-roam-capture--context
('title
(org-roam-capture--new-file))
('ref
(let ((completions (org-roam--get-ref-path-completions))
(ref (cdr (assoc 'ref org-roam-capture--info))))
(or (cdr (assoc ref completions))
(org-roam-capture--new-file))))
(_ (error "Invalid org-roam-capture-context")))))
(org-roam-capture--expand-template)
(org-roam-capture--put :file-path file-path)
(while org-roam-capture-additional-template-props
(let ((prop (pop org-roam-capture-additional-template-props))
(val (pop org-roam-capture-additional-template-props)))
(org-roam-capture--put prop val)))
(set-buffer (org-capture-target-buffer file-path))
(widen)
(goto-char (point-max))))
(defun org-roam-capture--cleanup-h ()
"Cleans up after an Org-roam capture process."
(setq org-roam-capture--in-process nil))
(defun org-roam-capture--convert-template (template)
"Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax."
(let* ((copy (copy-tree template))
converted
org-roam-plist
key
val)
;;put positional args on converted template
(dotimes (_ 5)
(push (pop copy) converted))
(while (setq key (pop copy)
val (pop copy))
(if (member key org-roam-capture--template-keywords)
(progn
(push val org-roam-plist)
(push key org-roam-plist))
(push key converted)
(push val converted)))
(append (nreverse converted) `(:org-roam ,org-roam-plist))))
(defun org-roam-capture (&optional goto keys)
"Create a new file, and return the path to the edited file.
The templates are defined at `org-roam-capture-templates'. The
GOTO and KEYS argument have the same functionality as
`org-capture'."
(let ((org-capture-templates (mapcar #'org-roam-capture--convert-template org-roam-capture-templates)))
(when (= (length org-capture-templates) 1)
(setq keys (caar org-capture-templates)))
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h)
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--cleanup-h 10)
(setq org-roam-capture--in-process t)
(org-capture goto keys)))
(provide 'org-roam-capture)
;;; org-roam-capture.el ends here

119
org-roam-completion.el Normal file
View File

@ -0,0 +1,119 @@
;;; org-roam-completion.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.0.0-rc1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite "1.0.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This library provides completion for org-roam.
;;; Code:
;;;; Library Requires
(require 'cl-lib)
(require 's)
(defvar helm-pattern)
(declare-function helm "ext:helm")
(declare-function helm-build-sync-source "ext:helm-source" (name &rest args) t)
(defcustom org-roam-completion-system 'default
"The completion system to be used by `org-roam'."
:type '(radio
(const :tag "Default" default)
(const :tag "Ido" ido)
(const :tag "Ivy" ivy)
(const :tag "Helm" helm)
(function :tag "Custom function"))
:group 'org-roam)
(defcustom org-roam-completion-fuzzy-match nil
"Whether to fuzzy match Org-roam's completion candidates."
:type 'boolean
:group 'org-roam)
(defun org-roam-completion--helm-candidate-transformer (candidates _source)
"Transforms CANDIDATES for Helm-based completing read.
SOURCE is not used."
(let ((prefix (propertize "[?] "
'face 'helm-ff-prefix)))
(cons (propertize helm-pattern
'display (concat prefix helm-pattern))
candidates)))
(cl-defun org-roam-completion--completing-read (prompt choices &key
require-match initial-input
action)
"Present a PROMPT with CHOICES and optional INITIAL-INPUT.
If REQUIRE-MATCH is t, the user must select one of the CHOICES.
Return user choice."
(let (res)
(setq res
(cond
((eq org-roam-completion-system 'ido)
(let ((candidates (mapcar #'car choices)))
(ido-completing-read prompt candidates nil require-match initial-input)))
((eq org-roam-completion-system 'default)
(completing-read prompt choices nil require-match initial-input))
((eq org-roam-completion-system 'ivy)
(if (fboundp 'ivy-read)
(ivy-read prompt choices
:initial-input initial-input
:require-match require-match
:action (prog1 action
(setq action nil))
:caller 'org-roam--completing-read
:re-builder (if org-roam-completion-fuzzy-match 'ivy--regex-fuzzy
'regexp-quote))
(user-error "Please install ivy from \
https://github.com/abo-abo/swiper")))
((eq org-roam-completion-system 'helm)
(unless (and (fboundp 'helm)
(fboundp 'helm-build-sync-source))
(user-error "Please install helm from \
https://github.com/emacs-helm/helm"))
(let ((source (helm-build-sync-source prompt
:candidates (mapcar #'car choices)
:filtered-candidate-transformer
(and (not require-match)
#'org-roam-completion--helm-candidate-transformer)
:fuzzy-match org-roam-completion-fuzzy-match))
(buf (concat "*org-roam "
(s-downcase (s-chop-suffix ":" (s-trim prompt)))
"*")))
(or (helm :sources source
:action (if action
(prog1 action
(setq action nil))
#'identity)
:prompt prompt
:input initial-input
:buffer buf)
(keyboard-quit))))))
(if action
(funcall action res)
res)))
(provide 'org-roam-completion)
;;; org-roam-completion.el ends here

364
org-roam-db.el Normal file
View File

@ -0,0 +1,364 @@
;;; org-roam-db.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.0.0-rc1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite "1.0.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This library is provides the underlying database api to org-roam
;;
;;; Code:
;;;; Library Requires
(require 'emacsql)
(require 'emacsql-sqlite)
(require 'org-roam-macs)
(defvar org-roam-directory)
(defvar org-roam-verbose)
(declare-function org-roam--extract-titles "org-roam")
(declare-function org-roam--extract-ref "org-roam")
(declare-function org-roam--extract-links "org-roam")
(declare-function org-roam--maybe-update-buffer "org-roam")
(declare-function org-roam--list-files "org-roam")
;;;; Options
(defcustom org-roam-db-location nil
"Location of the Org-roam database.
If this is non-nil, the Org-roam sqlite database is saved here.
It is the user's responsibility to set this correctly, especially
when used with multiple Org-roam instances."
:type 'string
:group 'org-roam)
(defconst org-roam-db--version 1)
(defconst org-roam-db--sqlite-available-p
(with-demoted-errors "Org-roam initialization: %S"
(emacsql-sqlite-ensure-binary)
t))
(defvaralias 'org-roam--db-connection 'org-roam-db--connection)
(make-obsolete-variable 'org-roam--db-connection 'org-roam-db--connection "2020/03/28")
(defvar org-roam-db--connection (make-hash-table :test #'equal)
"Database connection to Org-roam database.")
;;;; Core Functions
(defalias 'org-roam--get-db 'org-roam-db--get)
(make-obsolete 'org-roam--get-db 'org-roam-db--get "2020/03/28")
(defun org-roam-db--get ()
"Return the sqlite db file."
(interactive "P")
(or org-roam-db-location
(expand-file-name "org-roam.db" org-roam-directory)))
(defun org-roam-db--get-connection ()
"Return the database connection, if any."
(gethash (file-truename org-roam-directory)
org-roam-db--connection))
(defun org-roam-db ()
"Entrypoint to the Org-roam sqlite database.
Initializes and stores the database, and the database connection.
Performs a database upgrade when required."
(unless (and (org-roam-db--get-connection)
(emacsql-live-p (org-roam-db--get-connection)))
(let* ((db-file (org-roam-db--get))
(init-db (not (file-exists-p db-file))))
(make-directory (file-name-directory db-file) t)
(let ((conn (emacsql-sqlite db-file)))
(set-process-query-on-exit-flag (emacsql-process conn) nil)
(puthash (file-truename org-roam-directory)
conn
org-roam-db--connection)
(when init-db
(org-roam-db--init conn))
(let* ((version (caar (emacsql conn "PRAGMA user_version")))
(version (org-roam-db--maybe-update conn version)))
(cond
((> version org-roam-db--version)
(emacsql-close conn)
(user-error
"The Org-roam database was created with a newer Org-roam version. "
"You need to update the Org-roam package"))
((< version org-roam-db--version)
(emacsql-close conn)
(error "BUG: The Org-roam database scheme changed %s"
"and there is no upgrade path")))))))
(org-roam-db--get-connection))
;;;; Entrypoint: (org-roam-db-query)
(defalias 'org-roam-sql 'org-roam-db-query)
(make-obsolete 'org-roam-sql 'org-roam-db-query "2020/03/28")
(defun org-roam-db-query (sql &rest args)
"Run SQL query on Org-roam database with ARGS.
SQL can be either the emacsql vector representation, or a string."
(if (stringp sql)
(emacsql (org-roam-db) (apply #'format sql args))
(apply #'emacsql (org-roam-db) sql args)))
;;;; Schemata
(defconst org-roam-db--table-schemata
'((files
[(file :unique :primary-key)
(hash :not-null)
(last-modified :not-null)
])
(file-links
[(file-from :not-null)
(file-to :not-null)
(properties :not-null)])
(titles
[
(file :not-null)
titles])
(refs
[(ref :unique :not-null)
(file :not-null)])))
(defun org-roam-db--init (db)
"Initialize database DB with the correct schema and user version."
(emacsql-with-transaction db
(pcase-dolist (`(,table . ,schema) org-roam-db--table-schemata)
(emacsql db [:create-table $i1 $S2] table schema))
(emacsql db (format "PRAGMA user_version = %s" org-roam-db--version))))
(defun org-roam-db--maybe-update (db version)
"Upgrades the database schema for DB, if VERSION is old."
(emacsql-with-transaction db
'ignore
;; Do nothing now
version))
(defun org-roam-db--close (&optional db)
"Closes the database connection for database DB.
If DB is nil, closes the database connection for the database in
the current `org-roam-directory'."
(unless db
(setq db (org-roam-db--get-connection)))
(when (and db (emacsql-live-p db))
(emacsql-close db)))
(defun org-roam-db--close-all ()
"Closes all database connections made by Org-roam."
(dolist (conn (hash-table-values org-roam-db--connection))
(org-roam-db--close conn)))
;;;; Database API
;;;;; Initialization
(defun org-roam-db--initialized-p ()
"Whether the cache has been initialized."
(and (file-exists-p (org-roam-db--get))
(> (caar (org-roam-db-query [:select (funcall count) :from titles]))
0)))
(defun org-roam-db--ensure-built ()
"Ensures that Org-roam cache is built."
(unless (org-roam-db--initialized-p)
(error "[Org-roam] your cache isn't built yet! Please run org-roam-db-build-cache")))
;;;;; Clearing
(defalias 'org-roam--db-clear 'org-roam-db--clear)
(make-obsolete 'org-roam--db-clear 'org-roam-db--clear "2020/03/28")
(defun org-roam-db--clear ()
"Clears all entries in the caches."
(interactive)
(when (file-exists-p (org-roam-db--get))
(org-roam-db-query [:delete :from files])
(org-roam-db-query [:delete :from titles])
(org-roam-db-query [:delete :from file-links])
(org-roam-db-query [:delete :from refs])))
(defun org-roam-db--clear-file (&optional filepath)
"Remove any related links to the file at FILEPATH.
This is equivalent to removing the node from the graph."
(let* ((path (or filepath
(buffer-file-name)))
(file (file-truename path)))
(org-roam-db-query [:delete :from files
:where (= file $s1)]
file)
(org-roam-db-query [:delete :from file-links
:where (= file-from $s1)]
file)
(org-roam-db-query [:delete :from titles
:where (= file $s1)]
file)
(org-roam-db-query [:delete :from refs
:where (= file $s1)]
file)))
;;;;; Insertion
(defun org-roam-db--insert-links (links)
"Insert LINKS into the Org-roam cache."
(org-roam-db-query
[:insert :into file-links
:values $v1]
links))
(defun org-roam-db--insert-titles (file titles)
"Insert TITLES for a FILE into the Org-roam cache."
(org-roam-db-query
[:insert :into titles
:values $v1]
(list (vector file titles))))
(defun org-roam-db--insert-ref (file ref)
"Insert REF for FILE into the Org-roam cache."
(org-roam-db-query
[:insert :into refs
:values $v1]
(list (vector ref file))))
;;;;; Fetching
(defun org-roam-db--get-current-files ()
"Return a hash-table of file to the hash of its file contents."
(let* ((current-files (org-roam-db-query [:select * :from files]))
(ht (make-hash-table :test #'equal)))
(dolist (row current-files)
(puthash (car row) (cadr row) ht))
ht))
(defun org-roam-db--get-titles (file)
"Return the titles of FILE from the cache."
(caar (org-roam-db-query [:select [titles] :from titles
:where (= file $s1)]
file
:limit 1)))
;;;;; Updating
(defun org-roam-db--update-titles ()
"Update the title of the current buffer into the cache."
(let ((file (file-truename (buffer-file-name))))
(org-roam-db-query [:delete :from titles
:where (= file $s1)]
file)
(org-roam-db--insert-titles file (org-roam--extract-titles))))
(defun org-roam-db--update-refs ()
"Update the ref of the current buffer into the cache."
(let ((file (file-truename (buffer-file-name))))
(org-roam-db-query [:delete :from refs
:where (= file $s1)]
file)
(when-let ((ref (org-roam--extract-ref)))
(org-roam-db--insert-ref file ref))))
(defun org-roam-db--update-cache-links ()
"Update the file links of the current buffer in the cache."
(let ((file (file-truename (buffer-file-name))))
(org-roam-db-query [:delete :from file-links
:where (= file-from $s1)]
file)
(when-let ((links (org-roam--extract-links)))
(org-roam-db--insert-links links))))
(defun org-roam-db--update-file (&optional file-path)
"Update Org-roam cache for FILE-PATH."
(let (buf)
(if file-path
(setq buf (find-file-noselect file-path))
(setq buf (current-buffer)))
(with-current-buffer buf
(save-excursion
(org-roam-db--update-titles)
(org-roam-db--update-refs)
(org-roam-db--update-cache-links)
(org-roam--maybe-update-buffer :redisplay t)))))
;;;;; org-roam-db-build-cache
(defalias 'org-roam-build-cache 'org-roam-db-build-cache)
(make-obsolete 'org-roam-build-cache 'org-roam-db-build-cache "2020/03/28")
(defun org-roam-db-build-cache ()
"Build the cache for `org-roam-directory'."
(interactive)
(org-roam-db--close) ;; Force a reconnect
(org-roam-db) ;; To initialize the database, no-op if already initialized
(let* ((org-roam-files (org-roam--list-files org-roam-directory))
(current-files (org-roam-db--get-current-files))
(time (current-time))
all-files all-links all-titles all-refs)
(dolist (file org-roam-files)
(org-roam--with-temp-buffer
(insert-file-contents file)
(let ((contents-hash (secure-hash 'sha1 (current-buffer))))
(unless (string= (gethash file current-files)
contents-hash)
(org-roam-db--clear-file file)
(setq all-files
(cons (vector file contents-hash time) all-files))
(when-let (links (org-roam--extract-links file))
(setq all-links (append links all-links)))
(let ((titles (org-roam--extract-titles)))
(setq all-titles (cons (vector file titles) all-titles)))
(when-let ((ref (org-roam--extract-ref)))
(setq all-refs (cons (vector ref file) all-refs))))
(remhash file current-files))))
(dolist (file (hash-table-keys current-files))
;; These files are no longer around, remove from cache...
(org-roam-db--clear-file file))
(when all-files
(org-roam-db-query
[:insert :into files
:values $v1]
all-files))
(when all-links
(org-roam-db-query
[:insert :into file-links
:values $v1]
all-links))
(when all-titles
(org-roam-db-query
[:insert :into titles
:values $v1]
all-titles))
(when all-refs
(org-roam-db-query
[:insert :into refs
:values $v1]
all-refs))
(let ((stats (list :files (length all-files)
:links (length all-links)
:titles (length all-titles)
:refs (length all-refs)
:deleted (length (hash-table-keys current-files)))))
(when org-roam-verbose
(message "files: %s, links: %s, titles: %s, refs: %s, deleted: %s"
(plist-get stats :files)
(plist-get stats :links)
(plist-get stats :titles)
(plist-get stats :refs)
(plist-get stats :deleted)))
stats)))
(provide 'org-roam-db)
;;; org-roam-db.el ends here

178
org-roam-graph.el Normal file
View File

@ -0,0 +1,178 @@
;;; org-roam-graph.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.0.0-rc1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite "1.0.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This library provides graphing functionality for org-roam.
;;
;;; Code:
(require 'xml) ;xml-escape-string
(require 's) ;s-truncate, s-replace
(require 'org-roam-macs)
;;;; Declarations
(defvar org-roam-directory)
(declare-function org-roam-db--ensure-built "org-roam-db")
(declare-function org-roam-db-query "org-roam-db")
(declare-function org-roam--path-to-slug "org-roam")
;;;; Options
(defcustom org-roam-graph-viewer (executable-find "firefox")
"Path to executable for viewing SVG."
:type 'string
:group 'org-roam)
(defcustom org-roam-graph-executable (executable-find "dot")
"Path to grapher executable."
:type 'string
:group 'org-roam)
(defcustom org-roam-grapher-extra-options nil
"Extra options when constructing the grapher graph.
Example:
'((\"rankdir\" . \"LR\"))"
:type '(alist)
:group 'org-roam)
(defcustom org-roam-graph-max-title-length 100
"Maximum length of titles in graph nodes."
:type 'number
:group 'org-roam)
(defcustom org-roam-graph-exclude-matcher nil
"Matcher for excluding nodes from the generated graph.
Any nodes and links for file paths matching this string is
excluded from the graph.
If value is a string, the string is the only matcher.
If value is a list, all file paths matching any of the strings
are excluded."
:type '(choice
(string :tag "Matcher")
(list :tag "Matchers"))
:group 'org-roam)
(defcustom org-roam-graph-node-shape "ellipse"
"Shape of graph nodes."
:type 'string
:group 'org-roam)
;;;; Functions
(defun org-roam-graph--expand-matcher (col &optional negate where)
"Return the exclusion regexp from `org-roam-graph-exclude-matcher'.
COL is the symbol to be matched against. if NEGATE, add :not to sql query.
set WHERE to true if WHERE query already exists."
(let ((matchers (cond ((null org-roam-graph-exclude-matcher)
nil)
((stringp org-roam-graph-exclude-matcher)
(cons (concat "%" org-roam-graph-exclude-matcher "%") nil))
((listp org-roam-graph-exclude-matcher)
(mapcar (lambda (m)
(concat "%" m "%"))
org-roam-graph-exclude-matcher))
(t
(error "Invalid org-roam-graph-exclude-matcher"))))
res)
(dolist (match matchers)
(if where
(push :and res)
(push :where res)
(setq where t))
(push col res)
(when negate
(push :not res))
(push :like res)
(push match res))
(nreverse res)))
(defun org-roam-graph--build ()
"Build the grapher string.
The Org-roam database titles table is read, to obtain the list of titles.
The file-links table is then read to obtain all directed links, and formatted
into a digraph."
(org-roam-db--ensure-built)
(org-roam--with-temp-buffer
(let* ((node-query `[:select [file titles]
:from titles
,@(org-roam-graph--expand-matcher 'file t)])
(nodes (org-roam-db-query node-query))
(edges-query `[:select :distinct [file-to file-from]
:from file-links
,@(org-roam-graph--expand-matcher 'file-to t)
,@(org-roam-graph--expand-matcher 'file-from t t)])
(edges (org-roam-db-query edges-query)))
(insert "digraph \"org-roam\" {\n")
(dolist (option org-roam-grapher-extra-options)
(insert (concat (car option)
"="
(cdr option)
";\n")))
(dolist (node nodes)
(let* ((file (xml-escape-string (car node)))
(title (or (caadr node)
(org-roam--path-to-slug file)))
(shortened-title (s-truncate org-roam-graph-max-title-length title)))
(insert
(format " \"%s\" [label=\"%s\", shape=%s, URL=\"org-protocol://roam-file?file=%s\", tooltip=\"%s\"];\n"
file
(s-replace "\"" "\\\"" shortened-title)
org-roam-graph-node-shape
(url-hexify-string file)
(xml-escape-string title)))))
(dolist (edge edges)
(insert (format " \"%s\" -> \"%s\";\n"
(xml-escape-string (car edge))
(xml-escape-string (cadr edge)))))
(insert "}")
(buffer-string))))
(defalias 'org-roam-show-graph 'org-roam-graph-show)
(make-obsolete 'org-roam-show-graph 'org-roam-graph-show "2020/03/28")
(defun org-roam-graph-show (&optional prefix)
"Generate and displays the Org-roam graph using `org-roam-graph-viewer'.
If PREFIX, then the graph is generated but the viewer is not invoked."
(interactive "P")
(declare (indent 0))
(unless org-roam-graph-executable
(user-error "Can't find %s executable. Please check if it is in your path"
org-roam-graph-executable))
(let ((temp-dot (expand-file-name "graph.dot" temporary-file-directory))
(temp-graph (expand-file-name "graph.svg" temporary-file-directory))
(graph (org-roam-graph--build)))
(with-temp-file temp-dot
(insert graph))
(call-process org-roam-graph-executable nil 0 nil temp-dot "-Tsvg" "-o" temp-graph)
(unless prefix
(if (and org-roam-graph-viewer (executable-find org-roam-graph-viewer))
(call-process org-roam-graph-viewer nil 0 nil temp-graph)
(view-file temp-graph)))))
(provide 'org-roam-graph)
;;; org-roam-graph.el ends here

48
org-roam-macs.el Normal file
View File

@ -0,0 +1,48 @@
;;; org-roam-macs.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.0.0-rc1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite "1.0.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This library implements macros used throughout org-roam
;;
;;
;;; Code:
;;;; Library Requires
(defmacro org-roam--with-temp-buffer (&rest body)
"Execute BODY within a temp buffer.
Like `with-temp-buffer', but propagates `org-roam-directory'."
(declare (indent 0) (debug t))
(let ((current-org-roam-directory (make-symbol "current-org-roam-directory")))
`(let ((,current-org-roam-directory org-roam-directory))
(with-temp-buffer
(let ((org-roam-directory ,current-org-roam-directory))
,@body)))))
(provide 'org-roam-macs)
;;; org-roam-macs.el ends here

View File

@ -35,23 +35,10 @@
;; 2. "roam-ref": This protocol creates or opens a note with the given REF
;;
;;; Code:
(require 'org-protocol)
(require 'org-roam)
(declare-function org-roam-find-ref "org-roam" (&optional info))
(declare-function org-roam--capture-get-point "org-roam" ())
(defvar org-roam-ref-capture-templates
'(("r" "ref" plain (function org-roam--capture-get-point)
""
:file-name "${slug}"
:head "#+TITLE: ${title}
#+ROAM_KEY: ${ref}\n"
:unnarrowed t))
"The Org-roam templates used during a capture from the roam-ref protocol.
Details on how to specify for the template is given in `org-roam-capture-templates'.")
;;;; Functions
(defun org-roam-protocol-open-ref (info)
"Process an org-protocol://roam-ref?ref= style url with INFO.
@ -70,9 +57,9 @@ It opens or creates a note with the given ref.
(error "No ref key provided"))
(when-let ((title (cdr (assoc 'title decoded-alist))))
(push (cons 'slug (org-roam--title-to-slug title)) decoded-alist))
(let* ((org-roam-capture-templates org-roam-ref-capture-templates)
(org-roam--capture-context 'ref)
(org-roam--capture-info decoded-alist)
(let* ((org-roam-capture-templates org-roam-capture-ref-templates)
(org-roam-capture--context 'ref)
(org-roam-capture--info decoded-alist)
(template (cdr (assoc 'template decoded-alist))))
(raise-frame)
(org-roam-capture nil template)

File diff suppressed because it is too large Load Diff

View File

@ -31,94 +31,97 @@
(require 'org-roam)
(require 'dash)
(defun abs-path (file-path)
(defun org-roam-test-abs-path (file-path)
"Get absolute FILE-PATH from `org-roam-directory'."
(file-truename (expand-file-name file-path org-roam-directory)))
(defun org-roam--test-find-new-file (path)
(let ((path (abs-path path)))
(defun org-roam-test-find-new-file (path)
"PATH."
(let ((path (org-roam-test-abs-path path)))
(make-directory (file-name-directory path) t)
(find-file path)))
(defvar org-roam--tests-directory (file-truename (concat default-directory "tests/roam-files"))
(defvar org-roam-test-directory (file-truename (concat default-directory "tests/roam-files"))
"Directory containing org-roam test org files.")
(defun org-roam--test-init ()
(org-roam--db-close)
(let ((original-dir org-roam--tests-directory)
(defun org-roam-test-init ()
"."
(org-roam-db--close)
(let ((original-dir org-roam-test-directory)
(new-dir (expand-file-name (make-temp-name "org-roam") temporary-file-directory)))
(copy-directory original-dir new-dir)
(setq org-roam-directory new-dir)
(org-roam-mode +1)))
;;; Tests
(describe "org-roam-build-cache"
(describe "org-roam-db-build-cache"
(it "initializes correctly"
(org-roam--test-init)
(org-roam-build-cache)
(org-roam-test-init)
(org-roam-db-build-cache)
;; Cache
(expect (caar (org-roam-sql [:select (funcall count) :from files])) :to-be 8)
(expect (caar (org-roam-sql [:select (funcall count) :from file-links])) :to-be 5)
(expect (caar (org-roam-sql [:select (funcall count) :from titles])) :to-be 8)
(expect (caar (org-roam-sql [:select (funcall count) :from titles
(expect (caar (org-roam-db-query [:select (funcall count) :from files])) :to-be 8)
(expect (caar (org-roam-db-query [:select (funcall count) :from file-links])) :to-be 5)
(expect (caar (org-roam-db-query [:select (funcall count) :from titles])) :to-be 8)
(expect (caar (org-roam-db-query [:select (funcall count) :from titles
:where titles :is-null])) :to-be 2)
(expect (caar (org-roam-sql [:select (funcall count) :from refs])) :to-be 1)
(expect (caar (org-roam-db-query [:select (funcall count) :from refs])) :to-be 1)
;; TODO Test files
;; Links -- File-from
(expect (caar (org-roam-sql [:select (funcall count) :from file-links
(expect (caar (org-roam-db-query [:select (funcall count) :from file-links
:where (= file-from $s1)]
(abs-path "foo.org"))) :to-be 1)
(expect (caar (org-roam-sql [:select (funcall count) :from file-links
(org-roam-test-abs-path "foo.org"))) :to-be 1)
(expect (caar (org-roam-db-query [:select (funcall count) :from file-links
:where (= file-from $s1)]
(abs-path "nested/bar.org"))) :to-be 2)
(org-roam-test-abs-path "nested/bar.org"))) :to-be 2)
;; Links -- File-to
(expect (caar (org-roam-sql [:select (funcall count) :from file-links
(expect (caar (org-roam-db-query [:select (funcall count) :from file-links
:where (= file-to $s1)]
(abs-path "nested/foo.org"))) :to-be 1)
(expect (caar (org-roam-sql [:select (funcall count) :from file-links
(org-roam-test-abs-path "nested/foo.org"))) :to-be 1)
(expect (caar (org-roam-db-query [:select (funcall count) :from file-links
:where (= file-to $s1)]
(abs-path "nested/bar.org"))) :to-be 1)
(expect (caar (org-roam-sql [:select (funcall count) :from file-links
(org-roam-test-abs-path "nested/bar.org"))) :to-be 1)
(expect (caar (org-roam-db-query [:select (funcall count) :from file-links
:where (= file-to $s1)]
(abs-path "unlinked.org"))) :to-be 0)
(org-roam-test-abs-path "unlinked.org"))) :to-be 0)
;; TODO Test titles
(expect (org-roam-sql [:select * :from titles])
(expect (org-roam-db-query [:select * :from titles])
:to-have-same-items-as
(list (list (abs-path "alias.org")
(list (list (org-roam-test-abs-path "alias.org")
(list "t1" "a1" "a 2"))
(list (abs-path "bar.org")
(list (org-roam-test-abs-path "bar.org")
(list "Bar"))
(list (abs-path "foo.org")
(list (org-roam-test-abs-path "foo.org")
(list "Foo"))
(list (abs-path "nested/bar.org")
(list (org-roam-test-abs-path "nested/bar.org")
(list "Nested Bar"))
(list (abs-path "nested/foo.org")
(list (org-roam-test-abs-path "nested/foo.org")
(list "Nested Foo"))
(list (abs-path "no-title.org") nil)
(list (abs-path "web_ref.org") nil)
(list (abs-path "unlinked.org")
(list (org-roam-test-abs-path "no-title.org") nil)
(list (org-roam-test-abs-path "web_ref.org") nil)
(list (org-roam-test-abs-path "unlinked.org")
(list "Unlinked"))))
(expect (org-roam-sql [:select * :from refs])
(expect (org-roam-db-query [:select * :from refs])
:to-have-same-items-as
(list (list "https://google.com/" (abs-path "web_ref.org"))))
(list (list "https://google.com/" (org-roam-test-abs-path "web_ref.org"))))
;; Expect rebuilds to be really quick (nothing changed)
(expect (org-roam-build-cache)
(expect (org-roam-db-build-cache)
:to-equal
(list :files 0 :links 0 :titles 0 :refs 0 :deleted 0))))
(describe "org-roam-insert"
(before-each
(org-roam--test-init)
(org-roam--db-clear)
(org-roam-build-cache))
(org-roam-test-init)
(org-roam-db--clear)
(org-roam-db-build-cache))
(it "temp1 -> foo"
(let ((buf (org-roam--test-find-new-file "temp1.org")))
(let ((buf (org-roam-test-find-new-file "temp1.org")))
(with-current-buffer buf
(with-simulated-input
"Foo RET"
@ -126,7 +129,7 @@
(expect (buffer-string) :to-match (regexp-quote "file:foo.org")))
(it "temp2 -> nested/foo"
(let ((buf (org-roam--test-find-new-file "temp2.org")))
(let ((buf (org-roam-test-find-new-file "temp2.org")))
(with-current-buffer buf
(with-simulated-input
"Nested SPC Foo RET"
@ -134,7 +137,7 @@
(expect (buffer-string) :to-match (regexp-quote "file:nested/foo.org")))
(it "nested/temp3 -> foo"
(let ((buf (org-roam--test-find-new-file "nested/temp3.org")))
(let ((buf (org-roam-test-find-new-file "nested/temp3.org")))
(with-current-buffer buf
(with-simulated-input
"Foo RET"
@ -142,7 +145,7 @@
(expect (buffer-string) :to-match (regexp-quote "file:../foo.org")))
(it "a/b/temp4 -> nested/foo"
(let ((buf (org-roam--test-find-new-file "a/b/temp4.org")))
(let ((buf (org-roam-test-find-new-file "a/b/temp4.org")))
(with-current-buffer buf
(with-simulated-input
"Nested SPC Foo RET"
@ -151,153 +154,157 @@
(describe "rename file updates cache"
(before-each
(org-roam--test-init)
(org-roam--db-clear)
(org-roam-build-cache))
(org-roam-test-init)
(org-roam-db--clear)
(org-roam-db-build-cache))
(it "foo -> new_foo"
(rename-file (abs-path "foo.org")
(abs-path "new_foo.org"))
(rename-file (org-roam-test-abs-path "foo.org")
(org-roam-test-abs-path "new_foo.org"))
;; Cache should be cleared of old file
(expect (caar (org-roam-sql [:select (funcall count)
(expect (caar (org-roam-db-query [:select (funcall count)
:from titles
:where (= file $s1)]
(abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-db-query [:select (funcall count)
:from refs
:where (= file $s1)]
(abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-db-query [:select (funcall count)
:from file-links
:where (= file-from $s1)]
(abs-path "foo.org"))) :to-be 0)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
;; Cache should be updated
(expect (org-roam-sql [:select [file-to]
(expect (org-roam-db-query [:select [file-to]
:from file-links
:where (= file-from $s1)]
(abs-path "new_foo.org"))
(org-roam-test-abs-path "new_foo.org"))
:to-have-same-items-as
(list (list (abs-path "bar.org"))))
(expect (org-roam-sql [:select [file-from]
(list (list (org-roam-test-abs-path "bar.org"))))
(expect (org-roam-db-query [:select [file-from]
:from file-links
:where (= file-to $s1)]
(abs-path "new_foo.org"))
(org-roam-test-abs-path "new_foo.org"))
:to-have-same-items-as
(list (list (abs-path "nested/bar.org"))))
(list (list (org-roam-test-abs-path "nested/bar.org"))))
;; Links are updated
(expect (with-temp-buffer
(insert-file-contents (abs-path "nested/bar.org"))
(insert-file-contents (org-roam-test-abs-path "nested/bar.org"))
(buffer-string))
:to-match
(regexp-quote "[[file:../new_foo.org][Foo]]")))
(it "foo -> foo with spaces"
(rename-file (abs-path "foo.org")
(abs-path "foo with spaces.org"))
(rename-file (org-roam-test-abs-path "foo.org")
(org-roam-test-abs-path "foo with spaces.org"))
;; Cache should be cleared of old file
(expect (caar (org-roam-sql [:select (funcall count)
(expect (caar (org-roam-db-query [:select (funcall count)
:from titles
:where (= file $s1)]
(abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-db-query [:select (funcall count)
:from refs
:where (= file $s1)]
(abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-db-query [:select (funcall count)
:from file-links
:where (= file-from $s1)]
(abs-path "foo.org"))) :to-be 0)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
;; Cache should be updated
(expect (org-roam-sql [:select [file-to]
(expect (org-roam-db-query [:select [file-to]
:from file-links
:where (= file-from $s1)]
(abs-path "foo with spaces.org"))
(org-roam-test-abs-path "foo with spaces.org"))
:to-have-same-items-as
(list (list (abs-path "bar.org"))))
(expect (org-roam-sql [:select [file-from]
(list (list (org-roam-test-abs-path "bar.org"))))
(expect (org-roam-db-query [:select [file-from]
:from file-links
:where (= file-to $s1)]
(abs-path "foo with spaces.org"))
(org-roam-test-abs-path "foo with spaces.org"))
:to-have-same-items-as
(list (list (abs-path "nested/bar.org"))))
(list (list (org-roam-test-abs-path "nested/bar.org"))))
;; Links are updated
(expect (with-temp-buffer
(insert-file-contents (abs-path "nested/bar.org"))
(insert-file-contents (org-roam-test-abs-path "nested/bar.org"))
(buffer-string))
:to-match
(regexp-quote "[[file:../foo with spaces.org][Foo]]")))
(it "no-title -> meaningful-title"
(rename-file (abs-path "no-title.org")
(abs-path "meaningful-title.org"))
(rename-file (org-roam-test-abs-path "no-title.org")
(org-roam-test-abs-path "meaningful-title.org"))
;; File has no forward links
(expect (caar (org-roam-sql [:select (funcall count)
(expect (caar (org-roam-db-query [:select (funcall count)
:from file-links
:where (= file-from $s1)]
(abs-path "no-title.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count)
(org-roam-test-abs-path "no-title.org"))) :to-be 0)
(expect (caar (org-roam-db-query [:select (funcall count)
:from file-links
:where (= file-from $s1)]
(abs-path "meaningful-title.org"))) :to-be 1)
(org-roam-test-abs-path "meaningful-title.org"))) :to-be 1)
;; Links are updated with the appropriate name
(expect (with-temp-buffer
(insert-file-contents (abs-path "meaningful-title.org"))
(insert-file-contents (org-roam-test-abs-path "meaningful-title.org"))
(buffer-string))
:to-match
(regexp-quote "[[file:meaningful-title.org][meaningful-title]]")))
(it "web_ref -> hello"
(expect (org-roam-sql
(expect (org-roam-db-query
[:select [file] :from refs
:where (= ref $s1)]
"https://google.com/")
:to-equal
(list (list (abs-path "web_ref.org"))))
(rename-file (abs-path "web_ref.org")
(abs-path "hello.org"))
(expect (org-roam-sql
(list (list (org-roam-test-abs-path "web_ref.org"))))
(rename-file (org-roam-test-abs-path "web_ref.org")
(org-roam-test-abs-path "hello.org"))
(expect (org-roam-db-query
[:select [file] :from refs
:where (= ref $s1)]
"https://google.com/")
:to-equal (list (list (abs-path "hello.org"))))
(expect (caar (org-roam-sql
:to-equal (list (list (org-roam-test-abs-path "hello.org"))))
(expect (caar (org-roam-db-query
[:select [ref] :from refs
:where (= file $s1)]
(abs-path "web_ref.org")))
(org-roam-test-abs-path "web_ref.org")))
:to-equal nil)))
(describe "delete file updates cache"
(before-each
(org-roam--test-init)
(org-roam--db-clear)
(org-roam-build-cache)
(org-roam-test-init)
(org-roam-db--clear)
(org-roam-db-build-cache)
(sleep-for 1))
(it "delete foo"
(delete-file (abs-path "foo.org"))
(expect (caar (org-roam-sql [:select (funcall count)
(delete-file (org-roam-test-abs-path "foo.org"))
(expect (caar (org-roam-db-query [:select (funcall count)
:from titles
:where (= file $s1)]
(abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-db-query [:select (funcall count)
:from refs
:where (= file $s1)]
(abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count)
(org-roam-test-abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-db-query [:select (funcall count)
:from file-links
:where (= file-from $s1)]
(abs-path "foo.org"))) :to-be 0))
(org-roam-test-abs-path "foo.org"))) :to-be 0))
(it "delete web_ref"
(expect (org-roam-sql [:select * :from refs])
(expect (org-roam-db-query [:select * :from refs])
:to-have-same-items-as
(list (list "https://google.com/" (abs-path "web_ref.org"))))
(delete-file (abs-path "web_ref.org"))
(expect (org-roam-sql [:select * :from refs])
(list (list "https://google.com/" (org-roam-test-abs-path "web_ref.org"))))
(delete-file (org-roam-test-abs-path "web_ref.org"))
(expect (org-roam-db-query [:select * :from refs])
:to-have-same-items-as
(list))))
(provide 'test-org-roam)
;;; test-org-roam.el ends here