Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide example integration with Citar for notes #299

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions ebib-citar.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
;;; ebib-citar.el --- Ebib Notes from Citar -*- lexical-binding: t; -*-

;; Copyright (C) 2024 Samuel W. Flint
;; All rights reserved.

;; Author: Samuel W. Flint <[email protected]>
;; Maintainer: Samuel W. Flint <[email protected]>

;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions
;; are met:
;;
;; 1. Redistributions of source code must retain the above copyright
;; notice, this list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright
;; notice, this list of conditions and the following disclaimer in the
;; documentation and/or other materials provided with the distribution.
;; 3. The name of the author may not be used to endorse or promote products
;; derived from this software without specific prior written permission.
;;
;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
;; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
;; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
;; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
;; NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE,
;; DATA, OR PROFITS ; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
;; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

;;; Commentary:

;; This file is part of Ebib, a BibTeX database manager for Emacs. It
;; contains functions that integrate [citar] with Ebib.
;;
;; [citar]: https://github.com/emacs-citar/citar
;;
;; To use this code, `require' it in your init file, and enable
;; `ebib-citar-mode'. This mode will use `citar' to manage notes, and
;; set `citar-open-entry-function' to open entries in Ebib.

;;; Code:

(require 'ebib)
(require 'citar)

(defvar ebib-citar--notes-checker nil
"Notes checker for ebib-citar.")

(defun ebib-citar-backend (operation &rest args)
"Citar backend for ebib notes.
Execute OPERATION given ARGS per `ebib-notes-storage', which see."
(pcase operation
(`:has-note
(unless ebib-citar--notes-checker
(setf ebib-citar--notes-checker (citar-has-notes)))
(when ebib-citar--notes-checker
(funcall ebib-citar--notes-checker (car args))))
(`:create-note
(citar-create-note (car args))
(ebib-citar-backend :open-note (list (car args))))
(`:open-note
(when-let* ((citekey (car args))
(notes-hash-table (citar-get-notes citekey))
(notes-files (gethash citekey notes-hash-table))
(notes-file (nth (1- (length notes-files)) notes-files))
(buffer (find-file-noselect notes-file)))
buffer))))

(defun ebib-citar-entry-function (citekey)
"Open CITEKEY using `ebib'."
(ebib nil citekey))

(defvar ebib--citar-previous-values nil
"Ebib-Citar integration variable backup.
When `ebib-citar-mode' is enabled, this variable is used to
backup the values of the following variables:
- `ebib-notes-storage'
- `citar-open-entry-function'")

(define-minor-mode ebib-citar-mode
"Integrate Ebib and Citar.
When enabled, use Citar to manage notes and Ebib to open
bibliographic entries."
:global t
:group 'ebib-notes
(if ebib-citar-mode
(setf ebib--citar-previous-values (list ebib-notes-storage citar-open-entry-function)
ebib-notes-storage #'ebib-citar-backend
citar-open-entry-function #'ebib-citar-entry-function)
(setf ebib-notes-storage (nth 0 ebib--citar-previous-values)
citar-open-entry-function (nth 1 ebib--citar-previous-values)
ebib--citar-previous-values nil)))

(provide 'ebib-citar)

;;; ebib-citar.el ends here
50 changes: 40 additions & 10 deletions ebib-notes.el
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,38 @@ key."

(defcustom ebib-notes-storage 'one-file-per-note
"Storage location of external notes.
Possible values are `one-file-per-note' and
`multiple-notes-per-file'. If `one-file-per-note', each note is
stored in a separate file in the directory `ebib-notes-directory'
or in the first directory listed in `ebib-bib-search-dirs' if
`ebib-notes-directory' is nil.
Possible values are `one-file-per-note',
`multiple-notes-per-file', and a function. If
`one-file-per-note', each note is stored in a separate file in
the directory `ebib-notes-directory' or in the first directory
listed in `ebib-bib-search-dirs' if `ebib-notes-directory' is
nil.

If this option is set to `multiple-notes-per-file', notes are
searched in the files and directories listed in
`ebib-notes-locations'."
`ebib-notes-locations'.

If this option is set to a function, it must be a function of the
following form: (ACTION &rest PARAMETERS). ACTION is a keyword
indicating what action to perform, and PARAMETERS is a list of
parameters, which vary based on ACTION. The following keywords
must be supported:

- `:has-note' takes a KEY, and returns non-nil if a note exists
for the entry.

- `:create-note' takes a KEY and a DATABASE and returns a cons
of (BUFFER . POINT) where the note begins. Depending on how
note creation is performed, it may be helpful to set
`ebib-notes-template' to the empty string.

- `:open-note' takes a KEY and returns a buffer, with point at
the start of the note."
:group 'ebib-notes
:type '(choice (const :tag "Use one file per note" one-file-per-note)
(const :tag "Use multiple notes per file" multiple-notes-per-file)))
(const :tag "Use multiple notes per file" multiple-notes-per-file)
(function-item :tag "Use Citar" ebib-citar-backend)
(function :tag "Use external library")))

(defcustom ebib-notes-directory nil
"Directory to save notes files to.
Expand Down Expand Up @@ -342,7 +362,9 @@ note file if `ebib-notes-storage' is set to `one-note-per-file'."
(member key ebib--notes-list))
((eq ebib-notes-storage 'one-file-per-note)
(if (file-readable-p (ebib--create-notes-file-name key))
(cl-pushnew key ebib--notes-list))))))
(cl-pushnew key ebib--notes-list)))
((functionp ebib-notes-storage)
(apply ebib-notes-storage :has-note (list key))))))

(defun ebib--notes-goto-note (key)
"Find or create a buffer containing the note for KEY.
Expand Down Expand Up @@ -375,7 +397,11 @@ If KEY has no note, return nil."
;; Otherwise try and open the file.
(and (file-readable-p filename)
(ebib--notes-open-single-note-file filename)))))
(when buf (cons buf (with-current-buffer buf (point))))))))
(when buf (cons buf (with-current-buffer buf (point))))))
((functionp ebib-notes-storage)
(let ((buffer (apply ebib-notes-storage :open-note (list key))))
(when buffer
(cons buffer (with-current-buffer buffer (point))))))))

(defun ebib--notes-create-new-note (key db)
"Create a note for KEY in DB.
Expand Down Expand Up @@ -405,7 +431,11 @@ the position where point should be placed."
(if (file-writable-p filename)
(setq buf (ebib--notes-open-single-note-file filename)
pos 1)
(error "[Ebib] Could not create note file `%s' " filename)))))
(error "[Ebib] Could not create note file `%s' " filename))))
((functionp ebib-notes-storage)
(let ((note-data (apply ebib-notes-storage :create-note (list key db))))
(setq buf (car note-data)
pos (or (cdr note-data) 1)))))
(let ((note (ebib--notes-fill-template key db)))
(with-current-buffer buf
(goto-char pos)
Expand Down
13 changes: 12 additions & 1 deletion manual/ebib.text
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,9 @@ The procedure for creating a new note depends on the setting of `ebib-notes-stor

If `ebib-notes-storage` is set to `multiple-notes-per-files`, Ebib won't create a new file when you create a new note. Instead, it will ask you for the file to save the note to and offer the files in `ebib-notes-locations` as candidates. If you don't want to be asked, you can set the option `ebib-notes-default-file`: new notes are then automatically stored to that file. You can subsequently use Org to move notes around or archive them.

New notes are created based on a template (`ebib-note-nemplate`). By default, the note is a top-level item with an Org headline consisting of the author(s), year and title of the entry. The entry also has a `:PROPERTIES:` block containing a custom ID for the entry, which consists of the entry key. If `ebib-notes-storage` is set to `multiple-notes-per-fil`, this custom ID is essential, because it is what Ebib uses to find the note. (If you use `one-file-per-note`, the file name is used to identify an entry, even though the custom ID is still included.)
Finally, if `ebib-notes-storage` is set to a function, Ebib will use an external package to manage notes, but will display notes and note status using the package. (See [External Notes Management](#external-notes-management) for more information.)

New notes are created based on a template (`ebib-note-nemplate`). By default, the note is a top-level item with an Org headline consisting of the author(s), year and title of the entry. The entry also has a `:PROPERTIES:` block containing a custom ID for the entry, which consists of the entry key. If `ebib-notes-storage` is set to `multiple-notes-per-file`, this custom ID is essential, because it is what Ebib uses to find the note. (If you use `one-file-per-note`, the file name is used to identify an entry, even though the custom ID is still included.)

The template can of course be customised. Details are discussed below.

Expand Down Expand Up @@ -1073,6 +1075,15 @@ Note that Ebib only searches for a single note for each entry, so if you create

Note that Ebib also provides an Ebib-friendly command to call `org-capture` directly. This command, `ebib-org-capture` (not bound to any key by default), takes the same arguments as `org-capture` and can be used in the same way. It makes sure that `ebib-notes-create-org-template` can find the current entry and then calls `org-capture`.

## External Notes Management ## {.unlisted .unnumbered}

External packages can provide functions which fulfill a simple interface to replace Ebib's note management, as `ebib-notes-storage`. These functions must arguments in the form `(ACTION &rest PARAMETERS)`. Known actions and their return values are as follows.

- `:has-note` takes a *key* and returns a generalized boolean determining if a note exists for the entry.
- `:create-note` takes a *key* and a *database* object, and returns a cons of `(BUFFER . POINT)` denoting the start of the note.
- `:open-note` takes a *key* and returns a buffer containing the note, with point at its start.

Example integration may be found in `ebib-citar.el`.

# Managing a reading list #

Expand Down