pkg update and first config fix

org-brain not working, add org-roam
This commit is contained in:
2022-12-19 23:02:34 +01:00
parent 02b3e07185
commit 82f05baffe
885 changed files with 356098 additions and 36993 deletions

View File

@@ -0,0 +1,396 @@
;;; orb-anystyle.el --- Orb Roam BibTeX: Elisp interface to Anystyle -*- lexical-binding: t -*-
;; Copyright © 2020-2022 Mykhailo Shevchuk
;; Author: Mykhailo Shevchuk <mail@mshevchuk.com>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; 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
;; this program; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;; N.B. This file contains code snippets adopted from other
;; open-source projects. These snippets are explicitly marked as such
;; in place. They are not subject to the above copyright and
;; authorship claims.
;;; Commentary:
;;
;;; Code:
;; * Library requires
(require 'orb-core)
(eval-when-compile
(require 'subr-x)
(require 'cl-macs))
;; * Customize definitions
(defcustom orb-anystyle-executable "anystyle"
"Anystyle executable path or program name."
:type '(choice (const "anystyle")
(file :tag "Path to executable" :must-match t))
:group 'orb-anystyle)
(defcustom orb-anystyle-pdfinfo-executable nil
"Path to pdfinfo executable to be passed to anystyle.
When this is nil, anystyle will look for it in the system path."
:type '(choice
(file :tag "Path to executable")
(const nil))
:group 'orb-anystyle)
(defcustom orb-anystyle-pdftotext-executable nil
"Path to pdftotext executable to be passed to anystyle.
When this is nil, anystyle will look for it in the system path."
:type '(choice
(file :tag "Path to executable")
(const nil))
:group 'orb-anystyle)
(defcustom orb-anystyle-parser-model nil
"Path to anystyle custom parser model."
:type '(choice
(file :tag "Path to file" :must-match t)
(const :tag "Built-in" nil))
:group 'orb-anystyle)
(defcustom orb-anystyle-finder-model nil
"Path to anystyle custom finder model."
:type '(choice
(file :tag "Path to file" :must-match t)
(const :tag "Built-in" nil))
:group 'orb-anystyle)
;; --crop is currently broken upstream
(defcustom orb-anystyle-find-crop nil
"Crop value in pt to be passed to `anystyle find'.
An integer or a conc cell of integers."
:type '(choice (integer :tag "Top and bottom")
(cons :tag "Top, bottom, left and right"
(integer :tag "Top and bottom")
(integer :tag "Left and right"))
(const :tag "Do not crop" nil))
:group 'orb-anystyle)
(defcustom orb-anystyle-find-solo nil
"Non-nil to pass the `--solo' flag."
:type '(choice (const :tag "Yes" t)
(const :tag "No" nil))
:group 'orb-anystyle)
(defcustom orb-anystyle-find-layout nil
"Non-nil to pass the `--layout' flag."
:type '(choice (const :tag "Yes" t)
(const :tag "No" nil))
:group 'orb-anystyle)
(defcustom orb-anystyle-default-buffer "*Orb Anystyle Output*"
"Default buffer name for anystyle output."
:type 'string
:group 'orb-anystyle)
(defcustom orb-anystyle-user-directory
(concat (file-name-as-directory user-emacs-directory) "anystyle")
"Directory to keep anystyle user files."
:type 'directory
:group 'orb-anystyle)
(defcustom orb-anystyle-parser-training-set
(concat (file-name-as-directory orb-anystyle-user-directory) "core.xml")
"XML file containing parser training data."
:type '(file :must-match t)
:group 'anystyle)
(defcustom orb-anystyle-finder-training-set
(f-join (file-name-as-directory orb-anystyle-user-directory) "ttx/")
"Directory containing finder training data (.ttx files)."
:type 'directory
:group 'anystyle)
;; * Main functions
;;;###autoload
(cl-defun orb-anystyle (command
&key (exec orb-anystyle-executable)
verbose help version adapter
((:finder-model fmodel) orb-anystyle-finder-model)
((:parser-model pmodel) orb-anystyle-parser-model)
(pdfinfo orb-anystyle-pdfinfo-executable)
(pdftotext orb-anystyle-pdftotext-executable)
format stdout overwrite
(crop orb-anystyle-find-crop)
(solo orb-anystyle-find-solo)
(layout orb-anystyle-find-layout)
input output
(buffer orb-anystyle-default-buffer))
"Run anystyle COMMAND with `shell-command'.
ARGS is a plist with the following recognized keys:
Anystyle CLI options
==========
1) EXEC :exec => string (valid executable)
- default value can be set through `orb-anystyle-executable'
2) COMMAND :command => symbol or string
- valid values: find parse help check license train
3) Global options can be passed with the following keys.
FMODEL :finder-model => string (valid file path)
PMODEL :parser-model => string (valid file path)
PDFINFO :pdfinfo => string (valid executable)
PDFTOTEXT :pdftotext => string (valid executable)
ADAPTER :adapter => anything
STDOUT :stdout => boolean
HELP :help => boolean
VERBOSE :verbose => boolean
VERSION :version => boolean
OVERWRITE :overwrite => boolean
FORMAT :format => string, symbol or list of unquoted symbols
- FORMAT must be one or more output formats accepted by anystyle commands:
parse => bib csl json ref txt xml
find => bib csl json ref txt ttx xml
- string must be space- or comma-separated, additional spaces are
ignored
Default values for some of these options can be set globally via
the following variables: `orb-anystyle-finder-model',
`orb-anystyle-parser-model', `orb-anystyle-pdfinfo-executable',
`orb-anystyle-pdftotext-executable'.
4) Command options can be passed with the following keys:
CROP :crop => integer or cons cell of integers
LAYOUT :layout => boolean
SOLO :solo => boolean
- Command options are ignored for commands other than find
- anystyle help -c flag is not supported
Default values for these options can be set globally via the
following variables: `orb-anystyle-find-crop',
`orb-anystyle-find-layout', `orb-anystyle-find-solo'.
5) INPUT :input => string (file path)
6) OUTPUT :output => string (file path)
`shell-command'-related options
==========
7) BUFFER :buffer => buffer-or-name
- `shell-command''s OUTPUT-BUFFER
- can be a cons cell (OUTPUT-BUFFER . ERROR-BUFFER)
- when nil, defaults to `orb-anystyle-default-buffer'
anystyle CLI command synopsis:
anystyle [global options] command [command options] [arguments...].
Homepage: https://anystyle.io
Github: https://github.com/inukshuk/anystyle-cli
Courtesy of its authors."
(declare (indent 1))
(let* ((commands '(list find parse check train help license))
(exec (executable-find exec))
(buf (if (consp buffer) buffer (list buffer)))
;; '(a b c) => "a,b,c"
(to-string (lambda (str)
(--reduce-from
(format "%s,%s" acc it)
(car str) (cdr str))))
;; debug
;; (anystyle-run (lambda (str)
;; (message "command: %s \nbuffers: %s and %s" str (car buf) (cdr buf))))
(anystyle-run (lambda (str)
(if (eq command 'train)
;; train can take minutes, so run it in a sub-process
(start-process-shell-command
"anystyle" (car buf) str)
(shell-command str
(car buf) (cdr buf)))))
global-options command-options anystyle)
;; executable is a must
(unless exec
(user-error "Anystyle executable not found! \
Install anystyle-cli before running Orb PDF Scrapper"))
;; we process :version and :help before checking command
;; since with this global flag command is not required
(cond
;; help flag takes priority
(help
(setq global-options " --help"
command-options ""
input nil
output nil))
;; anystyle ignores everything with --version flag except the
;; --help flag, which we've just resolved above
(version
(setq global-options "--version"
command nil
command-options ""
input nil
output nil))
;; otherwise command is a must
((not command)
(user-error "Anystyle command required: \
find, parse, check, train, help or license")))
(when (stringp command)
(setq command (intern command)))
;; command must be a valid command
(unless (memq command commands)
(user-error "Invalid command %s. Valid commands are \
find, parse, check, train, help and license" command))
;;
;; command specific arguments
(cl-case command
('help
(when (stringp input)
(setq input (intern input)))
(unless (or (and global-options
(string= global-options " --help"))
(memq input commands))
(user-error "Invalid input %s. Valid input for 'anystyle help': \
find, parse, check, train, help or license" input)))
('license
(setq input nil
output nil
global-options ""
command-options ""))
('check
(setq output nil))
('find
;; pdfinfo and pdftotext must be present in the system
(when (and pdfinfo (not (executable-find pdfinfo)))
(user-error "Executable not found: pdfinfo, %s" pdfinfo))
(when (and pdftotext (not (executable-find pdftotext)))
(user-error "Executable not found: pdftotext, %s" pdftotext))
(setq global-options
(orb-format "%s" global-options
" --pdfinfo=\"%s\"" pdfinfo
" --pdftotext=\"%s\"" pdftotext))
;; Command options
;; N.B. Help command accepts a command option -c but it's totally
;; irrelevant for us:
;;
;; [COMMAND OPTIONS]
;; -c - List commands one per line, to assist with shell completion
;; so we do not implement it
;;
;; :crop value should be integer; if no value was explicitly supplied,
;; use the default from `orb-anystyle-find-crop'
(when crop
(unless (consp crop)
(setq crop (list crop)))
(let ((x (car crop))
(y (or (cdr crop) 0)))
(unless (and (integerp x)
(integerp y))
(user-error "Invalid value %s,%y. Number expected" x y))
(setq crop (format "%s,%s" x y))))
;; parse only accepts --[no]-layout, so we ignore the rest
;; append command options to command
(setq command-options
(orb-format " --crop=%s" crop
" --layout" (cons layout " --no-layout")
" --solo" (cons solo " --no-solo"))))
('train
(unless output
(setq output
(concat (or (file-name-directory orb-anystyle-parser-training-set)
(file-name-as-directory orb-anystyle-user-directory))
"parser.mod")))))
;; Arguments relevant for more than one command
;;
;; find, parse:
;; format option should be one of accepted types if present
(when (and (memq command '(find parse))
format)
(when (stringp format)
(setq format
(-map #'intern
(split-string (string-trim format)
"[, ]" t " "))))
(unless (listp format)
(setq format (list format)))
(let ((accepted-formats
(cl-case command
('find '(bib csl json ref txt ttx xml))
('parse '(bib csl json ref txt xml)))))
(when (--none? (memq it accepted-formats) format)
(user-error
"Invalid format(s) %s. Valid formats for command %s: %s"
(funcall to-string format)
command
(funcall to-string accepted-formats)))
;; convert format to a comma-separated string and append
;; it to global options
(setq global-options
(orb-format "%s" global-options
" -f %s" (funcall to-string format)))))
;; find, parse, check accept
;; finder and parser models
(when (memq command '(find parse check))
(when (and fmodel (not (f-exists? fmodel)))
(display-warning 'org-roam-bibtex
"Finder model file not found: %s, \
using the default one" fmodel)
(setq fmodel nil))
(when (and pmodel (not (f-exists? pmodel)))
(display-warning 'org-roam-bibtex
"Finder model file not found: %s, \
using the default one" pmodel)
(setq pmodel nil))
(setq global-options (orb-format "%s" global-options
" -F \"%s\"" fmodel
" -P \"%s\"" pmodel)))
;; find, train, parse and check:
;; 1) require input, which should be a valid path
;; 2) something called ruby adapter, probably a right place here
;; 3) --verbose, --stdout, --overwrite if non-nil
(when (memq command '(find train parse check))
(unless input
(user-error "Input required for command %s" command))
(unless (and (stringp input) (f-exists? input))
(user-error "Invalid input file or directory %s" input))
(setq global-options
(orb-format
"%s" global-options
" --verbose" (cons verbose " --no-verbose")
;; this flag does nothing for check
" --stdout" (cons stdout " --no-stdout")
" --adapter=\"%s\"" adapter
" --overwrite" (cons overwrite " --no-overwrite"))))
;; Set arguments and run the program
;;
(setq anystyle (orb-format "%s" exec
"%s" global-options
" %s" command
"%s" command-options
" \"%s\"" (when input (file-truename input))
" \"%s\"" (when output (file-truename output))))
(funcall anystyle-run anystyle)))
(provide 'orb-anystyle)
;;; orb-anystyle.el ends here
;; Local Variables:
;; coding: utf-8
;; fill-column: 79
;; End:

View File

@@ -0,0 +1,48 @@
;;; org-roam-bibtex-compat.el --- Org Roam BibTeX: obsolete definitions -*- lexical-binding: t -*-
;; Copyright © 2020-2022 Mykhailo Shevchuk
;; Copyright © 2020 Leo Vivier
;; Author: Mykhailo Shevchuk <mail@mshevchuk.com>
;; Leo Vivier <leo.vivier+dev@gmail.com>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; 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
;; this program; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Obsolete definitions live here. For a while.
;;; Code:
;; * org-roam-bibtex.el
(define-obsolete-variable-alias
'orb-citekey-format 'orb-roam-ref-format "0.6.1")
(define-obsolete-variable-alias
'orb-file-field-extensions 'orb-attached-file-extensions "0.6.1")
(define-obsolete-function-alias
'orb-process-file-field 'orb-get-attached-file "0.6.1")
(provide 'orb-compat)
;;; orb-compat.el ends here
;; Local Variables:
;; coding: utf-8
;; fill-column: 79
;; End:

View File

@@ -0,0 +1,609 @@
;;; orb-core.el --- Org Roam BibTeX: core library -*- lexical-binding: t -*-
;; Copyright © 2020-2022 Mykhailo Shevchuk
;; Copyright © 2020 Leo Vivier
;; Author: Mykhailo Shevchuk <mail@mshevchuk.com>
;; Leo Vivier <leo.vivier+dev@gmail.com>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; 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
;; this program; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; This file provides org-roam-bibtex' dependencies and thus should
;; normally be required by org-roam-bibtex feature libraries. It
;; defines customize groups and provides general utility functions
;; that depend on extra features provided through org-roam,
;; bibtex-completion and their dependencies.
;;; Code:
;; ============================================================================
;;; Dependencies
;; ============================================================================
(require 'orb-utils)
(require 'orb-compat)
(eval-when-compile
(require 'cl-macs)
(require 'subr-x)
(require 'rx))
(declare-function
bibtex-completion-get-entry "bibtex-completion" (entry-key))
(declare-function
bibtex-completion-get-value "bibtex-completion" (field entry &optional default))
(declare-function
bibtex-completion-find-pdf (key-or-entry &optional find-additional))
;; ============================================================================
;;; Customize groups
;; ============================================================================
;;
;; All modules should put their `defgroup' definitions here
;; Defcustom definitions should stay in respective files
(defgroup org-roam-bibtex nil
"Org-roam integration with BibTeX software."
:group 'org-roam
:prefix "orb-")
(defgroup orb-note-actions nil
"Orb Note Actions - run actions in note's context."
:group 'org-roam-bibtex
:prefix "orb-note-actions-")
(defgroup orb-pdf-scrapper nil
"Orb PDF Scrapper - retrieve references from PDF."
:group 'org-roam-bibtex
:prefix "orb-pdf-scrapper-")
(defgroup orb-anystyle nil
"Elisp interface to `anystyle-cli`."
:group 'org-roam-bibtex
:prefix "orb-anystyle-")
(defgroup orb-autokey nil
"Automatic generation of BibTeX citation keys."
:group 'org-roam-bibtex
:prefix "orb-autokey-")
;; ============================================================================
;;; BibTeX fields and their special handling
;; ============================================================================
(defcustom orb-bibtex-field-aliases
'(("=type=" . "entry-type")
("=key=" . "citekey")
("=has-pdf=" . "pdf?")
("=has-note=" . "note?")
("citation-number" . "#"))
"Alist of ORB-specific field aliases of the form (FIELD . ALIAS).
The ALIAS can be used instead of the FIELD anywhere in ORB's
configuration. This variable is useful to replace
`bibtex-completion''s internal '='-embraced virtual fields with
more casual alternatives."
:group 'org-roam-bibtex
:type '(repeat
(cons (string :tag "Field name")
(string :tag "Alias name"))))
(defcustom orb-attached-file-extensions '("pdf")
"When retrieving an attached file, keep files with only these extensions.
This is a list of file extensions without a dot as case-insensitive
strings.
Set it to nil to keep all file names regardless of their extensions.
BibTeX entries are searched for attached files according to
`bibtex-completion-pdf-field' (default `file') and in
BibDesk-specific `Bdsk-File-N' fields."
:group 'org-roam-bibtex
:type '(repeat :tag "List of extensions" (string)))
(defcustom orb-abbreviate-file-name t
"Non-nil to force abbreviation of file names by `orb-get-attached-file'.
When this option is set to a non-nil value, the filename returned
by `orb-get-attached-file' will get the home directory part
abbreviated to `~/'. Symlinked directories will be abbreviated
according to `directory-abbrev-alist', see `abbreviate-file-name'
for details.
An as-is value will be used otherwise."
:group 'org-roam-bibtex
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil)))
(defcustom orb-open-attached-file-as-truename t
"Non-nil to open attached files with their true names.
When this option is set non-nil, `orb-open-attached-file' will
open files using their true names. You may want to set it to nil
if using file symlinks and experiencing problems such as
discussed here:
https://github.com/org-roam/org-roam-bibtex/issues/259
An as-is value will be used otherwise."
:group 'org-roam-bibtex
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil)))
(defcustom orb-use-bibdesk-attachments nil
"Whether to look up BibDesk-specific file fields `Bdsk-File'.
If this is non-nil, attachments given in BibDesk-specific file
fields will be considered in addition to those found through the
`bibtex-completion-find-pdf' mechanism when performing a template
expansion, opening an attachment with `orb-note-actions' or
scraping a PDF with `orb-pdf-scrapper'.
Duplicates will be resolved, but since duplicate comparison is
performed using `file-truename', this will lead to expansion of
symlink paths if such are used in the normal BibTeX `file' field,
for example. See also `orb-abbreviate-file-name' on how to
abbreviate the retrieved filenames.
Set this to symbol `only' to look up only BibDesk attachments and
do not use `bibtex-completion-find-pdf'."
:group 'org-roam-bibtex
:type '(choice
(const :tag "Yes" t)
(const :tag "BibDesk only" only)
(const :tag "No" nil)))
(defsubst orb-resolve-field-alias (alias)
"Return ALIAS association from `orb-bibtex-field-aliases'.
Return ALIAS if association was not found."
(or (car (rassoc alias orb-bibtex-field-aliases)) alias))
(defun orb-get-bibdesk-filenames (entry)
"Return filenames stored in BibDesk file fields \"Bdsk-File-N\".
ENTRY is a BibTeX entry as returned by `bibtex-completion-get-entry'.
The variable `orb-attached-file-extensions' is respected."
;; NOTE: Mac-specific, hard-coded
(let* ((bdsk-file-fields
(seq-filter (lambda (cell)
(string-match-p "Bdsk-File" (car cell)))
entry))
(strip-value-rx
(rx (seq (opt (in "\"{"))
(group (* (not (in "\"{}"))))
(opt (in "\"}")))))
(filename-rx
(concat
(rx (seq "Users/" (* anychar)))
(if orb-attached-file-extensions
(regexp-opt orb-attached-file-extensions t)
"pdf")))
(bdsk-files
(mapcar
(lambda (cell)
(let ((val (cdr cell))
file)
(when (string-match strip-value-rx val)
(setq file (base64-decode-string (match-string 1 val)))
(when (string-match filename-rx file)
(concat "/" (match-string 0 file))))))
bdsk-file-fields)))
(seq-filter (lambda (val) val) bdsk-files)))
;;;###autoload
(defun orb-get-attached-file (citekey)
"Look up files associated with a BibTeX entry identified by CITEKEY.
Files are searched for using `bibtex-completion-find-pdf',
meaning that Mendeley, Zotero and plain file paths are all
supported, and variables `bibtex-completion-pdf-field' and
`bibtex-completion-library-path' are respected. Additionally,
the BibTeX entry is searched for BibDesk-specific file fields
`Bdsk-File-N'.
If `orb-attached-file-extensions' is non-nil, return only file paths
matching the respective extensions.
If `orb-abbreviate-file-name' is non-nil, force an abbreviated
file name.
Depending on the value of `orb-use-bibdesk-attachments', the
BibDesk-specific file fields `Bdsk-File-N' may or may not be used
for the lookup.
If multiple files have been found, the user will be prompted to
select one."
(condition-case err
(when-let* ((entry (bibtex-completion-get-entry citekey))
(paths
(--> (pcase orb-use-bibdesk-attachments
(`nil (bibtex-completion-find-pdf
entry bibtex-completion-find-additional-pdfs))
(`only (orb-get-bibdesk-filenames entry))
(_
(-->
(nconc (bibtex-completion-find-pdf entry)
(orb-get-bibdesk-filenames entry))
(-map #'file-truename it)
(-uniq it))))
(if (not orb-attached-file-extensions)
it ; do not filter by extensions
;; filter by extensions
(--filter
(when-let ((ext (file-name-extension it)))
(member-ignore-case ext orb-attached-file-extensions))
it))))
(path (if (cdr paths)
(completing-read "File to use: " paths)
(car paths))))
(if orb-abbreviate-file-name
(abbreviate-file-name path)
path))
;; ignore any errors that may be thrown by `bibtex-completion-find-pdf'
;; don't stop the capture process
(error
(orb-warning
(format "error in `orb-get-attached-file`: %s %s"
(car err) (cdr err))))))
;;;###autoload
(defun orb-open-attached-file (citekey)
"Open a file associated with CITEKEY.
CITEKEY must be a list for compatibility with `bibtex-completion'
functions, which also expect a list.
This is a modified and simplified version of `bibtex-completion-open-pdf',
which uses `orb-get-bibdesk-filenames' under the hood and is therefore
compatible with BibDesk. The file is opened with the function set in
`bibtex-completion-pdf-open-function'.
The intended primary use is with `orb-note-actions'."
(let* ((key (car citekey))
(attachment (orb-get-attached-file key)))
(if attachment
(funcall bibtex-completion-pdf-open-function
(if orb-open-attached-file-as-truename
(file-truename attachment)
attachment))
(message "No PDF(s) found for this entry: %s" key))))
;; ============================================================================
;;;; Orb autokey
;; ============================================================================
(defcustom orb-autokey-format "%a%y%T[4][1]"
"Format string for automatically generated citation keys.
Supported wildcards:
Basic
==========
%a |author| - first author's (or editor's) last name
%t |title | - first word of title
%f{field} |field | - first word of arbitrary field
%y |year | - year YYYY
%p |page | - first page
%e{(expr)} |elisp | - execute elisp expression
Extended
==========
1. Capitalized versions:
%A |author| >
%T |title | > Same as %a,%t,%f{field} but
%F{field} |field | > preserve original capitalization
2. Starred versions
%a*, %A* |author| - include author's (editor's) initials
%t*, %T* |title | - do not ignore words in `orb-autokey-titlewords-ignore'
%y* |year | - year's last two digits __YY
%p* |page | - use \"pagetotal\" field instead of default \"pages\"
3. Optional parameters
%a[N][M][D] |author| >
%t[N][M][D] |title | > include first N words/names
%f{field}[N][M][D] |field | > include at most M first characters of word/name
%p[D] |page | > put delimiter D between words
N and M should be a single digit 1-9. Putting more digits or any
other symbols will lead to ignoring the optional parameter and
those following it altogether. D should be a single alphanumeric
symbol or one of `-_.:|'.
Optional parameters work both with capitalized and starred
versions where applicable.
4. Elisp expression
- can be anything
- should return a string or nil
- will be evaluated before expanding other wildcards and therefore
can insert other wildcards
- will have `entry' variable bound to the value of BibTeX entry the key
is being generated for, as returned by `bibtex-completion-get-entry'.
The variable may be safely manipulated in a destructive manner.
%e{(or (bibtex-completion-get-value \"volume\" entry) \"N/A\")}
%e{(my-function entry)}
Key generation is performed by `orb-autokey-generate-key'."
:risky t
:type 'string
:group 'org-roam-bibtex)
(defcustom orb-autokey-titlewords-ignore
'("A" "An" "On" "The" "Eine?" "Der" "Die" "Das"
"[^[:upper:]].*" ".*[^[:upper:][:lower:]0-9].*")
"Patterns from title that will be ignored during key generation.
Every element is a regular expression to match parts of the title
that should be ignored during automatic key generation. Case
sensitive."
;; Default value was take from `bibtex-autokey-titleword-ignore'.
:type '(repeat :tag "Regular expression" regexp)
:group 'orb-autokey)
(defcustom orb-autokey-empty-field-token "N/A"
"String to use when BibTeX field is nil or empty."
:type 'string
:group 'orb-autokey)
(defcustom orb-autokey-invalid-symbols
" \"'()={},~#%\\"
"Characters not allowed in a BibTeX key.
The key will be stripped of these characters."
:type 'string
:group 'orb-autokey)
(defun orb--autokey-format-field (field &rest specs)
"Return BibTeX FIELD formatted according to plist SPECS.
Recognized keys:
==========
:entry - BibTeX entry to use
:value - Value of BibTeX field to use
instead retrieving it from :entry
:capital - capitalized version
:starred - starred version
:words - first optional parameter (number of words)
:characters - second optional parameter (number of characters)
:delimiter - third optional parameter (delimiter)
All values should be strings, including those representing numbers.
This function is used internally by `orb-autokey-generate-key'."
(declare (indent 1))
(-let* (((&plist :entry entry
:value value
:capital capital
:starred starred
:words words
:characters chars
:delimiter delim) specs)
;; field values will be split into a list of words. `separator' is a
;; regexp for word separators: either a whitespace, one or more
;; dashes, or en dash, or em dash
(separator "\\([ \n\t]\\|[-]+\\|[—–]\\)")
(invalid-chars-rx
(rx-to-string `(any ,orb-autokey-invalid-symbols) t))
(delim (or delim ""))
result)
;; 0. virtual field "=name=" is used internally here and in
;; `orb-autokey-generate-key'; it stands for author or editor
(if (string= field "=name=")
;; in name fields, logical words are full names consisting of several
;; words and containing spaces and punctuation, separated by a logical
;; separator, the word "and"
(setq separator " and "
value (or value
(bibtex-completion-get-value "author" entry)
(bibtex-completion-get-value "editor" entry)))
;; otherwise proceed with value or get it from entry
(setq value (or value
(bibtex-completion-get-value field entry))))
(if (or (not value)
(string-empty-p value))
(setq result orb-autokey-empty-field-token)
(when (> (length value) 0)
(save-match-data
;; 1. split field into words
(setq result (split-string value separator t "[ ,.;:-]+"))
;; 1a) only for title;
;; STARRED = include words from `orb-autokey-titlewords-ignore
;; unstarred version filters the keywords, starred ignores this block
(when (and (string= field "title")
(not starred))
(let ((ignore-rx (concat "\\`\\(:?"
(mapconcat #'identity
orb-autokey-titlewords-ignore
"\\|") "\\)\\'"))
(words ()))
(setq result (dolist (word result (nreverse words))
(unless (string-match-p ignore-rx word)
(push word words))))))
;; 2. take number of words equal to WORDS if that is set
;; or just the first word; also 0 = 1.
(if words
(setq words (string-to-number words)
result (-take (if (> words (length result))
(length result)
words)
result))
(setq result (list (car result))))
;; 2a) only for "=name=" field, i.e. author or editor
;; STARRED = include initials
(when (string= field "=name=")
;; NOTE: here we expect name field 'Doe, J. B.'
;; should ideally be able to handle 'Doe, John M. Longname, Jr'
(let ((r-x (if starred
"[ ,.\t\n]"
"\\`\\(.*?\\),.*\\'"))
(rep (if starred "" "\\1"))
(words ()))
(setq result
(dolist (name result (nreverse words))
(push (s-replace-regexp r-x rep name) words)))))
;; 3. take at most CHARS number of characters from every word
(when chars
(let ((words ()))
(setq chars (string-to-number chars)
result (dolist (word result (nreverse words))
(push
(substring word 0
(if (< chars (length word))
chars
(length word)))
words)))))
;; 4. almost there: concatenate words, include DELIMiter
(setq result (mapconcat #'identity result delim))
;; 5. CAPITAL = preserve case
(unless capital
(setq result (downcase result))))))
;; return result stripped of the invalid characters
(s-replace-regexp invalid-chars-rx "" result t)))
(defun orb--autokey-evaluate-expression (expr &optional entry)
"Evaluate arbitrary elisp EXPR passed as readable string.
The expression will have value of ENTRY bound to `entry' variable
at its disposal. ENTRY should be a BibTeX entry as returned by
`bibtex-completion-get-entry'. The result returned should be a
string or nil."
(let ((result (eval `(let ((entry (quote ,(copy-tree entry))))
,(read expr)))))
(unless (or (stringp result)
(not result))
(user-error "Result: %s, invalid type. \
Expression must be string or nil" result))
(or result "")))
;;;###autoload
(defun orb-autokey-generate-key (entry &optional control-string)
"Generate citation key from ENTRY according to `orb-autokey-format'.
Return a string. If optional CONTROL-STRING is non-nil, use it
instead of `orb-autokey-format'."
(let* ((case-fold-search nil)
(str (or control-string orb-autokey-format))
;; star regexp: group 3!
(star '(opt (group-n 3 "*")))
;; optional parameters: regexp groups 4-6!
(opt1 '(opt (and "[" (opt (group-n 4 digit)) "]")))
(opt2 '(opt (and "[" (opt (group-n 5 digit)) "]")))
(opt3 '(opt (and "[" (opt (group-n 6 (any alnum "_.:|-"))) "]")))
;; capital letters: regexp group 2!
;; author wildcard regexp
(a-rx (macroexpand
`(rx (group-n 1 (or "%a" (group-n 2 "%A"))
,star ,opt1 ,opt2 ,opt3))))
;; title wildcard regexp
(t-rx (macroexpand
`(rx (group-n 1 (or "%t" (group-n 2 "%T"))
,star ,opt1 ,opt2 ,opt3))))
;; any field wildcard regexp
;; required parameter: group 7!
(f-rx (macroexpand
`(rx (group-n 1 (or "%f" (group-n 2 "%F"))
(and "{" (group-n 7 (1+ letter)) "}")
,opt1 ,opt2 ,opt3))))
;; year wildcard regexp
(y-rx (rx (group-n 1 "%y" (opt (group-n 3 "*")))))
;; page wildcard regexp
(p-rx (macroexpand `(rx (group-n 1 "%p" ,star ,opt3))))
;; elisp expression wildcard regexp
;; elisp sexp: group 8!
(e-rx (rx (group-n 1 "%e"
"{" (group-n 8 "(" (1+ ascii) ")") "}"))))
;; Evaluating elisp expression should go the first because it can produce
;; additional wildcards
(while (string-match e-rx str)
(setq str (replace-match
(save-match-data
(orb--autokey-evaluate-expression
(match-string 8 str) entry)) t nil str 1)))
;; Expanding all other wildcards are actually
;; variations of calls to `orb--autokey-format-field' with many
;; commonalities, so we wrap it into a macro
(cl-macrolet
((expand
(wildcard &key field value entry capital
starred words characters delimiter)
(let ((cap (or capital '(match-string 2 str)))
(star (or starred '(match-string 3 str)))
(opt1 (or words '(match-string 4 str)))
(opt2 (or characters '(match-string 5 str)))
(opt3 (or delimiter '(match-string 6 str))))
`(while (string-match ,wildcard str)
(setq str (replace-match
;; we can safely pass nil values
;; `orb--autokey-format-field' should
;; handle them correctly
(orb--autokey-format-field ,field
:entry ,entry :value ,value
:capital ,cap :starred ,star
:words ,opt1 :characters ,opt2 :delimiter ,opt3)
t nil str 1))))))
;; Handle author wildcards
(expand a-rx
:field "=name="
:value (or (bibtex-completion-get-value "author" entry)
(bibtex-completion-get-value "editor" entry)))
;; Handle title wildcards
(expand t-rx
:field "title"
:value (or (bibtex-completion-get-value "title" entry) ""))
;; Handle custom field wildcards
(expand f-rx
:field (match-string 7 str)
:entry entry)
;; Handle pages wildcards %p*[-]
(expand p-rx
:field (if (match-string 3 str)
"pagetotal" "pages")
:entry entry
:words "1"))
;; Handle year wildcards
;; it's simple, so we do not use `orb--autokey-format-field' here
;; year should be well-formed: YYYY
;; TODO: put year into cl-macrolet
(let ((year (or (bibtex-completion-get-value "year" entry)
(bibtex-completion-get-value "date" entry))))
(if (or (not year)
(string-empty-p year)
(string= year orb-autokey-empty-field-token))
(while (string-match y-rx str)
(setq str (replace-match orb-autokey-empty-field-token
t nil str 1)))
(while (string-match y-rx str)
(setq year (format "%04d" (string-to-number year))
str (replace-match
(format "%s" (if (match-string 3 str)
(substring year 2 4)
(substring year 0 4)))
t nil str 1)))))
str))
(provide 'orb-core)
;;; orb-core.el ends here
;; Local Variables:
;; coding: utf-8
;; fill-column: 79
;; End:

View File

@@ -0,0 +1,98 @@
;;; orb-helm.el --- ORB support form Helm -*- lexical-binding: t -*-
;; Copyright © 2020-2022 Mykhailo Shevchuk
;; Author: Mykhailo Shevchuk <mail@mshevchuk.com>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; Soft dependencies: projectile, persp-mode, helm, ivy, hydra
;; 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
;; this program; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
;; ============================================================================
;;;; Dependencies
;; ============================================================================
(require 'orb-utils)
(require 'helm-bibtex)
(require 'helm-source)
(declare-function orb-insert-edit-note "org-roam-bibtex" (citekey))
(defvar orb-note-actions-default)
(defvar orb-note-actions-extra)
(defvar orb-note-actions-user)
;; ============================================================================
;;;; Note actions
;; ============================================================================
(orb-note-actions-defun helm
(helm :sources
`(((name . ,name)
(candidates . ,candidates)
(action . (lambda (f)
(funcall f (list ,citekey))))))))
;; ============================================================================
;;;; Orb insert
;; ============================================================================
(defvar helm-source-orb-insert
(helm-build-sync-source "BibTeX entries"
:header-name (lambda (name)
(format "%s: " name))
:candidates 'helm-bibtex-candidates
:filtered-candidate-transformer 'helm-bibtex-candidates-formatter
:action (helm-make-actions
"Edit note & insert a link" 'helm-orb-insert-edit-note
"Open PDF, URL or DOI" 'helm-bibtex-open-any
"Open URL or DOI in browser" 'helm-bibtex-open-url-or-doi
"Insert citation" 'helm-bibtex-insert-citation
"Insert reference" 'helm-bibtex-insert-reference
"Insert BibTeX key" 'helm-bibtex-insert-key
"Insert BibTeX entry" 'helm-bibtex-insert-bibtex
"Attach PDF to email" 'helm-bibtex-add-PDF-attachment
"Show entry" 'helm-bibtex-show-entry
"Add PDF to library" 'helm-bibtex-add-pdf-to-library))
"Helm source to use with `orb-insert'.
A copy of `helm-source-bibtex', in which \"Edit notes\" is made
the first (default) action. This action calls `helm-orb-insert-edit-note'.
Only relevant when `orb-insert-interface' is `helm-bibtex'.")
(helm-bibtex-helmify-action orb-insert-edit-note helm-orb-insert-edit-note)
(defun orb-helm-insert (&optional clear-cache)
"Run `helm-bibtex'.
If optional CLEAR-CACHE is non-nil, re-create `bibtex-completion-cache'.
This is a simple wrapper to be run from `orb-insert'."
(let ((helm-source-bibtex helm-source-orb-insert))
(helm-bibtex clear-cache)))
(provide 'orb-helm)
;;; orb-helm.el ends here
;; Local Variables:
;; coding: utf-8
;; fill-column: 79
;; End:

View File

@@ -0,0 +1,97 @@
;;; orb-ivy.el --- ORB support for Ivy -*- lexical-binding: t -*-
;; Copyright © 2020-2022 Mykhailo Shevchuk
;; Author: Mykhailo Shevchuk <mail@mshevchuk.com>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; Soft dependencies: projectile, persp-mode, helm, ivy, hydra
;; 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
;; this program; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
;; ============================================================================
;;;; Dependencies
;; ============================================================================
(require 'orb-utils)
(require 'ivy-bibtex)
(declare-function orb-insert-edit-note "org-roam-bibtex" (citekey))
(defvar orb-note-actions-default)
(defvar orb-note-actions-extra)
(defvar orb-note-actions-user)
;; ============================================================================
;;;; Note actions
;; ============================================================================
(orb-note-actions-defun ivy
(ivy-read name
candidates
:require-match t
:caller 'orb-note-actions-ivy
:action (lambda (c)
(funcall (cdr c) (list citekey)))))
;; ============================================================================
;;;; Orb insert
;; ============================================================================
(defvar orb-insert--ivy-actions
'(("e" ivy-orb-insert-edit-note "Edit note & insert a link")
("p" ivy-bibtex-open-pdf "Open PDF file (if present)")
("u" ivy-bibtex-open-url-or-doi "Open URL or DOI in browser")
("c" ivy-bibtex-insert-citation "Insert citation")
("r" ivy-bibtex-insert-reference "Insert reference")
("k" ivy-bibtex-insert-key "Insert BibTeX key")
("b" ivy-bibtex-insert-bibtex "Insert BibTeX entry")
("a" ivy-bibtex-add-PDF-attachment "Attach PDF to email")
("s" ivy-bibtex-show-entry "Show entry")
("l" ivy-bibtex-add-pdf-to-library "Add PDF to library")
("f" (lambda (_candidate) (ivy-bibtex-fallback ivy-text)) "Fallback options"))
"Ivy actions to use with `orb-insert'.
A copy of Ivy-bibtex's alist defining Ivy actions, in which
\"Edit note & insert a link\" is made first (default) action.
This action calls `orb-insert-edit-note'. Only relevant when
`orb-insert-interface' is `ivy-bibtex'.")
(ivy-bibtex-ivify-action orb-insert-edit-note ivy-orb-insert-edit-note)
(defun orb-ivy-insert (&optional clear-cache)
"Run `ivy-bibtex'.
If optional CLEAR-CACHE is non-nil, re-create `bibtex-completion-cache'.
This is a simple wrapper to be run from `orb-insert'."
(let* ((ivy-actions (copy-tree ivy--actions-list))
(ivy--actions-list
(plist-put ivy-actions 'ivy-bibtex orb-insert--ivy-actions))
(ivy-bibtex-default-action 'ivy-orb-insert-edit-note))
(ivy-bibtex clear-cache)))
(provide 'orb-ivy)
;;; orb-ivy.el ends here
;; Local Variables:
;; coding: utf-8
;; fill-column: 79
;; End:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
(define-package "orb" "0.6.2"
"Org Roam meets BibTeX"
'((emacs "27.1")
(org-roam "2.2.0")
(bibtex-completion "2.0.0"))
:homepage "https://github.com/org-roam/org-roam-bibtex"
:keywords '("bib" "hypermedia" "outlines" "wp"))
;; Local Variables:
;; flycheck-disabled-checkers: (emacs-list-checkdoc)
;; package-lint-main-file: nil
;; End:

View File

@@ -0,0 +1,173 @@
;;; orb-section.el --- Org Roam BibTeX: Sections for org-roam-mode -*- lexical-binding: t -*-
;; Copyright © 2022 Samuel W. Flint
;; Author: Samuel W. Flint <swflint@flintfam.org>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; 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
;; this program; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; This file provides org-roam-bibtex' dependencies and thus should
;; normally be required by org-roam-bibtex feature libraries. It
;; defines customize groups and provides general utility functions
;; that depend on extra features provided through org-roam,
;; bibtex-completion and their dependencies.
;;; Code:
;; ============================================================================
;;; Dependencies
;; ============================================================================
(require 'org-roam-node)
(require 'org-roam-utils)
(require 's)
(require 'magit-section)
(require 'bibtex-completion)
(require 'orb-utils)
(defvar orb-abbreviate-file-name)
;; ============================================================================
;;; Configuration Variables
;; ============================================================================
(defgroup orb-section nil
"Org-roam buffer sections for BibTeX."
:group 'org-roam-bibtex
:prefix "orb-section-")
(defcustom orb-section-reference-format-method
'bibtex-completion-apa-format-reference
"How to format the ORB citation.
Either a function taking a bibtex key and returning a string, or
an alist from type to format string. For formatting information,
see `bibtex-completion-display-formats'."
:type '(choice (const :tag "Use BibTeX-Completion APA Format"
'bibtex-completion-apa-format-reference)
(symbol :tag "Use a function")
(alist :key-type (choice (string :tag "Type Name :")
(const :tag "Default" t))
:value-type (string :tag "Format String:"))))
(defcustom orb-section-abstract-format-method :org-format
"How to format ORB abstract.
A function taking a key and returning a string, or one of:
- `:org-format' Assume that the content is org-formatted, and
format accordingly.
- `:pandoc-from-tex' Assume that the content is tex/latex
formatted and use `pandoc' to format accordingly."
:type '(choice (const :tag "Format as Org Text" :org-format)
(const :tag "Format from LaTeX" :pandoc-from-tex)
(symbol :tag "Use function.")))
;; ============================================================================
;;; Utility functions
;; ============================================================================
(defun orb-section-reference-format (key)
"Format reference for KEY according to `orb-section-reference-format-method'."
(if (functionp orb-section-reference-format-method)
(funcall orb-section-reference-format-method key)
(when-let ((entry (bibtex-completion-get-entry key))
(format-string (cdr (or (assoc-string (bibtex-completion-get-value "=type=" entry) orb-section-reference-format-method)
(assoc t orb-section-reference-format-method))))
(formatted-reference (s-format format-string 'bibtex-completion-apa-get-value entry)))
(replace-regexp-in-string "\\([.?!]\\)\\." "\\1" formatted-reference))))
(defun orb-section-unfill-region (beg end)
"Unfill the region from BEG to END.
Joining text paragraphs into a single logical line.
Taken from https://www.emacswiki.org/emacs/UnfillRegion"
(interactive "*r")
(let ((fill-column (point-max)))
(fill-region beg end)))
(defun orb-section-abstract-format (key)
"Format abstract for KEY per `orb-section-abstract-format-method'."
(if (functionp orb-section-abstract-format-method)
(funcall orb-section-abstract-format-method key)
(when-let ((entry (bibtex-completion-get-entry key))
(abstract (bibtex-completion-get-value "abstract" entry)))
(pcase orb-section-abstract-format-method
(:org-format
(org-roam-fontify-like-in-org-mode
(with-temp-buffer
(insert abstract)
(org-mode)
(orb-section-unfill-region (point-min) (point-max))
(save-match-data "\n\n" "\n" (string-trim (buffer-string))))))
(:pandoc-from-tex
(org-roam-fontify-like-in-org-mode
(with-temp-buffer
(insert abstract)
(shell-command-on-region (point-min) (point-max)
"pandoc -f latex -t org" (current-buffer) t)
(org-mode)
(orb-section-unfill-region (point-min) (point-max))
(save-match-data "\n\n" "\n" (string-trim (buffer-string))))))))))
;; ============================================================================
;;; Section Implementation
;; ============================================================================
;;;###autoload
(defun orb-section-reference (node)
"Show BibTeX reference for NODE if it exists."
(when-let ((cite-key (orb-get-node-citekey node))
(formatted-reference (orb-section-reference-format cite-key)))
(magit-insert-section (orb-section-reference)
(magit-insert-heading "Reference:")
(insert formatted-reference)
(insert "\n\n"))))
;;;###autoload
(defun orb-section-abstract (node)
"Show BibTeX entry abstract for NODE if it exists."
(when-let ((cite-key (orb-get-node-citekey node))
(formatted-abstract (orb-section-abstract-format cite-key)))
(magit-insert-section (orb-section-abstract)
(magit-insert-heading "Abstract:")
(insert formatted-abstract)
(insert "\n\n"))))
;;;###autoload
(defun orb-section-file (node)
"Show a link to entry file for NODE if it exists."
(when-let ((cite-key (orb-get-node-citekey node))
(file-name (let ((orb-abbreviate-file-name nil))
(orb-get-attached-file cite-key))))
(magit-insert-section (orb-section-file)
(magit-insert-heading "File:")
(insert-text-button (file-name-nondirectory file-name)
'action (lambda (_button) (orb-open-attached-file cite-key)))
(insert "\n\n"))))
(provide 'orb-section)
;;; orb-section.el ends here
;;
;; Local Variables:
;; coding: utf-8
;; fill-column: 79
;; End:

View File

@@ -0,0 +1,382 @@
;;; orb-utils.el --- Org Roam BibTeX: utility macros and functions -*- lexical-binding: t -*-
;; Copyright © 2020-2022 Mykhailo Shevchuk
;; Copyright © 2020 Leo Vivier
;; Author: Mykhailo Shevchuk <mail@mshevchuk.com>
;; Leo Vivier <leo.vivier+dev@gmail.com>
;; URL: https://github.com/org-roam/org-roam-bibtex
;; 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
;; this program; see the file LICENSE. If not, visit
;; <https://www.gnu.org/licenses/>.
;; N.B. This file contains code snippets adopted from other
;; open-source projects. These snippets are explicitly marked as such
;; in place. They are not subject to the above copyright and
;; authorship claims.
;;; Commentary:
;;
;; This file contains utility macros and helper functions used accross
;; different org-mode-bibtex modules. This library may be required
;; directly or through orb-core.el. Definitions in this file should
;; only depend on built-in Emacs libraries.
;;; Code:
;; ============================================================================
;;;; Dependencies
;; ============================================================================
;;
;; org-roam requires org,org-element, dash, f, s, emacsql, emacsql-sqlite,
;; so all these libraries are always at our disposal
(require 'org-roam)
(require 'bibtex-completion)
(require 'warnings)
(eval-when-compile
(require 'subr-x))
(defvar org-ref-cite-types)
;; Adopted from `org-roam-version'
(defun orb-version (&optional message)
"Return `orb-roam' version.
Interactively, or when MESSAGE is non-nil, show in the echo area."
(interactive)
(let* ((toplib (or load-file-name buffer-file-name))
gitdir topdir version)
(unless (and toplib (equal (file-name-nondirectory toplib) "orb-utils.el"))
(setq toplib (locate-library "orb-utils.el")))
(setq toplib (and toplib (org-roam--straight-chase-links toplib)))
(when toplib
(setq topdir (file-name-directory toplib)
gitdir (expand-file-name ".git" topdir)))
(when (file-exists-p gitdir)
(setq version
(let ((default-directory topdir))
(shell-command-to-string
"git describe --tags --dirty --always"))))
(unless version
(setq version (with-temp-buffer
(insert-file-contents-literally
(locate-library "org-roam-bibtex.el"))
(goto-char (point-min))
(save-match-data
(if (re-search-forward
"\\(?:;; Version: \\([^z-a]*?$\\)\\)" nil nil)
(substring-no-properties (match-string 1))
"N/A")))))
(if (or message (called-interactively-p 'interactive))
(message "%s" version)
version)))
;; ============================================================================
;;;; Macros
;; ============================================================================
(defmacro orb--with-message! (message &rest body)
"Put MESSAGE before and after BODY.
Append \"...\" to the first message and \"...done\" to the second.
Return result of evaluating the BODY."
(declare (indent 1) (debug (stringp &rest form)))
(let ((reporter (gensym "orb")))
`(let ((,reporter (make-progress-reporter ,message)))
,@body
(progress-reporter-done ,reporter))))
(defmacro orb-note-actions-defun (interface &rest body)
"Return a function definition for INTERFACE.
Function name takes a form of orb-note-actions--INTERFACE. A
simple docstring is constructed and BODY is injected into a `let'
form, which has two variables bound, NAME and CANDIDATES. NAME
is a string formatted with `orb-format-entry' and CANDIDATES
is a cons cell alist constructed from `orb-note-actions-default',
`orb-note-actions-extra', and `orb-note-actions-user'."
(declare (indent 1) (debug (symbolp &rest form)))
(let* ((interface-name (symbol-name interface))
(fun-name (intern (concat "orb-note-actions-" interface-name))))
`(defun ,fun-name (citekey)
,(format "Provide note actions using %s interface.
CITEKEY is the citekey." (capitalize interface-name))
(let ((name (orb-format-entry citekey)) ;; TODO: make a native format function
(candidates
,(unless (eq interface 'hydra)
'(append orb-note-actions-default
orb-note-actions-extra
orb-note-actions-user))))
,@body))))
;; ============================================================================
;;;; General utilities
;; ============================================================================
(defun orb-warning (warning &optional citekey)
"Display a WARNING message. Return nil.
Include CITEKEY if it is non-nil."
(display-warning
:warning (concat "ORB: " (when citekey (format "%s :" citekey)) warning))
nil)
(defun orb-buffer-string (&optional start end)
"Retun buffer (sub)string with no text porperties.
Like `buffer-substring-no-properties' but START and END are
optional and equal to (`point-min') and (`point-max'),
respectively, if nil."
(buffer-substring-no-properties (or start (point-min))
(or end (point-max))))
(defun orb-format (&rest args)
"Format ARGS conditionally and return a string.
ARGS must be a plist, whose keys are `format' control strings and
values are `format' objects. Thus only one object per control
string is allowed. The result will be concatenated into a single
string.
In the simplest case, it behaves as a sort of interleaved `format':
==========
\(orb-format \"A: %s\" 'hello
\" B: %s\" 'world
\" C: %s\" \"!\")
=> 'A: hello B: world C: !'
If format object is nil, it will be formatted as empty string:
==========
\(orb-format \"A: %s\" 'hello
\" B: %s\" nil
\" C: %s\" \"!\")
=> 'A: hello C: !'
Object can also be a cons cell. If its car is nil then its cdr
will be treated as default value and formatted as \"%s\":
==========
\(orb-format \"A: %s\" 'hello
\" B: %s\" '(nil . dworl)
\" C: %s\" \"!\")
=> 'A: hellodworl C: !'
Finally, if the control string is nil, the object will be formatted as \"%s\":
==========
\(orb-format \"A: %s\" 'hello
\" B: %s\" '(nil . \" world\")
nil \"!\")
=> 'A: hello world!'."
(let ((res ""))
(while args
(let ((str (pop args))
(obj (pop args)))
(unless (consp obj)
(setq obj (cons obj nil)))
(setq res
(concat res
(format (or (and (car obj) str) "%s")
(or (car obj) (cdr obj) ""))))))
res))
;; ============================================================================
;;;; Temporary files
;; ============================================================================
;;;;; Code in this section was adopted from ob-core.el
;;
;; Copyright (C) 2009-2020 Free Software Foundation, Inc.
;;
;; Authors: Eric Schulte
;; Dan Davison
(defvar orb--temp-dir)
(unless (or noninteractive (boundp 'orb--temp-dir))
(defvar orb--temp-dir
(or (and (boundp 'orb--temp-dir)
(file-exists-p orb--temp-dir)
orb--temp-dir)
(make-temp-file "orb-" t))
"Directory to hold temporary files created during reference parsing.
Used by `orb-temp-file'. This directory will be removed on Emacs
shutdown."))
(defun orb-temp-file (prefix &optional suffix)
"Create a temporary file in the `orb--temp-dir'.
Passes PREFIX and SUFFIX directly to `make-temp-file' with the
value of variable `temporary-file-directory' temporarily set to
the value of `orb--temp-dir'."
(let ((temporary-file-directory
(or (and (boundp 'orb--temp-dir)
(file-exists-p orb--temp-dir)
orb--temp-dir)
temporary-file-directory)))
(make-temp-file prefix nil suffix)))
(defun orb--remove-temp-dir ()
"Remove `orb--temp-dir' on Emacs shutdown."
(when (and (boundp 'orb--temp-dir)
(file-exists-p orb--temp-dir))
;; taken from `delete-directory' in files.el
(condition-case nil
(progn
(mapc (lambda (file)
;; This test is equivalent to
;; (and (file-directory-p fn) (not (file-symlink-p fn)))
;; but more efficient
(if (eq t (car (file-attributes file)))
(delete-directory file)
(delete-file file)))
(directory-files orb--temp-dir 'full
directory-files-no-dot-files-regexp))
(delete-directory orb--temp-dir))
(error
(message "Failed to remove temporary Org-roam-bibtex directory %s"
(if (boundp 'orb--temp-dir)
orb--temp-dir
"[directory not defined]"))))))
(add-hook 'kill-emacs-hook 'orb--remove-temp-dir)
;;;;; End of code adopted from ob-core.el
;; ============================================================================
;;;; Document properties
;; ============================================================================
(defvar orb-utils-citekey-re
;; NOTE: Not tested thoroughly
(rx
(or
(seq (group-n 2 (regexp
;; If Org-ref is available, use its types
;; default to "cite"
(if (boundp 'org-ref-cite-types)
(regexp-opt
(mapcar
(lambda (el)
;; Org-ref v3 cite type is a list of strings
;; Org-ref v2 cite type is a plain string
(or (car-safe el) el))
org-ref-cite-types))
"cite")))
":"
(or
;; Org-ref v2 style `cite:links'
(group-n 1 (+ (any "a-zA-Z0-9_:.-")))
;; Org-ref v3 style `cite:Some&key'
(seq (*? (not "&")) "&"
(group-n 1 (+ (any "!#-+./:<>-@^-`{-~-" word))))))
;; Org-cite [cite/@citations]
(seq "@" (group-n 1 (+ (any "!#-+./:<>-@^-`{-~-" word))))))
"Universal regexp to match citations in `ROAM_REFS'.
Supports Org-ref v2 and v3 and Org-cite.")
(defun orb-get-db-cite-refs ()
"Get a list of `cite` refs from Org Roam database."
(let* ((types "cite")
(refs (org-roam-db-query
[:select [ref nodes:file id pos title type]
:from refs
:left-join nodes
:on (= refs:node-id nodes:id)
:where (= type $s1)]
types))
result)
(dolist (ref refs result)
(push (-interleave '(:ref :file :id :pos :title :type) ref) result))))
(defvar orb-notes-cache nil
"Cache of ORB notes.")
(defun orb-make-notes-cache ()
"Update ORB notes hash table `orb-notes-cache'."
(let* ((db-entries (orb-get-db-cite-refs))
(size (round (/ (length db-entries) 0.8125))) ;; ht oversize
(ht (make-hash-table :test #'equal :size size)))
(dolist (entry db-entries)
(puthash (plist-get entry :ref)
(org-roam-node-create
:id (plist-get entry :id)
:file (plist-get entry :file)
:title (plist-get entry :title)
:point (plist-get entry :pos))
ht))
(setq orb-notes-cache ht)))
(defun orb-find-note-file (citekey)
"Find note file associated with CITEKEY.
Returns the path to the note file, or nil if it doesnt exist."
(when-let ((node (gethash citekey (or orb-notes-cache
(orb-make-notes-cache)))))
(org-roam-node-file node)))
(defun orb-get-buffer-keyword (keyword &optional buffer)
"Return the value of Org KEYWORD in-buffer directive.
The KEYWORD should be given as a string without \"#+\", e.g. \"title\".
If optional BUFFER is non-nil, return the value from that buffer
instead of `current-buffer'."
;; NOTE: does not work with `org-element-multiple-keywords' keywords
;; if that will somewhen be required, `org-element' should be used.
(with-current-buffer (or buffer (current-buffer))
(let ((case-fold-search t))
(save-excursion
(goto-char (point-min))
(re-search-forward
(format "^[ ]*#\\+%s:[ ]*\\(.*\\)$" keyword) nil t)
(match-string-no-properties 1)))))
(defun orb-note-exists-p (citekey)
"Check if a note exists whose citekey is CITEKEY.
Return Org Roam node or nil."
;; NOTE: This function can be made more general.
(gethash citekey (or orb-notes-cache
(orb-make-notes-cache))))
(defun orb-get-node-citekey (&optional node assert)
"Return citation key associated with NODE.
If optional NODE is nil, return the citekey for node at point.
ASSERT will be passed to `org-roam-node-at-point'. If it is
non-nil, an error will be thrown if there is no node at point."
(when-let ((node (or node (org-roam-node-at-point assert)))
(node-refs (cdr (assoc-string
"ROAM_REFS"
(org-roam-node-properties node) t))))
(let* ((ref-list (split-string-and-unquote node-refs)))
(catch 'found
(dolist (ref ref-list)
(when (string-match orb-utils-citekey-re ref)
(throw 'found (match-string 1 ref))))))))
(defun orb-format-entry (citekey)
"Format a BibTeX entry for display, whose citation key is CITEKEY.
Uses `bibtex-completion-format-entry' internally and so the
display can be tweaked in the `bibtex-completion-display-formats'
variable."
;; NOTE: A drop-in replacement for `org-ref-format-entry' which was removed
;; in Org-ref v3. Still waiting for a native solution.
(bibtex-completion-init)
(bibtex-completion-format-entry
(bibtex-completion-get-entry citekey) (1- (frame-width))))
(provide 'orb-utils)
;;; orb-utils.el ends here
;; Local Variables:
;; coding: utf-8
;; fill-column: 79
;; End:

View File

@@ -0,0 +1,15 @@
(define-package "org-roam-bibtex" "20221104.2139" "Org Roam meets BibTeX"
'((emacs "27.1")
(org-roam "2.2.0")
(bibtex-completion "2.0.0"))
:commit "3810ddcd9d69ab27a40d5ba88b553df8db1b4884" :authors
'(("Mykhailo Shevchuk" . "mail@mshevchuk.com")
("Leo Vivier" . "leo.vivier+dev@gmail.com"))
:maintainer
'("Mykhailo Shevchuk" . "mail@mshevchuk.com")
:keywords
'("bib" "hypermedia" "outlines" "wp")
:url "https://github.com/org-roam/org-roam-bibtex")
;; Local Variables:
;; no-byte-compile: t
;; End:

File diff suppressed because it is too large Load Diff