Skip to content

Commit

Permalink
Add :filter argument to citar-select-ref (emacs-citar#482)
Browse files Browse the repository at this point in the history
* Add :filter argument to `citar-select-ref`

Also simplify and update documentation for functions in `citar-file.el`

Closes emacs-citar#272.

* citar-file--make-file-predicate: Cache results of parsing file field

* citar.el: Add utility functions for making file and note predicates

* Update docstrings
  • Loading branch information
roshanshariff authored Dec 12, 2021
1 parent 63dd3d2 commit bd49a91
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 39 deletions.
45 changes: 30 additions & 15 deletions citar-file.el
Original file line number Diff line number Diff line change
Expand Up @@ -206,22 +206,37 @@ need to scan the contents of DIRS in this case."
(puthash key (nreverse filelist) files))
files))))

(defun citar-file--has-file-p (dirs extensions &optional additional-sep entry-field)
(defun citar-file--has-file (dirs extensions &optional entry-field)
"Return predicate testing whether a key and entry have associated files.
Files are found in two ways:
- In DIRS using `citar-file--directory-files`; see its
documentation for the meaning of EXTENSIONS and ADDITIONAL-SEP.
- By scanning DIRS for files with EXTENSIONS using
`citar-file--directory-files`, which see. Its ADDITIONAL-SEP
argument is taken from `citar-file-additional-files-separator`.
- In the entry field ENTRY-FIELD, when it is non-nil."
(let ((files (citar-file--directory-files dirs nil extensions additional-sep)))
- When ENTRY-FIELD is non-nil, by parsing the entry field it
names using `citar-file--parse-file-field`; see its
documentation. DIRS is used to resolve relative paths and
non-existent files are ignored.
Note: for performance reasons, this function should be called
once per command; the function it returns can be called
repeatedly."
(let ((files (citar-file--directory-files dirs nil extensions
citar-file-additional-files-separator)))
(lambda (key entry)
(or (car (gethash key files))
(when entry-field
(seq-some
#'file-exists-p
(citar-file--parse-file-field entry entry-field dirs)))))))
(let ((cached (gethash key files 'unknown)))
(if (not (eq cached 'unknown))
cached
;; KEY has no files in DIRS, so check the ENTRY-FIELD field of
;; ENTRY. This will run at most once for each KEY; after that, KEY
;; in hash table FILES will either contain nil or a file name found
;; in ENTRY.
(puthash key (seq-some
#'file-exists-p
(citar-file--parse-file-field entry entry-field dirs))
files))))))

(defun citar-file--files-for-entry (key entry dirs extensions)
"Find files related to bibliography item KEY with metadata ENTRY.
Expand All @@ -235,16 +250,16 @@ EXTENSIONS, and how files are found."
KEYS-ENTRIES is a list of (KEY . ENTRY) pairs. Return a list of
files found in two ways:
- Scan directories in DIRS for files starting with keys in
- By scanning directories in DIRS for files starting with keys in
KEYS-ENTRIES and having extensions in EXTENSIONS. The files
may also have additional text after the key, separated by the
value of `citar-file-additional-files-separator`. The scanning
is performed by `citar-file--directory-files`, which see.
- Parse the entries in KEYS-ENTRIES and find file names listed in
the field named by `citar-file-variable`. Relative paths are
resolved in the directories in DIRS, and only existing files
are returned."
- By parsing the field named by `citar-file-variable` of the
entries in KEYS-ENTRIES. DIRS is used to resolve relative
paths and non-existent files are ignored; see
`citar-file--parse-file-field`."
(let* ((keys (seq-map #'car keys-entries))
(files (citar-file--directory-files dirs keys extensions
citar-file-additional-files-separator)))
Expand Down
105 changes: 81 additions & 24 deletions citar.el
Original file line number Diff line number Diff line change
Expand Up @@ -316,28 +316,63 @@ of all citations in the current buffer."

;;; Completion functions

(cl-defun citar-select-ref (&optional &key rebuild-cache multiple)
(defun citar--completion-table (candidates &optional filter)
"Return a completion table for CANDIDATES.
CANDIDATES is an alist with entries (CAND KEY . ENTRY), where
CAND is a display string for the bibliography item given
by (KEY . ENTRY).
FILTER, if non-nil, should be a predicate function taking
arguments KEY and ENTRY. Only candidates for which this
function returns non-nil will be offered for completion.
The returned completion table can be used with `completing-read`
and other completion functions."
(let ((metadata `(metadata (category . citar-reference)
(affixation-function . ,#'citar--affixation))))
(lambda (string predicate action)
(if (eq action 'metadata)
metadata
(let ((predicate
(when (or filter predicate)
(lambda (cand-key-entry)
(pcase-let ((`(,cand ,key . ,entry) cand-key-entry))
(and (or (null filter) (funcall filter key entry))
(or (null predicate) (funcall predicate cand))))))))
(complete-with-action action candidates string predicate))))))

(cl-defun citar-select-ref (&optional &key rebuild-cache multiple filter)
"Select bibliographic references.
A wrapper around 'completing-read' that returns (KEY . ENTRY),
where ENTRY is a field-value alist. Therefore 'car' of the
return value is the cite key, and 'cdr' is an alist of structured
data.
Includes the following optional arguments:
Takes the following optional keyword arguments:
REBUILD-CACHE if t, forces rebuilding the cache before offering
the selection candidates.
REBUILD-CACHE: if t, forces rebuilding the cache before offering
the selection candidates.
MULTIPLE if t, calls `completing-read-multiple` and returns an
alist of (KEY . ENTRY) pairs."
MULTIPLE: if t, calls `completing-read-multiple` and returns an
alist of (KEY . ENTRY) pairs.
FILTER: if non-nil, should be a predicate function taking
arguments KEY and ENTRY. Only candidates for which this
function returns non-nil will be offered for completion. For
example:
(citar-select-ref :filter (citar-has-file))
(citar-select-ref :filter (citar-has-note))
(citar-select-ref
:filter (lambda (_key entry)
(when-let ((keywords (assoc-default \"keywords\" entry)))
(string-match-p \"foo\" keywords))))"
(let* ((candidates (citar--get-candidates rebuild-cache))
(metadata `(metadata (category . citar-reference)
(affixation-function . ,#'citar--affixation)))
(completions (lambda (string predicate action)
(if (eq action 'metadata)
metadata
(complete-with-action action candidates string predicate))))
(completions (citar--completion-table candidates filter))
(embark-transformer-alist (citar--embark-transformer-alist candidates))
(crm-separator "\\s-*&\\s-*")
(chosen (if multiple
Expand Down Expand Up @@ -367,13 +402,13 @@ alist of (KEY . ENTRY) pairs."
(message "Keys not found: %s" (mapconcat #'identity notfound "; ")))
(if multiple keyentries (car keyentries))))

(cl-defun citar-select-refs (&optional &key rebuild-cache)
(cl-defun citar-select-refs (&optional &key rebuild-cache filter)
"Select bibliographic references.
A wrapper around 'citar-select-ref' that returns an alist of (KEY . ENTRY)
cons pairs. If REBUILD-CACHE is non-nil, forces rebuilding the cache before
offering the selection candidates."
(citar-select-ref :rebuild-cache rebuild-cache :multiple t))
Call 'citar-select-ref' with argument :multiple; see its
documentation for the return value and the meaning of
REBUILD-CACHE and FILTER."
(citar-select-ref :rebuild-cache rebuild-cache :multiple t :filter filter))

(defun citar-select-files (files)
"Select file(s) from a list of FILES."
Expand Down Expand Up @@ -489,20 +524,42 @@ personal names of the form 'family, given'."
(list citar-file-variable)
citar-additional-fields))

(defun citar-has-file ()
"Return predicate testing whether entry has associated files.
Return a function that takes arguments KEY and ENTRY and returns
non-nil when the entry has associated files, either in
`citar-library-paths` or the field named in
`citar-file-variable`.
Note: for performance reasons, this function should be called
once per command; the function it returns can be called
repeatedly."
(citar-file--make-file-predicate citar-library-paths
citar-file-extensions
citar-file-variable))

(defun citar-has-note ()
"Return predicate testing whether entry has associated notes.
Return a function that takes arguments KEY and ENTRY and returns
non-nil when the entry has associated notes in `citar-notes-paths`.
Note: for performance reasons, this function should be called
once per command; the function it returns can be called
repeatedly."
(citar-file--make-file-predicate citar-notes-paths
citar-file-note-extensions))

(defun citar--format-candidates (bib-files &optional context)
"Format candidates from BIB-FILES, with optional hidden CONTEXT metadata.
This both propertizes the candidates for display, and grabs the
key associated with each one."
(let* ((candidates nil)
(raw-candidates
(parsebib-parse bib-files :fields (citar--fields-to-parse)))
(hasfilep (citar-file--has-file-p citar-library-paths
citar-file-extensions
citar-file-additional-files-separator
citar-file-variable))
(hasnotep (citar-file--has-file-p citar-notes-paths
citar-file-note-extensions
citar-file-additional-files-separator))
(hasfilep (citar-has-file))
(hasnotep (citar-has-note))
(main-width (citar--format-width (citar-get-template 'main)))
(suffix-width (citar--format-width (citar-get-template 'suffix)))
(symbols-width (string-width (citar--symbols-string t t t)))
Expand Down

0 comments on commit bd49a91

Please sign in to comment.