Skip to content

Commit

Permalink
adding the citarn minimal experiment
Browse files Browse the repository at this point in the history
  • Loading branch information
bdarcus committed Jun 7, 2022
1 parent 1976f29 commit ac00995
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 80 deletions.
189 changes: 109 additions & 80 deletions citar.el
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ of all citations in the current buffer."
:group 'citar
:type '(repeat string))

(defcustom citar-select-multiple t
"Use `completing-read-multiple' for selecting citation keys.
When nil, all citar commands will use `completing-read`."
:type 'boolean
:group 'citar)

;;; Keymaps

(defvar citar-map
Expand Down Expand Up @@ -380,13 +386,24 @@ of all citations in the current buffer."
map)
"Keymap for Embark citation-key actions.")

;;; Completion functions
;; Internal variables

(defcustom citar-select-multiple t
"Use `completing-read-multiple' for selecting citation keys.
When nil, all citar commands will use `completing-read`."
:type 'boolean
:group 'citar)
;; Most of this design is adapted from org-mode 'oc-basic',
;; written by Nicolas Goaziou.

(defvar citar--bibliography-cache nil
"Cache for parsed bibliography files.
This is an association list following the pattern:
(FILE-ID . ENTRIES)
FILE-ID is a cons cell (FILE . HASH), with FILE being the absolute file name of
the bibliography file, and HASH a hash of its contents.
ENTRIES is a hash table with citation references as keys and fields alist as
values.")

(defvar citar--completion-cache (make-hash-table :test #'equal)
"Hash with key as completion string, value as citekey.")

;;; Completion functions

(defun citar--completion-table (candidates &optional filter &rest metadata)
"Return a completion table for CANDIDATES.
Expand Down Expand Up @@ -419,7 +436,15 @@ and other completion functions."
(or (null predicate) (funcall predicate cand))))))))
(complete-with-action action candidates string predicate))))))

(cl-defun citar-select-ref (&optional &key rebuild-cache multiple filter)
(defun citar-select-ref (&optional _multiple _filter)
"Select reference, return citekey."
(let* ((table
(or (citar--ref-completion-table)
(user-error "No bibliography set")))
(choice (completing-read "Ref: " table)))
(gethash choice table)))

(cl-defun citaro-select-ref (&optional &key _rebuild-cache multiple filter)
"Select bibliographic references.
A wrapper around 'completing-read' that returns (KEY . ENTRY),
Expand Down Expand Up @@ -448,14 +473,14 @@ FILTER: if non-nil, should be a predicate function taking
:filter (lambda (_key entry)
(when-let ((keywords (assoc-default \"keywords\" entry)))
(string-match-p \"foo\" keywords))))"
(let* ((candidates (citar--get-candidates rebuild-cache))
(let* ((candidates (citar--ref-completion-table))
(chosen (if (and multiple citar-select-multiple)
(citar--select-multiple "References: " candidates
filter 'citar-history citar-presets)
(completing-read "Reference: " (citar--completion-table candidates filter)
nil nil nil 'citar-history citar-presets nil)))
(notfound nil)
(keyentries
(keys
(seq-mapcat
;; Find citation key-entry of selected candidate.
;; CHOICE is either the formatted candidate string, or the citation
Expand All @@ -467,14 +492,14 @@ FILTER: if non-nil, should be a predicate function taking
(if-let ((cand (seq-find
(lambda (cand) (member choice (seq-take cand 2)))
candidates)))
(list (cdr cand))
(list cand)
;; If not found, add CHOICE to NOTFOUND and return nil
(push choice notfound)
nil))
(if (listp chosen) chosen (list chosen)))))
(when notfound
(message "Keys not found: %s" (mapconcat #'identity notfound "; ")))
(if multiple keyentries (car keyentries))))
(when multiple keys keys)))

(cl-defun citar-select-refs (&optional &key rebuild-cache filter)
"Select bibliographic references.
Expand Down Expand Up @@ -572,6 +597,59 @@ HISTORY is the 'completing-read' history argument."
((string-match "http" resource 0) "Links")
(t "Library Files")))))

(defun citar--ref-completion-table ()
"Return completion table for cite keys, as a hash table.
In this hash table, keys are a strings with author, date, and
title of the reference. Values are the cite keys.
Return nil if there are no bibliography files or no entries."
;; Populate bibliography cache.
(let ((entries (citar--parse-bibliography)))
(cond
((null entries) nil) ; no bibliography files
((gethash entries citar--completion-cache)
citar--completion-cache) ; REVIEW ?
(t
(clrhash citar--completion-cache)
(dolist (key (citar--all-keys))
(let ((completion (citar--get-value key "title"))) ; TODO hook up string formatting
(puthash completion key citar--completion-cache)))
(unless (map-empty-p citar--completion-cache) ; no key
(puthash entries t citar--completion-cache) ; REVIEW ?
citar--completion-cache)))))

;; adapted from 'org-cite-basic--parse-bibliography'
(defvar citar--file-id-cache nil
"Hash table linking files to their hash.")

(defun citar--parse-bibliography ()
"List all entries available in the buffer.
Each association follows the pattern
(FILE . ENTRIES)
where FILE is the absolute file name of the bibliography file,
and ENTRIES is a hash table where keys are references and values
are association lists between fields, as symbols, and values as
strings or nil."
(unless (hash-table-p citar--file-id-cache)
(setq citar--file-id-cache (make-hash-table :test #'equal)))
(let ((results nil))
;; FIX the files to parse needs to be a function that returns the right
;; local and/or global bibliography files for the current buffer.
(dolist (file citar-bibliography)
(when (file-readable-p file)
(with-temp-buffer
(when (or (file-has-changed-p file)
(not (gethash file citar--file-id-cache)))
(insert-file-contents file)
(puthash file (md5 (current-buffer)) citar--file-id-cache))
(let* ((file-id (cons file (gethash file citar--file-id-cache)))
(entries
(or (cdr (assoc file-id citar--bibliography-cache))
(let ((table (parsebib-parse file)))
(push (cons file-id table) citar--bibliography-cache)
table))))
(push (cons file entries) results)))))
results))

(defun citar--get-major-mode-function (key &optional default)
"Return function associated with KEY in 'major-mode-functions'.
If no function is found matching KEY for the current major mode,
Expand Down Expand Up @@ -599,9 +677,21 @@ If no function is found, the DEFAULT function is called."
(citar-file--normalize-paths
citar-bibliography)))

(defun citar--get-value (field entry)
"Return the FIELD value for ENTRY."
(cdr (assoc-string field entry 'case-fold)))
(defun citar--get-entry (key)
"Return entry for KEY, as an association list."
(catch :found
;; Iterate through the cached bibliography hashes and find a key.
(pcase-dolist (`(,_ . ,entries) (citar--parse-bibliography))
(let ((entry (gethash key entries)))
(when entry (throw :found entry))))
nil))

(defun citar--get-value (key-or-entry field)
"Return FIELD value for KEY-OR-ENTRY."
(let ((entry (if (stringp key-or-entry)
(citar--get-entry key-or-entry)
key-or-entry)))
(cdr (assoc-string field entry))))

(defun citar--field-with-value (fields entry)
"Return the first field that has a value in ENTRY among FIELDS ."
Expand Down Expand Up @@ -778,38 +868,6 @@ key associated with each one."
"")
"")))

(defvar citar--candidates-cache 'uninitialized
"Store the global candidates list.
Default value of 'uninitialized is used to indicate that cache
has not yet been created")

(defvar-local citar--local-candidates-cache 'uninitialized
;; We use defvar-local so can maintain per-buffer candidate caches.
"Store the local (per-buffer) candidates list.")

;;;###autoload
(defun citar-refresh (&optional force-rebuild-cache scope)
"Reload the candidates cache.
If called interactively with a prefix or if FORCE-REBUILD-CACHE
is non-nil, also run the `citar-before-refresh-hook' hook.
If SCOPE is `global' only global cache is refreshed, if it is
`local' only local cache is refreshed. With any other value both
are refreshed."
(interactive (list current-prefix-arg nil))
(when force-rebuild-cache
(run-hooks 'citar-force-refresh-hook))
(unless (eq 'local scope)
(setq citar--candidates-cache
(citar--format-candidates
(citar-file--normalize-paths citar-bibliography))))
(unless (eq 'global scope)
(setq citar--local-candidates-cache
(citar--format-candidates
(citar--local-files-to-cache) "is:local"))))

(defun citar--get-template (template-name)
"Return template string for TEMPLATE-NAME."
(let ((template
Expand All @@ -818,40 +876,11 @@ are refreshed."
(error "No template for \"%s\" - check variable 'citar-templates'" template-name))
template))

(defun citar--get-candidates (&optional force-rebuild-cache filter)
"Get the cached candidates.
If the cache is unintialized, this will load the cache.
If FORCE-REBUILD-CACHE is t, force reload the cache.
If FILTER, use the function to filter the candidate list."
(when force-rebuild-cache
(citar-refresh force-rebuild-cache))
(when (eq 'uninitialized citar--candidates-cache)
(citar-refresh nil 'global))
(when (eq 'uninitialized citar--local-candidates-cache)
(citar-refresh nil 'local))
(let ((candidates
(seq-concatenate 'list
citar--local-candidates-cache
citar--candidates-cache)))
(if candidates
(if filter
(seq-filter
(pcase-lambda (`(_ ,citekey . ,entry))
(funcall filter citekey entry))
candidates)
candidates)
(unless (or citar--candidates-cache citar--local-candidates-cache)
(error "Make sure to set citar-bibliography and related paths")) )))

(defun citar--get-entry (key)
"Return the cached entry for KEY."
(cddr (seq-find
(lambda (entry)
(string-equal key (cadr entry)))
(citar--get-candidates))))
(defun citar--all-keys ()
"List all keys available in current bibliography."
(seq-mapcat (pcase-lambda (`(,_ . ,entries))
(map-keys entries))
(citar--parse-bibliography)))

(defun citar--get-link (entry)
"Return a link for an ENTRY."
Expand Down
Loading

0 comments on commit ac00995

Please sign in to comment.