From 63d7351317303e4f6a8690aca4633b0504e50b04 Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Wed, 5 Jan 2022 23:45:45 +0100 Subject: [PATCH 1/5] (int) refactor orb-pdf-scrapper and orb-autokey - Make them separate packages --- orb-anystyle.el | 396 ---------- orb-core.el | 334 +------- orb-pdf-scrapper.el | 1793 ------------------------------------------- org-roam-bibtex.el | 18 +- 4 files changed, 10 insertions(+), 2531 deletions(-) delete mode 100644 orb-anystyle.el delete mode 100644 orb-pdf-scrapper.el diff --git a/orb-anystyle.el b/orb-anystyle.el deleted file mode 100644 index 7dde042..0000000 --- a/orb-anystyle.el +++ /dev/null @@ -1,396 +0,0 @@ -;;; orb-anystyle.el --- Orb Roam BibTeX: Elisp interface to Anystyle -*- lexical-binding: t -*- - -;; Copyright © 2020-2022 Mykhailo Shevchuk - -;; Author: Mykhailo Shevchuk -;; 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 -;; . - -;; 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: diff --git a/orb-core.el b/orb-core.el index f8f2f8a..3d717b2 100644 --- a/orb-core.el +++ b/orb-core.el @@ -70,21 +70,6 @@ :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 @@ -139,7 +124,7 @@ 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'. +scraping a PDF with `refuse' (formerly `orb-pdf-scrapper'). Duplicates will be resolved, but since duplicate comparison is performed using `file-truename', this will lead to expansion of @@ -265,323 +250,6 @@ The intended primary use is with `orb-note-actions'." (funcall bibtex-completion-pdf-open-function (file-truename 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: diff --git a/orb-pdf-scrapper.el b/orb-pdf-scrapper.el deleted file mode 100644 index c973646..0000000 --- a/orb-pdf-scrapper.el +++ /dev/null @@ -1,1793 +0,0 @@ -;;; orb-pdf-scrapper.el --- Orb Roam BibTeX: PDF reference scrapper -*- lexical-binding: t -*- - -;; Copyright © 2020-2022 Mykhailo Shevchuk - -;; Author: Mykhailo Shevchuk -;; 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 -;; . - -;; 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: - -;; ============================================================================ -;;; Dependencies -;; ============================================================================ - -(require 'orb-core) -(require 'orb-anystyle) - -;; it's fine here since `orb-pdf-scrapper' is autoloaded -(require 'bibtex-completion) - -(require 'bibtex) -(require 'rx) -(require 'cl-extra) - -(eval-when-compile - (require 'cl-lib) - (require 'cl-macs) - (require 'subr-x)) - - -;; ============================================================================ -;;; Customize definitions -;; ============================================================================ - -(defcustom orb-pdf-scrapper-prompt-to-generate-keys 'when-buffer-modified - "Prompt the user to generate keys in the BibTeX buffer? -After having finished editing in the BibTeX buffer and before -proceeding to Org buffer, the user will be prompted -to (re-)generate citation keys according to the value of this -option on these occasions: - -- symbol `when-buffer-modified' - only when the buffer has - modified and changes have not been saved -- nil - never -- t or any other value - always." - :group 'orb-pdf-scrapper - :type '(choice - (const when-buffer-modified) - (const :tag "Yes" t) - (const :tag "No" nil))) - -(defcustom orb-pdf-scrapper-grouped-export - '((in-roam "In Org Roam database" list) - (in-bib "In BibTeX file" list) - (valid "Valid citation keys" table) - (invalid "Invalid citation keys" table)) - "Determines appearence of grouped references in Org view. -A list of five elements of the form (GROUP TITLE TYPE). - -GROUP must be one of the symbols `parent', `in-roam', `in-bib', -`valid' or `invalid'. - -TITLE is an arbitrary string, which will be the title of the -group's headline. - -TYPE must be one of the symbols `list' or `table' determining how -the generated citations will appear under the group's headline. -TYPE is ignored for the `parent' group and defaults to `list' for -other groups when set to nil. - -Takes effect when `orb-pdf-scrapper-group-references' is t." - :type '(list (list :tag "\nIn-roam" - (const :format "" in-roam) - (string :tag "Title") - (radio :tag "Type" :value list - (const list) (const table))) - (list :tag "\nIn-bib" - (const :format "" in-bib) - (string :tag "Title") - (radio :tag "Type" :value list - (const list) (const table))) - (list :tag "\nValid" - (const :format "" valid) - (string :tag "Title") - (radio :tag "Type" :value table - (const list) (const table))) - (list :tag "\nInvalid" - (const :format "" invalid) - (string :tag "Title") - (radio :tag "Type" :value table - (const list) (const table)))) - :group 'orb-pdf-scrapper) - -(defcustom orb-pdf-scrapper-ungrouped-export 'list - "Determines appearence of ungrouped references in Org view. -Valid values are the symbols `list' and `table'. - -Takes effect when `orb-pdf-scrapper-group-references' is nil." - :group 'orb-pdf-scrapper - :type '(radio - (const list) - (const table))) - -(defcustom orb-pdf-scrapper-table-export-fields - '("#" "citekey" "author" "editor" "journal" - "date" "volume" "pages") - "BibTeX fields for export into Org mode tables in the Org view. -A list in which each element is of form FIELD or (FIELD . TYPES). -The order of items in this list determines the order of table -columns. - -FIELD is a field to export. Field mapping according to the value -of `orb-bibtex-field-aliases' is recognized. The non-standard -BibTeX field `citation-number' created during the reference -extraction process is treated specially according to the value of -the variable `orb-pdf-scrapper-reference-numbers'. - -TYPES is a list of strings corresponding to BibTeX entry types -for which to export the FIELD. E.g. a value of a list cell - -\(\"editor\" . \"collection\") - -means to export the value of the field \"editor\" only for -entries whose entry type is \"collection\". If it is -nil (default), export the FIELD for all entry types. - -See also the variables `orb-pdf-scrapper-grouped-export' and -`orb-pdf-scrapper-ungrouped-export', which allow to choose between -list and table export." - :type '(repeat (string :tag "Field")) - :group 'orb-pdf-scrapper) - -(defcustom orb-pdf-scrapper-group-references t - "If this is non-nil, group the retrieved references in the Org view. -These groups are `in-roam', `in-bib', `valid' and `invalid'." - :group 'orb-pdf-scrapper - :type '(choice - (const :tag "Yes" t) - (const :tag "No" nil))) - -(defcustom orb-pdf-scrapper-list-style 'unordered-hyphen - "Format of Org lists produced by ORB PDF Scrapper. -Valid values are symbols: -`ordered-point' => 1. citation -`ordered-parenthesis' => 1) citation -`unordered-hyphen' => - citation -`unordered-plus' => + citation -`unordered-asterisk' => * citation - -This variable can also take a string value, in which case the -string should be a valid format string containing the '%s' -specifier, which will be used to insert citation numbers, for -example: - -\"- [%s] \" => - [1] citation - -The format string should take care of padding as no additional -spaces will be inserted automatically. - -The variable `orb-pdf-scrapper-reference-numbers' allows to -choose the numbering scheme for ordered and custom format lists." - :group 'orb-pdf-scrapper - :type - '(choice - (const :tag "Point-terminated ordered list" ordered-point) - (const :tag "Parenthesis-terminated ordered list" ordered-parenthesis) - (const :tag "Hyphen-led unordered list" unordered-hyphen) - (const :tag "Plus-led unordered list" unordered-plus) - (const :tag "Asterisk-led unordered list" unordered-asterisk) - (string :tag "Custom ordered format"))) - -(defcustom orb-pdf-scrapper-reference-numbers 'citation-number - "This variable specifies the source of ordered list numbers. -Valid values are: - -`citation-number' - use the extracted `citation-number', -stripping off any non-numerical characters. Fall back to -unordered hyphen-led list if this field is non-existent, empty, -or does not contain a number. - -`citation-number-alnum' - use the extracted `citation-number', -stripping off any non-alphanumerical characters, fall back to -unordered hyphen-led list if this field is non-existent, empty or -does not contain alphanumerical characters. This value will only -have effect when `orb-pdf-scrapper-list-style' is a custom format -string and behave as `citation-number' otherwise. - -`as-retrieved' - use the natural order of BibTeX entry in the buffer - -If the value of `orb-pdf-scrapper-list-style' is one of the -'unordered' choices, this variable will have no effect." - :group 'orb-pdf-scrapper - :type '(radio - (const :tag "Citation number" citation-number) - (const :tag "Alphanumerical citation number" citation-number-alnum) - (const :tag "As retrieved" as-retrieved))) - -(defcustom orb-pdf-scrapper-citekey-format "cite:%s" - "Format of the citekey for insertion into Org mode buffer." - :group 'orb-pdf-scrapper - :type 'string) - -(defcustom orb-pdf-scrapper-export-options - '((org (heading "References (extracted by ORB PDF Scrapper)" - :property-drawer (("PDF_SCRAPPER_TYPE") - ("PDF_SCRAPPER_SOURCE") - ("PDF_SCRAPPER_DATE"))))) - "Options for automatic export of references extracted by ORB PDF Scrapper. -This variable is an association list of the form -\(TYPE . ((TARGET LOCATION PROPERTIES))). - -TYPE is the type of the exported data, one of the symbols `txt', -`bib' or `org'. The data will only be exported if its -corresponding symbol is present on the list. - -TARGET must be one of the symbols `heading' or `path'. The -symbol `heading' means export the data under a heading in the -buffer of origin, the Org-mode buffer where the ORB PDF Scrapper -process was started. The symbol `path' means export the data to -another file. It is possible to specify both export targets -simultaneously for a given export TYPE or multiple targets of the -same type. - -Example: -\(setq orb-pdf-scrapper-export-options - '((org (heading HEADLINE PROPERTIES) - (path LOCATION PROPERTIES)))) - -LOCATION (HEADLINE) is a string specifying location of the TARGET. - -- If TARGET is `heading', the supplied string will be used as -headline text. The data will be exported slightly differently -depending on TYPE. Text references will be exported as is. -BibTeX references will be put into an Org-mode source code block. -Org-mode references, if grouped under different headings, will be -exported with the headings demoted by one level. - -- If TARGET is `path', the supplied string will be used as a -filesystem target. The path can be absolute or relative, in the -latter case it will be relative to the directory of the buffer of -origin. If the path (absolute or relative) is an *existing* -directory, the full path to the target file will be constructed -from the supplied string as a directory name, the #+ROAM_KEY: -property in the buffer of origin as a file name and TYPE as the -file's extension. If the path is not an existing directory, it -will be treated as a file name, and the data will be exported -there. The file will be created if it does not exist: - -> \"~/org/references.org\" - absolute path -> \"this-note's-bib-references.bib\" - path relative to the buffer of origin -> \"~/orb-pdf-scrapper-references/\" - absolute directory path. - -In the latter case, if the directory exists, the extracted data -will be put into a file \"Doe2020.bib\", assuming the #+ROAM_KEY: -property is \"Doe2020\" and TYPE is `bib'. If the directory does -not exist, the extracted data will be put into a (newly created) -file \"~/orb-pdf-scrapper-references/\". - -Example: -\(setq orb-pdf-scrapper-export-options - '((org (heading \"Org references\" PROPERTIES)) - (txt (path \"text-references/\" PROPERTIES)))) - -PROPERTIES is a property list providing additional export -specifications. Some properties are specific to only certain -export TYPEs or TARGETs. - -`:placement' property allows to specify placement of the exported -data. It can be a symbol `prepend' or `append'. - -When TARGET is `path', text data is simply put at the beginning -or end of the target file accordingly to the value of the -`:placement' property. Org-mode data is placed before the first -or after the last heading, respectively. Similarly, BibTeX data -is placed or before the first or after the last entry, comments -and @String entries ignored. - -When TARGET is `heading', this property specifies whether the -parent heading should be put before or after other headings. - -When TARGET is `path' and LOCATION is an Org-mode file, the value -of the `:placement' property can also be a list of the form -\(heading HEADLINE PROPERTIES). In this case the data will be -put in the target file under a heading with HEADLINE as the -headline text. PROPERTIES are additional export options as -described here and below. The `heading' value of the -`:placement' property cannot be used recursively in this case. - -Example: -\(setq orb-pdf-scrapper-export-options - '((org (path \"references.org\" :placement append)) - (bib (path \"references.bib\" :placement prepend)) - (txt (path \"references.txt\" - :placement (headline \"References\" :placement append))))) - -If placement is not specified, the data is appended by default. - -`:property-drawer' property allows to supply a heading with some -properties. The value of this property is list with -elements (PROPERTY_NAME . PROPERTY_VALUE) or PROPERTY_NAME, the -latter form being treated as (PROPERTY_NAME . nil). - -PROPERTY_NAME must be a string, it will be used as a property -name. PROPERY_VALUE can be a string, in which case it will be -used as the value of the property. It can also be a function -name as an unquoted symbol, in which case this function will be -called to get the value of the property. The return value must -be a string. - -The following properties are recognized internally and will be -supplied with automatically generated values if PROPERTY_VALUE is -nil: - -> PDF_SCRAPPER_TYPE - TYPE of the export data -> PDF_SCRAPPER_SOURCE - name of the PDF file the data were extracted from -> PDF_SCRAPPER_DATE - time and date the data were exported - -Example: -\(setq orb-pdf-scrapper-export-options - '((org (heading \"Org references\" PROPERTIES))) - (txt (heading \"Text references\" - :property-drawer - '((\"PDF_SCRAPPER_TYPE\" . \"text\") - \"PDF_SCRAPPER_DATE\" - \"PDF_SCRAPPER_SOURCE\" - (\"PROPERTY_1\" . \"VALUE_1\") - (\"PROPERTY_2\" . my-function)))))) - -`:filter-bib-entries' property controls filtering of exported -BibTeX entries. If the value of this property is non-nil and -TARGET is a BibTeX file, only the entries that are not already -present in this file will be exported. The value can also be a -string or a list of strings specifying BibTeX file(s), or a -variable as an unquoted symbol holding a string or a list of -strings specifying BibTeX file(s), in which cases the entries -will be filtered also against this/these file(s) in *addition* to -the TARGET file. In such instances, filtering will also be -applied to entries exported to an Org-mode heading. - -Example: -\(setq orb-pdf-scrapper-export-options - '((bib (path \"references.bib\" :filter-bib-entries t - (heading \"BibTeX references\" - :filter-bib-entries bibtex-completion-bibliography))))." - - :group 'orb-pdf-scrapper - :risky t - :type '(repeat list)) - -(defcustom orb-pdf-scrapper-set-fields - '(("author" orb-pdf-scrapper--invalidate-nil-value) - ("editor" orb-pdf-scrapper--invalidate-nil-value - "book" "collection") - ("title" orb-pdf-scrapper--invalidate-nil-value) - ("journal" orb-pdf-scrapper--invalidate-nil-value - "article") - ("date" orb-pdf-scrapper--invalidate-nil-value) - ("volume" orb-pdf-scrapper--invalidate-nil-value - "article" "incollection") - ("pages" orb-pdf-scrapper--fix-or-invalidate-range - "article" "incollection")) - "BibTeX fields to set during key generation. -A list in which each element is a list of the form (FIELD FUNCTION . TYPES). - -FIELD is a BibTeX field name to be set. - -FUNCTION is a function that will be called to generate the value, -it takes one argument ENTRY, which is the current entry. - -TYPES is a list of strings corresponding to BibTeX entry types -for which the FIELD should be set. If it is nil, set the FIELD -for all entry types." - :risky t - :type '(repeat - (list :tag "Item" - (string :tag "Field") - (function :tag "Function") - (repeat :tag "Entry types" :inline t - (string :tag "Type")))) - :group 'orb-pdf-scrapper) - -(defcustom orb-pdf-scrapper-invalid-key-pattern "\\`.*N/A.*\\'" - "Regexp to match an invalid key." - :type 'regexp - :group 'orb-pdf-scrapper) - - -;; ============================================================================ -;;; Helper functions: BibTeX buffer and citekey-related routines -;; ============================================================================ - -(defvar orb-pdf-scrapper--refs nil - "Internal list with cell format (CITEKEY ENTRY . VALIDP).") - -(defun orb-pdf-scrapper--invalidate-nil-value (field entry) - "Return value of FIELD or `orb-autokey-empty-field-token' if it is nil. -ENTRY is a BibTeX entry." - (bibtex-completion-get-value field entry orb-autokey-empty-field-token)) - -(defun orb-pdf-scrapper--fix-or-invalidate-range (field entry) - "Replace missing or non-standard delimiter between two strings with \"--\". -FIELD is the name of a BibTeX field from ENTRY. Return -`orb-autokey-empty-field-token' if the value is nil. - -This function is primarily intended for fixing anystyle parsing -artefacts such as those often encountered in \"pages\" field, -where two numbers have only spaces between them." - (replace-regexp-in-string "\\`[[:alnum:]]*?\\([- –]+\\)[[:alnum:]]*\\'" - "--" - (bibtex-completion-get-value - field entry orb-autokey-empty-field-token) - nil nil 1)) - -(defun orb-pdf-scrapper--get-entry-info (entry &optional collect-only) - "Collect some information from and about the BibTeX ENTRY for further use. -Take a bibtex entry as returned by `bibtex-completion-get-entry' \ -and return a plist with the following keys set: - -:key |string | citekey generated with `orb-autokey-generate-key' -:validp |boolean| according to `orb-pdf-scrapper-invalid-key-pattern' -:set-fields |(cons) | as per `orb-pdf-scrapper-set-fields' - -Each element of `:set-fields' list is a a cons cell (FIELD . VALUE). - -If optional COLLECT-ONLY is non-nil, do not generate the key, -`:set-fields' is set to nil." - (let ((type (bibtex-completion-get-value "=type=" entry)) - ;; return values - key validp fields-to-set - ;; internal variable - fields) - ;; when requested to collect keys, just do that - (if collect-only - (setq key (bibtex-completion-get-value "=key=" entry) - fields entry) - ;; otherwise - ;; prepare fields for setting - (dolist (field-to-set orb-pdf-scrapper-set-fields) - (let ((field-name (car field-to-set)) - (types-to-set (cddr field-to-set))) - ;; push the field for setting only when entry type is one of the - ;; specified types or nil, which means set the field regardless of - ;; entry type - (when (or (not types-to-set) - (member type types-to-set)) - (push (cons field-name - ;; call the function if provided - (if-let ((fn (cadr field-to-set))) - (funcall fn field-name entry) - ;; otherwise get the value from current entry - (bibtex-completion-get-value field-name entry ""))) - fields-to-set)))) - ;; prioritize fields from fields-to-set over entry fields - ;; for autokey generation - (let ((-compare-fn (lambda (x y) - (string= (car x) (car y))))) - (setq fields (-union fields-to-set entry) - key (orb-autokey-generate-key fields)))) - ;; validate the new shiny key (or the old existing one) - ;; not sure if save-match-data is needed here - ;; but it seems to be always a good choice - (save-match-data - (setq validp (and (not (string-match-p - orb-pdf-scrapper-invalid-key-pattern key)) - t))) - ;; return the entry - (list :key key - :validp validp - :set-fields fields-to-set))) - -(defun orb-pdf-scrapper--update-record-at-point - (&optional collect-only natural-order) - "Generate citation key and update the BibTeX record at point. -Calls `orb-pdf-scrapper--get-entry-info' to get information about -BibTeX record at point and updates it accordingly. If optional -COLLECT-ONLY is non-nil, do not generate the key and do not set -the fields. - -If optional argument NATURAL-ORDER is non-nil, set the field -'natural-order' of the returned entry to its value. - -This is an auxiliary function for command -`orb-pdf-scrapper-generate-keys'." - (let* ((entry (parsebib-read-entry (parsebib-find-next-item))) - (key-plist (orb-pdf-scrapper--get-entry-info entry collect-only)) - (new-key (plist-get key-plist :key)) - (validp (plist-get key-plist :validp)) - (fields-to-set (plist-get key-plist :set-fields))) - (unless collect-only - (save-excursion - ;; update citekey - ;; adjusted from bibtex-clean-entry - (bibtex-beginning-of-entry) - (re-search-forward bibtex-entry-maybe-empty-head) - (if (match-beginning bibtex-key-in-head) - (delete-region (match-beginning bibtex-key-in-head) - (match-end bibtex-key-in-head))) - (insert new-key) - ;; set the bibtex fields - (when fields-to-set - (save-excursion - (dolist (field fields-to-set) - (let ((name (car field)) - (value (cdr field)) - bound) - ;; (bibtex-set-field name value) - (bibtex-beginning-of-entry) - (when (setq bound (bibtex-search-forward-field name t)) - (goto-char (car (cdr bound))) - (bibtex-kill-field nil t)) - (bibtex-make-field (list name "" value) t t))) - ;; Some hard-set cosmetic changes - (let ((bibtex-entry-format - '(whitespace realign unify-case braces sort-fields)) - (bibtex-field-indentation 2) - (fill-column 160)) - (bibtex-clean-entry)))))) - ;; return the result ((NEW-KEY . ENTRY) . VALIDP) - ;; TODO: for testing until implemented - (when natural-order - (cl-pushnew `("natural-order" . ,natural-order) entry)) - (cons new-key (cons entry validp)))) - -(defun orb-pdf-scrapper--sort-refs (refs) - "Sort references REFS. -Auxiliary function for `orb-pdf-scrapper-generate-keys'. -REFS should be an alist of form ((CITEKEY . FORMATTED-ENTRY) . VALIDP). - -References validated by `orb-pdf-scrapper-keygen-function' function -are further sorted into four groups: - -'in-roam - available in the `org-roam' database; -'in-bib - available in `bibtex-completion-bibliography' file(s); -'valid - marked valid by the keygen function but are not - available in user database(s); -'invalid - marked invalid by the keygen function." - (let* ((bibtex-completion-bibliography (orb-pdf-scrapper--get :global-bib)) - ;; When using a quoted list here, sorted-refs is not erased in - ;; consecutive runs - (sorted-refs (list (list 'in-roam) (list 'in-bib) - (list 'valid) (list 'invalid)))) - (dolist (ref refs) - (cond ((org-roam-db-query [:select [ref] - :from refs - :where (= ref $s1)] - (format "%s" (car ref))) - (push ref (cdr (assoc 'in-roam sorted-refs)))) - ((bibtex-completion-get-entry (car ref)) - (push ref (cdr (assoc 'in-bib sorted-refs)))) - ((cddr ref) - (push ref (cdr (assoc 'valid sorted-refs)))) - (t - (push ref (cdr (assoc 'invalid sorted-refs)))))) - sorted-refs)) - - -;; ============================================================================ -;;; Helper functions: Org buffer-related routines -;; ============================================================================ - -(defun orb-pdf-scrapper--get-reference-number - (entry &optional numbering-source) - "ENTRY NUMBERING-SOURCE." - (let ((numbering-source - (or numbering-source orb-pdf-scrapper-reference-numbers))) - (cl-case numbering-source - (citation-number - (--> (bibtex-completion-get-value "citation-number" entry nil) - (when (and it (string-match ".*?\\([0-9]+\\).*?" it)) - (match-string 1 it)))) - (citation-number-alnum - (--> (bibtex-completion-get-value "citation-number" entry nil) - (when (and it (string-match "\ -[^[:alnum:]]?\\([[:digit:]]*\\)\\([^[:alnum:]]*\\)\\([[:alpha:]]*\\)" it)) - (concat (match-string 1 it) (match-string 3 it))))) - (as-retrieved - (bibtex-completion-get-value "natural-order" entry "")) - (t (user-error "Unsupported reference numbers source: %s" - numbering-source))))) - -(defun orb-pdf-scrapper--insert-org-as-list (ref-alist) - "Insert REF-ALIST as Org-mode list." - (let* ((numbering-source - (if (and (eq orb-pdf-scrapper-reference-numbers - 'citation-number-alnum) - (not (stringp orb-pdf-scrapper-list-style))) - 'citation-number - orb-pdf-scrapper-reference-numbers)) - (leader - (cl-case orb-pdf-scrapper-list-style - (ordered-point "%s. ") - (ordered-parenthesis "%s) ") - (unordered-hyphen "- ") - (unordered-plus "+ ") - (unordered-asterisk "* ") - (t (if (stringp orb-pdf-scrapper-list-style) - orb-pdf-scrapper-list-style - (user-error "ORB: Unrecognized list style %s requested" - orb-pdf-scrapper-list-style))))) - (unorderedp - (memq orb-pdf-scrapper-list-style - '(unordered-hyphen unordered-plus unordered-asterisk))) - (fallback (if unorderedp leader "- "))) - (dolist (ref ref-alist) - (let* ((citekey (format orb-pdf-scrapper-citekey-format (car ref))) - (entry (cadr ref)) - (number (unless unorderedp - (orb-pdf-scrapper--get-reference-number - entry numbering-source)))) - (insert (orb-format leader `(,number . ,fallback)) citekey "\n"))))) - -(defun orb-pdf-scrapper--get-export-value (field entry) - "Get FIELD value from ENTRY. -Similar to `bibtex-completion-get-value' but does some additional cleaning." - ;; list fields for org export - (let* ((field (or (car (rassoc field orb-bibtex-field-aliases)) - field)) - (value (bibtex-completion-get-value field entry ""))) - ;; truncate author list to first three names, append et.al instead - ;; of the remaining names - ;; This is a hard-coded "reasonable default" - ;; and it may be replaced with something more - ;; flexible in the future - (cond - ((member field '("author" "editor")) - (--> value - (split-string it " and " t "[ ,.;:-]+") - (if (> (length it) 3) - (append (-take 3 it) '("et.al.")) - it) - (concat (mapconcat #'identity it "; ")))) - ((string= field "citation-number") - (orb-pdf-scrapper--get-reference-number entry)) - ((string= field "=key=") - (format orb-pdf-scrapper-citekey-format value)) - (t value)))) - -(defun orb-pdf-scrapper--insert-org-as-table (ref-alist) - "Insert REF-ALIST as Org-mode table." - (insert - (format "|%s\n" (mapconcat #'identity - orb-pdf-scrapper-table-export-fields "|"))) - (forward-line -1) - (org-table-insert-hline) - (forward-line 2) - (let ((table "")) - (dolist (ref ref-alist) - (setq table - (format "%s|%s|\n" table - (mapconcat - (lambda (field) - (orb-pdf-scrapper--get-export-value field (cadr ref))) - orb-pdf-scrapper-table-export-fields "|")))) - (insert table)) - (forward-line -1) - (org-table-align)) - -(defun orb-pdf-scrapper--insert-refs () - "Insert the references list as org structure. -If `orb-pdf-scrapper-group-references' is non-nil, sort the references into -categories `in-roam', `in-bib', `valid', `invalid'. Make a plain -list otherwise." - (cond - (orb-pdf-scrapper-group-references - (dolist (ref-group - (orb-pdf-scrapper--sort-refs orb-pdf-scrapper--refs)) - (when-let* ((group (car ref-group)) - (refs (cdr ref-group)) - (heading - (cdr (assoc group - orb-pdf-scrapper-grouped-export))) - (title (car heading)) - (type (cadr heading)) - (pos (make-marker))) - ;; NOTE: Investigate - ;; The behaviour of org-insert-heading has changed at some point: - ;; If in an empty buffer, e.g. temp-buffer, the function fails messaging "beginning of buffer" - (org-N-empty-lines-before-current 1) - (org-insert-heading '(16) nil t) - ;; insert heading - (insert (format "%s\n" title)) - (org-N-empty-lines-before-current 1) - ;; insert references - (insert (format "#+name: %s\n" group)) - (set-marker pos (point)) - (set-marker-insertion-type pos t) - (cl-case type - ('table - (orb-pdf-scrapper--insert-org-as-table refs)) - (t - (orb-pdf-scrapper--insert-org-as-list refs))) - (goto-char pos)))) - (t - (insert "\n") - (let ((refs (nreverse orb-pdf-scrapper--refs))) - (cl-case orb-pdf-scrapper-ungrouped-export - ('table - (orb-pdf-scrapper--insert-org-as-table refs)) - (t - (orb-pdf-scrapper--insert-org-as-list refs)))))) - (goto-char (point-max)) - (org-N-empty-lines-before-current 0)) - - -;; ============================================================================ -;;; Helper functions: Dispatcher -;; ============================================================================ - -(defvar orb-pdf-scrapper--plist nil - "Communication channel for Orb PDF Scrapper.") - -(defvar orb-pdf-scrapper--buffer "*Orb PDF Scrapper*" - "Orb PDF Scrapper special buffer.") - -(defmacro orb--with-scrapper-buffer! (&rest body) - "Execute BODY with `orb-pdf-scrapper--buffer' as current. -If the buffer does not exist it will be created." - (declare (indent 0) (debug t)) - `(save-current-buffer - (set-buffer (get-buffer-create orb-pdf-scrapper--buffer)) - ,@body)) - -(defmacro orb--when-current-context! (context &rest body) - "Execute BODY if CONTEXT is current context. -Run `orb-pdf-scrapper-keygen-function' with `error' context -otherwise. If CONTEXT is a list then current context must be a -member of that list." - (declare (indent 1) (debug t)) - `(if (not (orb-pdf-scrapper--current-context-p ,context)) - (orb-pdf-scrapper-dispatcher 'error) - ,@body)) - -(defun orb-pdf-scrapper--current-context-p (context) - "Return t if CONTEXT is current context. -CONTEXT can also be a list, in which case t is returned when -current context is its memeber." - (if (listp context) - (memq (orb-pdf-scrapper--get :context) context) - (eq (orb-pdf-scrapper--get :context) context))) - -(defun orb-pdf-scrapper--refresh-mode (mode) - "Restart `orb-pdf-scrapper-mode' with new major MODE." - (cl-case mode - ('txt - (text-mode) - (orb-pdf-scrapper--put :callee 'edit-bib - :context 'start - :caller 'edit-txt)) - ('bib - (bibtex-mode) - ;; anystyle uses biblatex dialect - (bibtex-set-dialect 'biblatex t) - (orb-pdf-scrapper--put :callee 'edit-org - :context 'start - :caller 'edit-bib)) - ('org - (org-mode) - (orb-pdf-scrapper--put :callee 'checkout - :context 'start - :caller 'edit-org)) - ('xml - (xml-mode) - (cl-case (orb-pdf-scrapper--get :context) - ;; since :callee is not used in training session, we set :callee here to - ;; the original :caller, so that we can return to the editing mode we - ;; were called from if the training session is to be cancelled - ('start - (orb-pdf-scrapper--put :callee (orb-pdf-scrapper--get :caller) - :context 'edit - :caller 'edit-xml)))) - ('train - (fundamental-mode) - (cl-case (orb-pdf-scrapper--get :context) - ('train - (orb-pdf-scrapper--put :context 'train - :caller 'train)) - ;; Since the session was not cancelled, we return to text, as everything - ;; else should be regenerated anyway. - ('finished - (orb-pdf-scrapper--put :callee 'edit-txt - :context 'continue - :caller 'train)))) - (t - (unwind-protect - (error "Oops...something went wrong. \ -Pressing the RED button, just in case") - (orb-pdf-scrapper-dispatcher 'error)))) - (set-buffer-modified-p nil) - (setq mark-active nil) - (orb-pdf-scrapper-mode -1) - (orb-pdf-scrapper-mode +1) - (goto-char (point-min))) - -(defun orb-pdf-scrapper--edit-txt () - "Edit text references in `orb-pdf-scrapper--buffer'." - ;; callee will be overridden in case of error - (cl-case (orb-pdf-scrapper--get :context) - ;; parse pdf file and switch to text editing mode - ('start - (let ((temp-txt (orb-temp-file "orb-pdf-scrapper-" ".txt")) - (pdf-file (orb-pdf-scrapper--get :pdf-file))) - (orb-pdf-scrapper--put :temp-txt temp-txt) - (let ((same-window-buffer-names (list orb-pdf-scrapper--buffer))) - (pop-to-buffer orb-pdf-scrapper--buffer)) - (setq buffer-file-name nil) - (orb--with-message! (format "Scrapping %s.pdf" (f-base pdf-file)) - (erase-buffer) - (orb-anystyle 'find - :format 'ref - :layout nil - :finder-model orb-anystyle-finder-model - :input pdf-file - :stdout t - :buffer orb-pdf-scrapper--buffer)) - (setq buffer-undo-list nil) - (orb-pdf-scrapper--refresh-mode 'txt))) - ;; read the previously generated text file - ('continue - (if-let ((temp-txt (orb-pdf-scrapper--get :temp-txt)) - (f-exists? temp-txt)) - (progn - (pop-to-buffer orb-pdf-scrapper--buffer) - (erase-buffer) - (insert-file-contents temp-txt) - (setq buffer-undo-list (orb-pdf-scrapper--get :txt-undo-list)) - (orb-pdf-scrapper--refresh-mode 'txt)) - (orb-pdf-scrapper-dispatcher 'error))) - (t - (orb-pdf-scrapper-dispatcher 'error)))) - -(defun orb-pdf-scrapper--edit-bib () - "Generate and edit BibTeX data in `orb-pdf-scrapper--buffer'." - (pop-to-buffer orb-pdf-scrapper--buffer) - (cl-case (orb-pdf-scrapper--get :context) - ('start - (let* ((temp-bib (or (orb-pdf-scrapper--get :temp-bib) - (orb-temp-file "orb-pdf-scrapper-" ".bib")))) - (orb-pdf-scrapper--put :temp-bib temp-bib) - ;; save previous progress in txt buffer - (write-region (orb-buffer-string) - nil (orb-pdf-scrapper--get :temp-txt) nil -1) - (orb-pdf-scrapper--put :txt-undo-list (copy-tree buffer-undo-list)) - (orb--with-message! "Generating BibTeX data" - ;; Starting from Emacs 27, whether shell-command erases buffer - ;; is controlled by `shell-command-dont-erase-buffer', so we - ;; make sure the buffer is clean - (erase-buffer) - (orb-anystyle 'parse - :format 'bib - :parser-model orb-anystyle-parser-model - :input (orb-pdf-scrapper--get :temp-txt) - :stdout t - :buffer orb-pdf-scrapper--buffer) - (write-region (orb-buffer-string) nil temp-bib nil -1)) - (setq buffer-undo-list nil)) - (orb-pdf-scrapper--refresh-mode 'bib)) - ('continue - (if-let ((temp-bib (orb-pdf-scrapper--get :temp-bib)) - (f-exists? temp-bib)) - (progn - (erase-buffer) - (insert-file-contents temp-bib) - (setq buffer-undo-list (orb-pdf-scrapper--get :bib-undo-list)) - (orb-pdf-scrapper--refresh-mode 'bib)) - (orb-pdf-scrapper-dispatcher 'error))) - (t - (orb-pdf-scrapper-dispatcher 'error)))) - -(defun orb-pdf-scrapper--edit-org () - "Edit generated Org-mode data." - (pop-to-buffer orb-pdf-scrapper--buffer) - (cl-case (orb-pdf-scrapper--get :context) - ('start - ;; if the BibTeX buffer was modified, save it and maybe generate keys - (orb-pdf-scrapper-generate-keys - nil - (cl-case orb-pdf-scrapper-prompt-to-generate-keys - ('when-buffer-modified - (if (buffer-modified-p) - ;; TODO: it's clumsy - ;; not "yes" means generate - ;; not "no" means collect only - (not (y-or-n-p "The buffer contents has changed. \ -Generate BibTeX keys? ")) - t)) - ;; do not prompt - (nil t) - ;; always prompt - (t - (not (y-or-n-p "Generate BibTeX keys? "))))) - (when (> (cl-random 100) 98) - (orb--with-message! "Pressing the RED button")) - (write-region (orb-buffer-string) - nil (orb-pdf-scrapper--get :temp-bib) nil 1) - (orb-pdf-scrapper--put :bib-undo-list (copy-tree buffer-undo-list)) - ;; generate Org-mode buffer - (let* ((temp-org (or (orb-pdf-scrapper--get :temp-org) - (orb-temp-file "orb-pdf-scrapper-" ".org")))) - (orb-pdf-scrapper--put :temp-org temp-org - :caller 'edit-org) - ;; we must change the mode in the beginning to get all the Org-mode - ;; facilities - (orb-pdf-scrapper--refresh-mode 'org) - (orb--with-message! "Generating Org data" - (erase-buffer) - (orb-pdf-scrapper--insert-refs) - (write-region (orb-buffer-string) nil temp-org nil -1) - (setq buffer-undo-list nil) - (set-buffer-modified-p nil) - (goto-char (point-min))))) - ('continue - (if-let ((temp-org (orb-pdf-scrapper--get :temp-org)) - (f-exists? temp-org)) - (progn - (erase-buffer) - (insert-file-contents temp-org) - (setq buffer-undo-list (orb-pdf-scrapper--get :org-undo-list)) - (orb-pdf-scrapper--refresh-mode 'org)) - (orb-pdf-scrapper-dispatcher 'error))))) - -(defun orb-pdf-scrapper--edit-xml () - "Edit XML data." - (pop-to-buffer orb-pdf-scrapper--buffer) - (cl-case (orb-pdf-scrapper--get :context) - ('start - (let* ((temp-xml (or (orb-pdf-scrapper--get :temp-xml) - (orb-temp-file "orb-pdf-scrapper-" ".xml")))) - (orb-pdf-scrapper--put :temp-xml temp-xml) - (orb--with-message! "Generating XML data" - ;; save progress in text mode when called from there if called from - ;; anywhere else, text mode progress is already saved, other data will - ;; be re-generated anyway - (when (eq (orb-pdf-scrapper--get :caller) 'edit-txt) - (write-region (orb-buffer-string) - nil (orb-pdf-scrapper--get :temp-txt) nil -1) - (orb-pdf-scrapper--put :txt-undo-list (copy-tree buffer-undo-list))) - (erase-buffer) - (orb-anystyle 'parse - :format 'xml - :parser-model orb-anystyle-parser-model - :input (orb-pdf-scrapper--get :temp-txt) - :stdout t - :buffer orb-pdf-scrapper--buffer) - (write-region (orb-buffer-string) nil temp-xml nil -1) - (setq buffer-undo-list nil) - (orb-pdf-scrapper--refresh-mode 'xml)))) - ('edit-master - (progn - (erase-buffer) - (insert-file-contents orb-anystyle-parser-training-set) - ;; we allow the user to see which file they are editing - (setq buffer-file-name orb-anystyle-parser-training-set) - (setq buffer-undo-list nil) - (orb-pdf-scrapper--refresh-mode 'xml))) - (t - (orb-pdf-scrapper-dispatcher 'error)))) - -(defun orb-pdf-scrapper--update-master-file () - "Append generated XML data to `orb-anystyle-parser-training-set'." - (orb--with-scrapper-buffer! - (orb--with-message! (format "Appending to master training set %s" - orb-anystyle-parser-training-set) - ;; save any progress in XML mode - (write-region (orb-buffer-string) nil - (orb-pdf-scrapper--get :temp-xml) nil -1) - (let (new-data) - ;; strip down the header and footer tokens from our data - (save-excursion - (save-match-data - (let* (beg end) - (goto-char (point-min)) - (re-search-forward "\\(^[ \t]*[ \t]*\n\\)" nil t) - (setq beg (or (match-end 1) - (point-min))) - (re-search-forward "\\(^[ \t]*[ \t]*\n\\)" nil t) - (setq end (or (match-beginning 1) - (point-max))) - (setq new-data (orb-buffer-string beg end))))) - ;; append our data to the master file - (with-temp-buffer - (insert-file-contents orb-anystyle-parser-training-set) - ;; backup the master file - (let ((master-backup (concat orb-anystyle-parser-training-set ".back"))) - (orb-pdf-scrapper--put :master-backup master-backup) - (rename-file orb-anystyle-parser-training-set master-backup t)) - (goto-char (point-max)) - (forward-line -1) - (insert new-data) - (f-touch orb-anystyle-parser-training-set) - (write-region (orb-buffer-string) nil - orb-anystyle-parser-training-set nil -1)))))) - -(defun orb-pdf-scrapper--train (&optional review) - "Update parser training set and run anystyle train. -If optional REVIEW is non-nil, run `orb-pdf-scrapper--edit-xml' -in `:edit-master' context." - (pop-to-buffer orb-pdf-scrapper--buffer) - ;; edit the master file or proceed to training - (if review - ;; we've been requested to review the master file - (progn - (orb-pdf-scrapper--update-master-file) - (orb-pdf-scrapper--put :context 'edit-master) - (orb-pdf-scrapper--edit-xml)) - ;; start the training process otherwise - (orb-pdf-scrapper--update-master-file) - (message "Training anystyle parser model...") - (when buffer-file-name - (save-buffer)) - (setq buffer-file-name nil) - (erase-buffer) - (orb-pdf-scrapper--put :context 'train) - (orb-pdf-scrapper--refresh-mode 'train) - (insert (format "\ -This can take several minutes depending on the size of your training set. -You can continue your work meanwhile and return here later.\n -Training set => %s -Parser model => %s\n -anystyle output: -=====================\n" - orb-anystyle-parser-training-set - (or orb-anystyle-parser-model "none"))) - (goto-char (point-min)) - ;; normally, anystyle runs with `shell-command', anystyle train, however, - ;; can take minutes on large files, so it runs in a shell sub-process - (let ((training-process - (orb-anystyle 'train - :stdout t - :overwrite t - :input orb-anystyle-parser-training-set - :output orb-anystyle-parser-model - :buffer orb-pdf-scrapper--buffer))) - (orb-pdf-scrapper--put :training-process training-process) - ;; finalize - (set-process-sentinel - training-process - (lambda (_p result) - (orb--with-scrapper-buffer! - (if (string= result "finished\n") - (orb--with-scrapper-buffer! - (goto-char (point-max)) - (insert "=====================\n\nDone!\n\n") - (if orb-anystyle-parser-model - (insert (format "Parser model update: %s" - orb-anystyle-parser-model)) - (insert - (format "Parser model created: %s\n" - (concat - (or (file-name-directory - orb-anystyle-parser-training-set) - (file-name-as-directory - orb-anystyle-user-directory)) - "parser.mod")) - "To use the model, \ -set `orb-anystyle-parser-model' variable to the above path.")) - (message "Training anystyle parser model...done") - (orb-pdf-scrapper--put :context 'finished - :training-process nil) - (orb-pdf-scrapper--refresh-mode 'train)) - (orb-pdf-scrapper--put :context 'error - :training-process nil)))))))) - - -;; ============================================================================ -;;; Helper functions: Export of extracted references -;; ============================================================================ - -(defun orb-pdf-scrapper--export-get-point (type placement) - "In current buffer, go to the point where data should be placed. -TYPE is target type, one of the symbols `txt', `bib' or `org'. -PLACEMENT is placement type, one of the symbols `append' or `prepend'. - -Return the point." - ;; for Org export go to the first or last heading, for BibTeX export - ;; go to the first or last entry rather than the beginning or end of - ;; buffer, respectively. - (cl-case placement - ('prepend - (cl-case type - (bib - (let ((bibtex-sort-ignore-string-entries t)) - (bibtex-beginning-of-first-entry))) - (org - (goto-char (point-min)) - (when (org-before-first-heading-p) - (org-get-next-sibling))) - (t - (goto-char (point-min))))) - (t - (cl-case type - (bib - (let ((bibtex-sort-ignore-string-entries t)) - (bibtex-end-of-entry))) - (org - (goto-char (point-max)) - (org-end-of-subtree) - (forward-line)) - (t - (goto-char (point-max)))))) - (point)) - -(defun orb-pdf-scrapper--export-insert-temp-data (type properties) - "Insert data from temporary file at point. -TYPE is type of data. PROPERTIES are additional export properties." - (let* ((temp-file (orb-pdf-scrapper--get - (intern (format ":temp-%s" type)))) - (filter (plist-get properties :filter-bib-entries)) - ;; inline subroutine to filter BibTeX entries - (insert-filtered-bib-entries - (lambda (temp-file filter) - (when (symbolp filter) - (setq filter (symbol-value filter))) - (let ((sources (cond - ((stringp filter) (list filter buffer-file-name)) - ((listp filter) (append filter buffer-file-name)) - (t (list buffer-file-name)))) - keys buf-data) - (save-excursion - (dolist (source sources) - (when source - (let ((buffer-visisted-p (find-buffer-visiting source))) - (find-file source) - (when (eq major-mode 'bibtex-mode) - (maphash (lambda (key _val) - (push key keys)) - (car (parsebib-parse-buffer)))) - (unless buffer-visisted-p - (kill-buffer (current-buffer))))))) - (with-temp-buffer - (insert-file-contents temp-file) - (goto-char (point-min)) - (let ((bibtex-sort-ignore-string-entries t)) - (bibtex-set-dialect 'biblatex t) - (bibtex-map-entries - (lambda (key _beg _end) - (when (member key keys) - (bibtex-kill-entry))))) - (setq buf-data (orb-buffer-string))) - (insert buf-data))))) - (cl-case type - (bib - (if filter - (funcall insert-filtered-bib-entries temp-file filter) - (insert-file-contents temp-file))) - (t - (insert-file-contents temp-file))))) - -(defun orb-pdf-scrapper--export-to-heading (type name properties) - "Description TYPE NAME PROPERTIES." - (let ((drawer-props (plist-get properties :property-drawer)) - (placement (plist-get properties :placement)) - (end (make-marker)) - beg data) - ;; Make the heading in a temporary buffer - (with-temp-buffer - ;; get the desired position - ;; insert parent heading - ;; NOTE: Investigate - ;; The behaviour of org-insert-heading has changed at some point: - ;; If in an empty buffer, e.g. temp-buffer, the function fails messaging "beginning of buffer" - (org-N-empty-lines-before-current 1) - (org-insert-heading nil nil t) - (insert name) - ;; insert properties - (dolist (prop drawer-props) - (let ((prop-name (or (car-safe prop) prop)) - (value (cdr-safe prop)) - prop-value) - (cond - ;; call user function if provided - ((functionp value) - (setq prop-value (funcall value)) - (unless (stringp prop-value) - (user-error "Function %s must return a string. \ -Check `orb-pdf-scrapper-export-options'" value))) - ;; provide some values for select properties - if the name was - ;; specified but not a value; - ;; NOTE: rather a placeholder for future elaboration - ((null value) - (cond - ((string= "PDF_SCRAPPER_TYPE" prop-name) - (setq prop-value (format "%s" type))) - ((string= "PDF_SCRAPPER_SOURCE" prop-name) - (setq prop-value - (f-filename (orb-pdf-scrapper--get :pdf-file)))) - ((string= "PDF_SCRAPPER_DATE" prop-name) - (setq prop-value (org-timestamp-format - (org-timestamp-from-time - (current-time) 'with-time) - "%Y-%m-%d %a %H:%M"))))) - ;; insert the user value - (t (setq prop-value value))) - ;; insert the property - (org-set-property prop-name prop-value))) - (org-end-of-meta-data) - (insert "\n") - (setq beg (point)) - (set-marker end beg) - (set-marker-insertion-type end t) - (orb-pdf-scrapper--export-insert-temp-data type properties) - ;; do some type-specific stuff - ;; - ;; Org: demote group headings which are to become subheadings of the - ;; newly created heading. - ;; - ;; BibTeX: insert into a language source block - (cl-case type - (org - (org-mode) - (goto-char beg) - (while (re-search-forward org-heading-regexp nil t) - (org-demote))) - (bib - (goto-char beg) - (insert "#+begin_src bibtex\n") - (goto-char end) - (insert "#+end_src\n"))) - (setq data (orb-buffer-string)) - (set-marker end nil)) - ;; insert the data - (orb-pdf-scrapper--export-get-point 'org placement) - (insert data "\n"))) - -(defun orb-pdf-scrapper--export-to-file (type location properties) - "Export data generated by ORB PDF Scrapper to a file. -TYPE is a symbol identifying type of data to be exported, one of -`org', `txt', or `bib'. - -LOCATION is a string specifying the location of the target file. -It can be a relative or an absolute file path. If the file does -not exist, it will be created. It can also be a relative or an -absolute path to an existing directory. In this case the data -will be exported to a file in that directory with the citation -key associated with the buffer of origin (extracted from its -#+ROAM_KEY: property) as the filename and TYPE as the extension. - -PROPERTIES is a property list with additional export properties. -See `orb-pdf-scrapper-export-options' for details." - (let* ((current-dir (file-name-directory - (buffer-file-name - (orb-pdf-scrapper--get :original-buffer)))) - (current-key - (orb-pdf-scrapper--get :current-key)) - ;; this is a sort of cond, but execute all clauses sequentially - (path (--> location - ;; if location is non-nil and it is a relative filename, - ;; expand it within the original buffer's directory - (when it - (if (f-relative? it) (f-join current-dir it) it)) - ;; if location is nil assume current directory - (if (null it) current-dir it) - ;; if location is a directory, make a file with citekey as - ;; the file name and type as the extension the location - ;; otherwise return the location - (if (f-dir? it) - (f-join it (format "%s.%s" current-key type)) - it))) - ;; file extension if any - (ext (f-ext path)) - (buffer-visited-p (find-buffer-visiting path)) - target-type buf) - (find-file path) - (setq buf (current-buffer)) - ;; type of the target file; try to determine it from the major mode; - ;; assume TYPE otherwise. - (setq target-type - (pcase major-mode - ('org-mode 'org) - ('bibtex-mode 'bib) - ((or 'text-mode 'fundamental-mode) 'txt) - (_ type))) - (save-mark-and-excursion - (pcase (plist-get properties :placement) - ('prepend - (orb-pdf-scrapper--export-get-point target-type 'prepend) - (orb-pdf-scrapper--export-insert-temp-data type properties) - (when (memq type '(org txt)) - (insert "\n"))) - (`(heading ,headline . ,heading-properties) - (if (string= ext "org") - ;; NOTE: heading-properties take precendence over path - ;; properties - (orb-pdf-scrapper--export-to-heading - type headline (append heading-properties properties)) - (user-error "Heading placement only possible in ORG files"))) - ;; defaults to append - (_ - (orb-pdf-scrapper--export-get-point target-type 'append) - (when (memq type '(bib txt)) - (insert "\n")) - (orb-pdf-scrapper--export-insert-temp-data type properties)))) - (save-buffer buf) - (unless buffer-visited-p - (kill-buffer buf)))) - -(defun orb-pdf-scrapper--export (type) - "Export the extracted and/or generated data. -TYPE is a symbol identifying type of data to be exported, one -of `txt', `bib' or `org'. - -The user variable `orb-pdf-scrapper-export-options' controls -export options." - ;; there may be several targets for a given TYPE, export to all of them - (cl-loop - for (target location . properties) - in (cdr (assoc type orb-pdf-scrapper-export-options)) - do (cl-case target - (heading - (orb-pdf-scrapper--export-to-heading type location properties)) - (path - (orb-pdf-scrapper--export-to-file type location properties))))) - -(defun orb-pdf-scrapper--checkout () - "Finalize Orb PDF Scrapper process. -Insert the extracted and generated data according to the settings -of `orb-pdf-scrapper-export-options'." - (cl-case (orb-pdf-scrapper--get :context) - ('start - (pop-to-buffer (orb-pdf-scrapper--get :original-buffer)) - ;; export the extracted/generated data - (dolist (type (mapcar #'car orb-pdf-scrapper-export-options)) - (orb-pdf-scrapper--export type)) - ;; NOTE: "break point" for ease of debugging - ;; (user-error "Halt") - (orb-pdf-scrapper-dispatcher 'kill)) - (t - (orb-pdf-scrapper-dispatcher 'error)))) - -(defun orb-pdf-scrapper--cleanup () - "Clean up before and after Orb Pdf Scrapper process." - (setq orb-pdf-scrapper--refs ()) - (dolist (prop (list :running :callee :context :caller - :current-key :prevent-concurring - :temp-txt :temp-bib :temp-org :temp-xml - :pdf-file :global-bib :master-backup - :txt-undo-list :bib-undo-list :org-undo-list - :training-process :window-conf :original-buffer)) - (orb-pdf-scrapper--put prop nil))) - - -;; ============================================================================ -;;; Minor mode -;; ============================================================================ - -;;; Code in this section was adopted from org-capture.el -(defvar orb-pdf-scrapper-mode-map - (let ((map (make-sparse-keymap))) - (define-key map "\C-c\C-k" #'orb-pdf-scrapper-kill) - (define-key map [remap save-buffer] #'orb-pdf-scrapper-save) - (define-key map [remap write-file] #'orb-pdf-scrapper-save-as) - map) - "Keymap for `orb-pdf-scrapper-mode' minor mode. -The keymap is updated automatically according to the Orb PDF -Scrapper process context. It is not supposed to be modified -directly by user." ) - -(defcustom orb-pdf-scrapper-mode-hook nil - "Hook for the `orb-pdf-scrapper-mode' minor mode." - :type 'hook - :group 'orb-pdf-scrapper) - -(define-minor-mode orb-pdf-scrapper-mode - "Minor mode for special key bindings in a orb-pdf-scrapper buffer. -Turning on this mode runs the normal hook `orb-pdf-scrapper-mode-hook'." - nil " OPS" orb-pdf-scrapper-mode-map - (when orb-pdf-scrapper-mode - (orb-pdf-scrapper--update-keymap) - (setq-local - header-line-format - (orb-pdf-scrapper--format-header-line)))) - -(defun orb-pdf-scrapper--put (&rest props) - "Add properties PROPS to `orb-pdf-scrapper--plist'. -Returns the new plist." - (while props - (setq orb-pdf-scrapper--plist - (plist-put orb-pdf-scrapper--plist - (pop props) - (pop props))))) - -(defun orb-pdf-scrapper--get (prop) - "Get PROP from `orb-pdf-scrapper--plist'." - (plist-get orb-pdf-scrapper--plist prop)) -;;; -;;; End of code adopted from org-capture.el - - -;; TODO combine `orb-pdf-scrapper--format-header-line' -;; and `orb-pdf-scrapper--update-keymap' into one -;; function and use a macro to generate each entry -(defun orb-pdf-scrapper--format-header-line () - "Return formatted buffer header line depending on context." - (substitute-command-keys - (format "\\Orb PDF Scrapper: %s. %s" - (orb-pdf-scrapper--get :current-key) - (cl-case (orb-pdf-scrapper--get :caller) - ('edit-txt - "\ -Generate BibTeX `\\[orb-pdf-scrapper-dispatcher]', \ -sanitize text `\\[orb-pdf-scrapper-sanitize-text]', \ -train parser `\\[orb-pdf-scrapper-training-session]', \ -abort `\\[orb-pdf-scrapper-kill]'.") - ('edit-bib - "\ -Generate Org `\\[orb-pdf-scrapper-dispatcher]', \ -generate keys `\\[orb-pdf-scrapper-generate-keys]', \ -return to text `\\[orb-pdf-scrapper-cancel]', \ -train parser `\\[orb-pdf-scrapper-training-session], \ -abort `\\[orb-pdf-scrapper-kill]'.") - ('edit-org - "\ -Finish `\\[orb-pdf-scrapper-dispatcher]', \ -return to BibTeX `\\[orb-pdf-scrapper-cancel]', \ -abort `\\[orb-pdf-scrapper-kill]'.") - ('edit-xml - (cl-case (orb-pdf-scrapper--get :context) - ('edit - (format "\ -Train `\\[orb-pdf-scrapper-training-session]', \ -review %s `\\[orb-pdf-scrapper-review-master-file]', \ -cancel `\\[orb-pdf-scrapper-cancel], \ -abort `\\[orb-pdf-scrapper-kill]'." - (file-name-nondirectory - orb-anystyle-parser-training-set))) - ('edit-master - "\ -Train `\\[orb-pdf-scrapper-training-session]', \ -cancel `\\[orb-pdf-scrapper-cancel], \ -abort `\\[orb-pdf-scrapper-kill]'."))) - ('train - (cl-case (orb-pdf-scrapper--get :context) - ('train - "\ -Abort `\\[orb-pdf-scrapper-kill]'.") - ('continue - "\ -Finish `\\[orb-pdf-scrapper-dispatcher]', \ -abort `\\[orb-pdf-scrapper-kill]'."))) - (t - "\ -Press the RED button `\\[orb-pdf-scrapper-kill]'."))))) - -(defun orb-pdf-scrapper--update-keymap () - "Update `orb-pdf-scrapper-mode-map' according to current editing mode. -Context is read from `orb-pdf-scrapper--plist' property `:context'." - (let ((map orb-pdf-scrapper-mode-map)) - (cl-case (orb-pdf-scrapper--get :caller) - ;; - ('edit-txt - (define-key map "\C-c\C-c" #'orb-pdf-scrapper-dispatcher) - (define-key map "\C-c\C-u" #'orb-pdf-scrapper-sanitize-text) - (define-key map "\C-C\C-t" #'orb-pdf-scrapper-training-session) - (define-key map "\C-c\C-r" nil)) - ;; - ('edit-bib - (define-key map "\C-c\C-c" #'orb-pdf-scrapper-dispatcher) - (define-key map "\C-c\C-u" #'orb-pdf-scrapper-generate-keys) - (define-key map "\C-C\C-t" #'orb-pdf-scrapper-training-session) - (define-key map "\C-c\C-r" #'orb-pdf-scrapper-cancel)) - ;; - ('edit-org - (define-key map "\C-c\C-c" #'orb-pdf-scrapper-dispatcher) - (define-key map "\C-c\C-u" nil) - (define-key map "\C-C\C-t" nil) - (define-key map "\C-c\C-r" #'orb-pdf-scrapper-cancel)) - ('edit-xml - (cl-case (orb-pdf-scrapper--get :context) - ('edit - (define-key map "\C-c\C-c" #'orb-pdf-scrapper-training-session) - (define-key map "\C-c\C-u" nil) - (define-key map "\C-C\C-t" #'orb-pdf-scrapper-review-master-file) - (define-key map "\C-c\C-r" #'orb-pdf-scrapper-cancel)) - ('edit-master - (define-key map "\C-c\C-c" #'orb-pdf-scrapper-training-session) - (define-key map "\C-c\C-u" nil) - (define-key map "\C-C\C-t" nil) - (define-key map "\C-c\C-r" #'orb-pdf-scrapper-cancel)))) - ('train - (cl-case (orb-pdf-scrapper--get :context) - ('train - (define-key map "\C-c\C-c" nil) - (define-key map "\C-c\C-r" nil) - (define-key map "\C-c\C-u" nil) - (define-key map "\C-c\C-t" nil)) - ('continue - (define-key map "\C-c\C-c" #'orb-pdf-scrapper-dispatcher)))) - (t - (define-key map "\C-c\C-u" nil) - (define-key map "\C-c\C-t" nil) - (define-key map "\C-c\C-r" nil))))) - - -;; ============================================================================ -;;; Interactive functions -;; ============================================================================ - -(defun orb-pdf-scrapper-generate-keys (&optional at-point collect-only) - "Generate BibTeX citation keys in the current buffer. -\\ -While the Orb PDF Scrapper interactive process, when editing -BibTeX data, press \\[orb-pdf-scrapper-generate-keys] to generate -citation keys using the function specified in -`orb-pdf-scrapper-keygen-function'. When called interactively -with a \\[universal-argument] prefix argument AT-POINT, generate -key only for the record at point. - -When called from Lisp, if optional COLLECT-ONLY is non-nil, do -not generate the key and update the records, just collect records -for future use." - (interactive "P") - (orb--with-message! "Generating citation keys" - (let ((bibtex-help-message nil) - (bibtex-contline-indentation 2) - (bibtex-text-indentation 2)) - (save-excursion - (if (equal at-point '(4)) - ;; generate key at point - (progn - (bibtex-beginning-of-entry) - (let* ((old-key (save-excursion - (re-search-forward - bibtex-entry-maybe-empty-head) - (bibtex-key-in-head))) - (old-ref (assoc old-key orb-pdf-scrapper--refs)) - (new-ref (orb-pdf-scrapper--update-record-at-point - collect-only))) - (if old-ref - (setf (car old-ref) (car new-ref) - (cdr old-ref) (cdr new-ref)) - (cl-pushnew new-ref orb-pdf-scrapper--refs :test 'equal)))) - ;; generate keys in the buffer otherwise - (let ((refs ()) - (natural-order 1)) - (goto-char (point-min)) - (bibtex-skip-to-valid-entry) - (while (not (eobp)) - (cl-pushnew (orb-pdf-scrapper--update-record-at-point - collect-only (format "%s" natural-order)) - refs) - (bibtex-skip-to-valid-entry) - (setq natural-order (1+ natural-order))) - (setq orb-pdf-scrapper--refs refs))))) - (write-region (orb-buffer-string) nil - (orb-pdf-scrapper--get :temp-bib) nil -1) - (set-buffer-modified-p nil))) - -(defun orb-pdf-scrapper-sanitize-text (&optional contents) - "Run string processing in current buffer. -Try to get every reference onto newline. Return this buffer's -contents (`orb-buffer-string'). - -If optional string CONTENTS was specified, run processing on this -string instead. Return modified CONTENTS." - (interactive) - (let* ((rx1 '(and "(" (** 1 2 (any "0-9")) ")")) - (rx2 '(and "[" (** 1 2 (any "0-9")) "]")) - (rx3 '(and "(" (any "a-z") (opt (any space)) ")")) - (rx4 '(and " " (any "a-z") ")")) - (regexp (rx-to-string - `(group-n 1 (or (or (and ,rx1 " " ,rx3) - (and ,rx2 " " ,rx3)) - (or (and ,rx1 " " ,rx4) - (and ,rx2 " " ,rx4)) - (or ,rx1 ,rx2) - (or ,rx3 ,rx4))) t))) - (if contents - (--> contents - (s-replace "\n" " " it) - (s-replace-regexp regexp "\n\\1" it)) - (goto-char (point-min)) - (while (re-search-forward "\n" nil t) - (replace-match " " nil nil)) - (goto-char (point-min)) - (while (re-search-forward regexp nil t) - (replace-match "\n\\1" nil nil)) - (goto-char (point-min)) - (orb-buffer-string)))) - -(defun orb-pdf-scrapper-training-session (&optional context) - "Run training session subroutines depending on CONTEXT. -If context is not provided, it will be read from -`orb-pdf-scrapper--plist''s `:context'." - (interactive) - (pop-to-buffer orb-pdf-scrapper--buffer) - (let ((context (or context (orb-pdf-scrapper--get :context)))) - (orb-pdf-scrapper--put :context context) - (cl-case context - ('start - ;; generate xml - (orb-pdf-scrapper--edit-xml)) - ((edit edit-master) - (orb-pdf-scrapper--train nil)) - ('finished - (orb-pdf-scrapper-dispatcher 'edit-txt 'continue)) - (t (orb-pdf-scrapper-dispatcher 'error))))) - -(defun orb-pdf-scrapper-review-master-file () - "Review parser training set (master file)." - (interactive) - (orb-pdf-scrapper--train t)) - -(defun orb-pdf-scrapper-dispatcher (&optional callee context) - "Call Orb PDF Scrapper subroutine CALLEE in context CONTEXT. -CALLEE and CONTEXT can be passed directly as optional variables, -or they will be read from `orb-pdf-scrapper--plist''s -respectively `:collee' and `:context' properties. - -Recognized CALLEEs are: -========== -'edit-txt - `orb-pdf-scrapper--edit-txt' -'edit-bib - `orb-pdf-scrapper--edit-bib' -'edit-org - `orb-pdf-scrapper--edit-org' -'train - `orb-pdf-scrapper-training-session' -'checkout - `orb-pdf-scrapper--checkout' - -Passing or setting any other CALLEE will kill the process. - -This function also checks `:prevent-concurring' property in -`orb-pdf-scrapper--plist' and will suggest to restart the process -if its value is non-nil." - ;; TODO: check for whether the user killed any of the buffers - (interactive) - (let ((callee (or callee (orb-pdf-scrapper--get :callee))) - (context (or context (orb-pdf-scrapper--get :context)))) - ;; in case context was passed as an argument - (orb-pdf-scrapper--put :callee callee - :context context) - (if - ;; Prevent another Orb PDF Scrapper process from running - ;; Ask user whether to kill the currently running process - (orb-pdf-scrapper--get :prevent-concurring) - (if (y-or-n-p - (format "Another Orb PDF Scrapper process is running: %s. \ -Kill it and start a new one %s? " - (orb-pdf-scrapper--get :current-key) - (orb-pdf-scrapper--get :new-key))) - ;; Kill the process and start a new one - (progn - (orb--with-message! "Killing current process" - (orb-pdf-scrapper--cleanup)) - (orb-pdf-scrapper-run (orb-pdf-scrapper--get :new-key))) - ;; go to the Scrapper buffer - (pop-to-buffer orb-pdf-scrapper--buffer) - ;; reset the concurring flag set by `orb-pdf-scrapper-run' - (orb-pdf-scrapper--put :prevent-concurring nil)) - ;; Finilize the requested context otherwise - (cl-case callee - ('edit-txt - (orb-pdf-scrapper--edit-txt)) - ('edit-bib - (orb-pdf-scrapper--edit-bib)) - ;; edit org - ('edit-org - (orb-pdf-scrapper--edit-org)) - ('checkout - ;; currently, this is unnecessary but may be useful - ;; if some recovery options are implemented - (orb--with-scrapper-buffer! - (write-region (orb-buffer-string) - nil (orb-pdf-scrapper--get :temp-org) nil 1)) - (orb-pdf-scrapper--checkout)) - (t - ;; 1 in 100 should not be too annoying - (when (> (cl-random 100) 98) - (message "Oops...") - (sleep-for 1) - (message "Oops...Did you just ACCIDENTALLY press the RED button?") - (sleep-for 1) - (message "Activating self-destruction subroutine...") - (sleep-for 1) - (message "Activating self-destruction subroutine...Bye-bye") - (sleep-for 1)) - (let ((kill-buffer-query-functions nil)) - (and (get-buffer orb-pdf-scrapper--buffer) - (kill-buffer orb-pdf-scrapper--buffer))) - (set-window-configuration (orb-pdf-scrapper--get :window-conf)) - (orb-pdf-scrapper--cleanup)))))) - -(defun orb-pdf-scrapper-cancel () - "Discard edits and return to previous editing mode." - (interactive) - (cl-case (orb-pdf-scrapper--get :caller) - ('edit-bib - (orb--with-scrapper-buffer! - (orb-pdf-scrapper--put :bib-undo-list nil)) - (orb-pdf-scrapper-dispatcher 'edit-txt 'continue)) - ('edit-org - (orb-pdf-scrapper-dispatcher 'edit-bib 'continue)) - ('edit-xml - (when-let ((master-backup (orb-pdf-scrapper--get :master-backup))) - (rename-file master-backup orb-anystyle-parser-training-set t) - (setq buffer-file-name nil)) - (orb-pdf-scrapper-dispatcher (orb-pdf-scrapper--get :callee) 'continue)) - (t - (orb-pdf-scrapper-dispatcher 'error)))) - -(defun orb-pdf-scrapper-kill () - "Kill the interactive Orb PDF Scrapper process." - (interactive) - (when-let (process (orb-pdf-scrapper--get :training-process)) - (kill-process process)) - (orb-pdf-scrapper-dispatcher 'kill)) - -(defun orb-pdf-scrapper-save () - "Save current ORB PDF Scrapper buffer in the respective temp file. -This command shadows `save-buffer' when `orb-pdf-scrapper-mode' is active." - (interactive) - (let ((temp-file - (cl-case (orb-pdf-scrapper--get :caller) - ('edit-txt (orb-pdf-scrapper--get :temp-txt)) - ('edit-bib (orb-pdf-scrapper--get :temp-bib)) - ('edit-org (orb-pdf-scrapper--get :temp-org)) - ('edit-xml (orb-pdf-scrapper--get :temp-xml)) - (t nil)))) ; fallback flag - (cond - ;; ORB PDF Scrapper buffers do not have file names - ((and (not buffer-file-name) temp-file) - (write-region (orb-buffer-string) nil temp-file nil -1) - (set-buffer-modified-p nil)) - ((save-buffer))))) - -(defun orb-pdf-scrapper-save-as () - "Export current ORB PDF Scrapper buffer to a file. -This command shadows `write-file' when `orb-pdf-scrapper-mode' is active." - (interactive) - ;; ORB PDF Scrapper buffers do not have file names - (cond - ((not buffer-file-name) - (call-interactively #'write-file) - (set-visited-file-name nil) - (rename-buffer orb-pdf-scrapper--buffer)) - ((call-interactively #'write-file)))) - - -;; ============================================================================ -;;; Entry point -;; ============================================================================ - -;;;###autoload -(defun orb-pdf-scrapper-run (key) - "Run Orb PDF Scrapper interactive process. -KEY is note's citation key." - (interactive) - (if (orb-pdf-scrapper--get :running) - (progn - (orb-pdf-scrapper--put :prevent-concurring t - :new-key key) - (orb-pdf-scrapper-dispatcher)) - ;; in case previous process was not killed properly - (orb-pdf-scrapper--cleanup) - (orb-pdf-scrapper--put :callee 'edit-txt - :context 'start - :caller 'run - :current-key key - :new-key nil - :pdf-file (file-truename - (orb-get-attached-file key)) - :running t - :prevent-concurring nil - :global-bib bibtex-completion-bibliography - :original-buffer (current-buffer) - :window-conf (current-window-configuration)) - (orb-pdf-scrapper-dispatcher))) - -(provide 'orb-pdf-scrapper) -;;; orb-pdf-scrapper.el ends here -;; Local Variables: -;; coding: utf-8 -;; fill-column: 79 -;; End: diff --git a/org-roam-bibtex.el b/org-roam-bibtex.el index 41d0add..ed55178 100644 --- a/org-roam-bibtex.el +++ b/org-roam-bibtex.el @@ -58,17 +58,17 @@ (require 'orb-core) +(require 'cl-lib) + (eval-when-compile - (require 'subr-x) - (require 'cl-lib) - (require 'cl-macs)) + (require 'subr-x)) ;; declare own functions and variables (declare-function orb-helm-insert "orb-helm") (declare-function orb-note-actions-helm "orb-helm") (declare-function orb-ivy-insert "orb-ivy") (declare-function orb-note-actions-ivy "orb-ivy") -(declare-function orb-pdf-scrapper-run "orb-pdf-scrapper" (key)) +(declare-function refuse-run "refuse" (key)) ;; declare external functions and variables @@ -386,7 +386,7 @@ Each action is a cons cell DESCRIPTION . FUNCTION." (defcustom orb-note-actions-extra '(("Save citekey to kill-ring and clipboard" . orb-note-actions-copy-citekey) - ("Run Orb PDF Scrapper" . orb-note-actions-scrap-pdf)) + ("Run Orb PDF Scrapper" . orb-note-actions-scrape-pdf)) "Extra actions for `orb-note-actions'. Each action is a cons cell DESCRIPTION . FUNCTION." :risky t @@ -1022,11 +1022,11 @@ CITEKEY is a list whose car is a citation key." (insert (car citekey)) (copy-region-as-kill (point-min) (point-max)))) -(defun orb-note-actions-scrap-pdf (citekey) - "Wrapper around `orb-pdf-scrapper-insert'. +(defun orb-note-actions-scrape-pdf (citekey) + "Wrapper around `refuse-run'. CITEKEY is a list whose car is a citation key." - (require 'orb-pdf-scrapper) - (orb-pdf-scrapper-run (car citekey))) + (require 'refuse) + (refuse-run (car citekey))) ;; ============================================================================ ;;;; Org-roam-bibtex minor mode From ceb31e30c42c70a142e57dcb7e96b00a7acf804f Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Fri, 7 Jan 2022 00:10:51 +0100 Subject: [PATCH 2/5] (int) remove utilities no longer used in ORB - orb--with-message! -> refuse-with-progress - orb-buffer-string -> refuse-buffer-string - orb-format -> refuse-anystyle-format - orb-temp-file -> refuse-temp-file --- orb-utils.el | 131 --------------------------------------------------- 1 file changed, 131 deletions(-) diff --git a/orb-utils.el b/orb-utils.el index 082b59c..bb23da9 100644 --- a/orb-utils.el +++ b/orb-utils.el @@ -58,16 +58,6 @@ ;;;; 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 @@ -101,127 +91,6 @@ Include CITEKEY if it is non-nil." :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 ;; ============================================================================ From 7b4cdb06cd7343c495ae1ce5a60c11701c11f194 Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Fri, 7 Jan 2022 12:57:03 +0100 Subject: [PATCH 3/5] (int,dev) move most of the stuff to orb-core.el - To silence `package-lint` complaining about `orb-` namespace in `org-roam-bibtex.el` --- orb-core.el | 1019 ++++++++++++++++++++++++++++++++++++++++++++ orb-helm.el | 8 +- orb-ivy.el | 8 +- org-roam-bibtex.el | 1017 +------------------------------------------ 4 files changed, 1028 insertions(+), 1024 deletions(-) diff --git a/orb-core.el b/orb-core.el index 3d717b2..b0c2965 100644 --- a/orb-core.el +++ b/orb-core.el @@ -32,6 +32,7 @@ ;; bibtex-completion and their dependencies. ;;; Code: + ;; ============================================================================ ;;; Dependencies @@ -40,11 +41,22 @@ (require 'orb-utils) (require 'orb-compat) +(require 'cl-lib) (eval-when-compile (require 'cl-macs) (require 'subr-x) (require 'rx)) +;; own functions and variables +;; +(declare-function orb-helm-insert "orb-helm") +(declare-function orb-note-actions-helm "orb-helm") +(declare-function orb-ivy-insert "orb-ivy") +(declare-function orb-note-actions-ivy "orb-ivy") + +;; external functions and variables + +;; Bibtex-completion (declare-function bibtex-completion-get-entry "bibtex-completion" (entry-key)) (declare-function @@ -52,6 +64,25 @@ (declare-function bibtex-completion-find-pdf (key-or-entry &optional find-additional)) + +;; Projectile, Perspective-mode +(declare-function projectile-relevant-open-projects "ext:projectile") +(declare-function persp-switch "ext:persp-mode") +(declare-function persp-names "ext:persp-mode") + +;; Org-ref +(defvar org-ref-notes-function) +(declare-function org-ref-find-bibliography "ext:org-ref-core") + +;; Citar +(defvar citar-open-note-function) + +;; Hydra +(declare-function defhydra "ext:hydra") + +;; Refuse +(declare-function refuse-run "refuse" (key)) + ;; ============================================================================ ;;; Customize groups @@ -250,6 +281,994 @@ The intended primary use is with `orb-note-actions'." (funcall bibtex-completion-pdf-open-function (file-truename attachment)) (message "No PDF(s) found for this entry: %s" key)))) + +;; ============================================================================ +;;; Customize definitions +;; ============================================================================ + +(defcustom orb-preformat-templates t + "Non-nil to enable template pre-expanding. +See `orb-edit-note' for details." + :type '(choice + (const :tag "Yes" t) + (const :tag "No" nil)) + :group 'org-roam-bibtex) + +(defcustom orb-preformat-keywords + '("citekey" "entry-type" "date" "pdf?" "note?" "file" + "author" "editor" "author-abbrev" "editor-abbrev" + "author-or-editor-abbrev") + "A list of template placeholders for pre-expanding. +Any BibTeX field can be set for pre-expanding including +Bibtex-completion virtual fields such as '=key=' and '=type='. +BibTeX fields can be referred to by means of their aliases +defined in `orb-bibtex-field-aliases'. + +Usage example: + +\(setq orb-preformat-keywords '(\"citekey\" \"author\" \"date\")) +\(setq orb-templates + '((\"r\" \"reference\" plain + \"#+ROAM_KEY: %^{citekey}%? +%^{author} published %^{entry-type} in %^{date}: fullcite:%\\1.\" + :target + (file+head \"references/${citekey.org}\" \"#+title: ${title}\n\") + :unnarrowed t))) + +Special cases: + +The \"file\" keyword will be treated specially if the value of +`orb-process-file-keyword' is non-nil. See its docstring for an +explanation. + +This variable takes effect when `orb-preformat-templates' is set +to t (default). See also `orb-edit-note' for further details. + +Consult Bibtex-completion documentation for additional +information on BibTeX field names." + :type '(repeat :tag "BibTeX field names" string) + :group 'org-roam-bibtex) + +(defcustom orb-process-file-keyword t + "Whether to treat the file keyword specially during template pre-expanding. +When this variable is non-nil, the \"%^{file}\" and \"${file}\" +wildcards will be processed by `org-process-file-field' rather +than simply replaced with the field value. This may be useful in +situations when the file field contains several file names and +only one file name is desirable for retrieval. The \"file\" +keyword must be set for pre-expanding in `orb-preformat-keywords' +as usual. + +If this variable is `string', for example \"my-file\", use its +value as the wildcard keyword instead of the default \"file\" +keyword. Thus, it will be possible to get both the raw file +field value by expanding the %^{file} and ${file} wildcards and a +single file name by expanding the %^{my-file} and ${my-file} +wildcards. The keyword, e.g. \"my-file\", must be set for +pre-expanding in `orb-preformat-keywords' as usual. + +The variable `orb-attached-file-extensions' controls filtering of +file names based on file extensions." + ;; TODO: check if a custom string is really working as described + :group 'org-roam-bibtex + :type '(choice + (const :tag "Yes" t) + (const :tag "No" nil) + (string :tag "Custom wildcard keyword"))) + +(defcustom orb-roam-ref-format 'org-ref-v2 + "Defines the format of citation key in the `ROAM_REFS' property. +Should be one of the following symbols: +- `org-ref-v2': Old Org-ref `cite:links' +- `org-ref-v3': New Org-ref `cite:&links' +- `org-cite' : Org-cite `@elements' + +This can also be a custom `format' string with a single `%s' specifier." + :type '(radio + (const :tag "Org-ref v2" org-ref-v2) + (const :tag "Org-ref v3" org-ref-v3) + (const :tag "Org-cite" org-cite) + (string :tag "Custom format string")) + :group 'org-roam-bibtex) + +(defcustom orb-bibtex-entry-get-value-function #'bibtex-completion-apa-get-value + "Function to be used by ORB for values from a BibTeX entry. + +The default value of this variable is `bibtex-completion-apa-get-value', +which offers some post-formatting for author fields. + +Another possible choice available out of the box is +`bibtex-completion-get-value', which returns a verbatim value. + +Set this to a custom function if you need more flexibility. +This function should take two arguments FIELD-NAME and ENTRY. +FIELD-NAME is the name of the field whose value should be retrieved. +ENTRY is a BibTeX entry as returned by `bibtex-completion-get-entry'." + :risky t + :group 'org-roam-bibtex + :type '(radio (function-item bibtex-completion-apa-get-value) + (function-item bibtex-completion-get-value) + (function :tag "Custom function"))) + +(defcustom orb-persp-project `("notes" . ,org-roam-directory) + "Perspective name and path to the project with bibliography notes. +A cons cell (PERSP-NAME . PROJECT-PATH). Only relevant when +`orb-switch-persp' is set to t. + +PERSP-NAME should be a valid Perspective name, PROJECT-PATH should be +an open Projectile project. + +See `orb-edit-note' for details" + :type '(cons (string :tag "Perspective name") + (directory :tag "Projectile directory")) + :group 'org-roam-bibtex) + +(defcustom orb-switch-persp nil + "Non-nil to enable switching to the notes perspective. +Set the name of the perspective and the path to the notes project +in `orb-persp-project' for this to take effect. + +Perspective switching works with Pers-mode and Projectile." + :type '(choice + (const :tag "Yes" t) + (const :tag "No" nil)) + :group 'org-roam-bibtex) + +(defcustom orb-ignore-bibtex-store-link-functions + '(org-bibtex-store-link) + "Functions to override with `ignore' during note creation process. + +Org Ref defines function `org-ref-bibtex-store-link' to store +links to a BibTeX buffer, e.g. with `org-store-link'. At the +same time, Org ref requires `ol-bibtex' library, which defines +`org-bibtex-store-link' to do the same. When creating a note +with `orb-edit-note' from a BibTeX buffer, for example by calling +`org-ref-open-bibtex-notes', the initiated `org-capture' process +implicitly calls `org-store-link'. The latter loops through all +the functions for storing links, and if more than one function +can store links to the location, the BibTeX buffer in this +particular case, the user will be prompted to choose one. This +is definitely annoying, hence ORB will advise all functions in +this list to return nil to trick `org-capture' and get rid of the +prompt. + +The default value is `(org-bibtex-store-link)', which means this +function will be ignored and `org-ref-bibtex-store-link' will be +used to store a link to the BibTeX buffer. See +`org-capture-templates' on how to use the link in your templates." + :type '(repeat (function)) + :risky t + :group 'org-roam-bibtex) + +(defcustom orb-insert-interface 'generic + "Interface frontend to use with `orb-insert-link'. +Possible values are the symbols `helm-bibtex', `ivy-bibtex', or +`generic' (default). In the first two cases the respective +commands will be used, while in the latter case the command +`orb-insert-generic' will be used. + +When using `helm-bibtex' or `ivy-bibtex' as `orb-insert-interface', +choosing the action \"Edit note & insert a link\" will insert the +desired link. For convenience, this action is made default for +the duration of an `orb-insert-link' session. It will not +persist when `helm-bibtex' or `ivy-bibtex' proper are run. +Otherwise, the command is just the usual `helm-bibtex'/`ivy-bibtex'. +For example, it is possible to run other `helm-bibtex' or +`ivy-bibtex' actions. When action other than \"Edit note & +insert a link\" is run, no link will be inserted, although the +session can be resumed later with `helm-resume' or `ivy-resume', +respectively, where it will be possible to select the \"Edit note +& insert a link\" action. + +When using the `generic' interface, a simple list of available +citation keys is presented using `completion-read' and after +choosing a candidate the appropriate link will be inserted. + +Please note that this variable should be set using the Customize +interface, `use-package''s `:custom' keyword, or Doom's `setq!' +macro. Simple `setq' will not work." + :group 'org-roam-bibtex + :type '(radio + (const helm-bibtex) + (const ivy-bibtex) + (const generic)) + :set (lambda (var value) + (cond + ((eq value 'ivy-bibtex) + (require 'orb-ivy)) + ((eq value 'helm-bibtex) + (require 'orb-helm))) + (set-default var value))) + +(defcustom orb-insert-link-description 'title + "Link description format for links created with `orb-insert-link'. +The command `orb-insert-link' can be used to create Org-mode +links to bibliographic notes of type [[id:note_id][Description]]. +This variable determines the 'Description' part from the example +above. It is an `s-format' string, where special placeholders of +form '${field}' will be expanded with data from the respective +BibTeX field of the associated BibTeX entry. If the field's +value cannot be retrieved, the user will be prompted to input a +value interactively. When retrieving BibTeX data, the user +options `orb-bibtex-field-aliases' and +`orb-bibtex-entry-get-value-function' are respected. + +This variable can also be one of the following symbols: + +`title' - equivalent to \"${title}\" +`citekey' - equivalent to \"${citekey}\" +`citation-org-ref-2' - create Org-ref v2 'cite:citekey' citation instead +`citation-org-ref-3' - create Org-ref v3 'cite:&citekey' citation instead +`citation-org-cite' - create Org-cite '[cite:@citekey]' citation instead + +The default value set by this variable can be overriden by +calling `orb-insert-link' with an appropriated numerical prefix +argument. See its docstring for more information." + :group 'org-roam-bibtex + :type '(choice + (string :tag "Format string") + (const :tag "Title" title) + (const :tag "Citation key" citekey) + (const :tag "Citation link" citation-org-ref-2) + (const :tag "Citation link" citation-org-ref-3) + (const :tag "Citation link" citation-org-cite))) + +(defcustom orb-insert-follow-link nil + "Whether to follow a newly inserted link." + :group 'orb-roam-bibtex + :type '(choice + (const :tag "Yes" t) + (const :tag "No" nil))) + +(defcustom orb-insert-generic-candidates-format 'key + "Format of selection candidates for `orb-insert-generic' interface. +Possible values are `key' and `entry'." + :group 'org-roam-bibtex + :type '(choice + (const key) + (const entry))) + +(defcustom orb-note-actions-interface 'default + "Interface frontend for `orb-note-actions'. +Supported values (interfaces) are symbols `default', `ido', +`hydra', `ivy' and `helm'. + +Alternatively, it can be set to a function, in which case the +function should expect one argument CITEKEY, which is a list +whose car is the citation key associated with the org-roam note +the current buffer is visiting. Also, it should ideally make use +of `orb-note-actions-default', `orb-note-actions-extra' and +`orb-note-actions-user' for providing an interactive interface, +through which the combined set of note actions is presented as a +list of candidates and the function associated with the candidate +is executed upon selecting it. + +This variable should be set using the Customize interface, +`use-package''s `:custom' keyword, or Doom's `setq!' macro. +Simple `setq' will not work." + :risky t + :type '(radio + (const :tag "Default" default) + (const :tag "Ido" ido) + (const :tag "Hydra" hydra) + (const :tag "Ivy" ivy) + (const :tag "Helm" helm) + (function :tag "Custom function")) + :set (lambda (var value) + (cond + ((eq value 'ivy) + (require 'orb-ivy)) + ((eq value 'helm) + (require 'orb-helm)) + ((eq value 'hydra) + (require 'hydra))) + (set-default var value)) + :group 'orb-note-actions) + +(defcustom orb-note-actions-default + '(("Open PDF file(s)" . orb-open-attached-file) + ("Add PDF to library" . bibtex-completion-add-pdf-to-library) + ("Open URL or DOI in browser" . bibtex-completion-open-url-or-doi) + ("Show record in the bibtex file" . bibtex-completion-show-entry)) + "Default actions for `orb-note-actions'. +Each action is a cons cell DESCRIPTION . FUNCTION." + :risky t + :type '(alist + :tag "Default actions for `orb-note-actions'" + :key-type (string :tag "Description") + :value-type (function :tag "Function")) + :group 'orb-note-actions) + +(defcustom orb-note-actions-extra + '(("Save citekey to kill-ring and clipboard" . orb-note-actions-copy-citekey) + ("Run Orb PDF Scrapper" . orb-note-actions-scrape-pdf)) + "Extra actions for `orb-note-actions'. +Each action is a cons cell DESCRIPTION . FUNCTION." + :risky t + :type '(alist + :tag "Extra actions for `orb-note-actions'" + :key-type (string :tag "Description") + :value-type (function :tag "Function")) + :group 'orb-note-actions) + +(defcustom orb-note-actions-user nil + "User actions for `orb-note-actions'. +Each action is a cons cell DESCRIPTION . FUNCTION." + :risky t + :type '(alist + :tag "User actions for `orb-note-actions'" + :key-type (string :tag "Description") + :value-type (function :tag "Function")) + :group 'orb-note-actions) + + +;; ============================================================================ +;;; Orb edit notes +;; ============================================================================ + +(defun orb--switch-perspective () + "Helper function for `orb-edit-note'." + (when (and (require 'projectile nil t) + (require 'persp-mode nil t)) + (let ((notes-project (cdr orb-persp-project)) + (projects (projectile-relevant-open-projects)) + openp) + (dolist (project projects openp) + (setq openp (or (f-equal? project notes-project) openp))) + (when openp + (let ((p-names (cdr (persp-names)))) + (dolist (p-name p-names) + (when (s-equals? p-name (car orb-persp-project)) + (persp-switch p-name)))))))) + +(defun orb--store-link-functions-advice (action) + "Add or remove advice for each of `orb-ignore-bibtex-store-link-functions'. +ACTION should be a symbol `add' or `remove'. A piece of advice +is the function `ignore', it is added as `:override'." + (when orb-ignore-bibtex-store-link-functions + (let ((advice-func (intern (format "advice-%s" action))) + (advice (cl-case action + (add (list :override #'ignore)) + (remove (list #'ignore)) + (t (user-error "Action type not recognised: %s" action))))) + (dolist (advisee orb-ignore-bibtex-store-link-functions) + (apply advice-func (push advisee advice)))))) + +(defun orb--pre-expand-template (template entry) + "Helper function for `orb--new-note'. +TEMPLATE is an element of `org-roam-capture-templates' and ENTRY +is a BibTeX entry as returned by `bibtex-completion-get-entry'." + ;; Handle org-roam-capture part + (letrec (;; Org-capture templates: handle different types of + ;; org-capture-templates: string, file and function; this is + ;; a stripped down version of `org-capture-get-template' + (org-template + (pcase (nth 3 template) ; org-capture template is here + (`nil 'nil) + ((and (pred stringp) tmpl) tmpl) + (`(file ,file) + (let ((flnm (expand-file-name file org-directory))) + (if (file-exists-p flnm) (f-read-text flnm) + (format "Template file %S not found" file)))) + (`(function ,fun) + (if (functionp fun) (funcall fun) + (format "Template function %S not found" fun))) + (_ (user-error "ORB: Invalid capture template")))) + ;; org-roam capture properties are here + (plst (cddddr template)) + ;; regexp for org-capture prompt wildcard + (rx "\\(%\\^{[[:alnum:]-_]*}\\)") + (file-keyword (when orb-process-file-keyword + (or (and (stringp orb-process-file-keyword) + orb-process-file-keyword) + "file"))) + ;; inline function to handle :target list expansion + (expand-roam-template + (lambda (roam-template-list old new) + (let (elements) + (dolist (el roam-template-list) + (if (listp el) + (setq elements + (nreverse + (append elements + (list (funcall expand-roam-template + el old new))))) + (push (s-replace old new el) elements))) + (nreverse elements)))) + (lst nil)) + ;; First run: + ;; 1) Make a list of (org-wildcard field-value match-position) for the + ;; second run + ;; 2) replace org-roam-capture wildcards + (dolist (keyword orb-preformat-keywords) + (let* (;; prompt wildcard keyword + (keyword (cond + ;; for some backward compatibility with old + ;; `orb-preformat-keywords' + ((consp keyword) (car keyword)) + ((stringp keyword) keyword) + (t (user-error "Error in `orb-preformat-keywords': \ +Keyword \"%s\" has invalid type (string was expected)" keyword)))) + ;; bibtex field name + (field-name (orb-resolve-field-alias keyword)) + ;; get the bibtex field value + (field-value + ;; maybe process file keyword + (or (if (and file-keyword (string= field-name file-keyword)) + (prog1 + (orb-get-attached-file + (funcall orb-bibtex-entry-get-value-function "=key=" entry)) + ;; we're done so don't even compare file-name with + ;; file-keyword in the successive cycles + (setq file-keyword nil)) + ;; do the usual processing otherwise + ;; condition-case to temporary workaround an upstream bug + (condition-case nil + (funcall orb-bibtex-entry-get-value-function field-name entry) + (error ""))) + "")) + ;; org-capture prompt wildcard + (org-wildcard (concat "%^{" (or keyword "citekey") "}")) + ;; org-roam-capture prompt wildcard + (roam-wildcard (concat "${" (or keyword "citekey") "}")) + ;; org-roam-capture :target property + (roam-template (or (plist-get plst :if-new) (plist-get plst :target))) + (i 1) ; match counter + pos) + ;; Search for org-wildcard, set flag m if found + (when org-template + (while (string-match rx org-template pos) + (if (string= (match-string 1 org-template) org-wildcard) + (progn + (setq pos (length org-template)) + (cl-pushnew (list org-wildcard field-value i) lst )) + (setq pos (match-end 1) + i (1+ i))))) + ;; Replace placeholders in org-roam-capture-templates :target property + (when roam-template + (setcdr roam-template + (funcall expand-roam-template + (cdr roam-template) roam-wildcard field-value))))) + ;; Second run: replace prompts and prompt matches in org-capture + ;; template string + (dolist (l lst) + (when (and org-template (nth 1 l)) + (let ((pos (concat "%\\" (number-to-string (nth 2 l))))) + ;; replace prompt match wildcards with prompt wildcards + ;; replace prompt wildcards with BibTeX field value + (setq org-template (s-replace pos (car l) org-template) + org-template (s-replace (car l) (nth 1 l) org-template)))) + (setf (nth 3 template) org-template)) + template)) + +(defun orb--new-note (citekey &optional props) + "Process templates and run `org-roam-capture-'. +CITEKEY is the citation key of an entry for which the note is +created. PROPS are additional properties for `org-roam-capture-'." + ;; Check if the requested BibTeX entry actually exists and fail + ;; gracefully otherwise + (if-let* ((entry (or (bibtex-completion-get-entry citekey) + (orb-warning + "Could not find the BibTeX entry" citekey))) + ;; Depending on the templates used: run + ;; `org-roam-capture--capture' or call `org-roam-node-find' + (org-capture-templates org-roam-capture-templates) + ;; hijack org-capture-templates + ;; entry is our bibtex entry, it just happens that + ;; `org-capture' calls a single template entry "entry"; + (template (--> (if (null (cdr org-capture-templates)) + ;; if only one template is defined, use it + (car org-capture-templates) + (org-capture-select-template)) + (when (listp it) + (copy-tree it)) + ;; optionally pre-expand templates + (if (and it orb-preformat-templates) + (orb--pre-expand-template it entry) + it))) + ;; pretend we had only one template + ;; `org-roam-capture--capture' behaves specially in this case + ;; NOTE: this circumvents using functions other than + ;; `org-capture', see `org-roam-capture-function'. + ;; If the users start complaining, we may revert previous + ;; implementation + (org-roam-capture-templates (list template)) + ;; Org-roam coverts the templates to its own syntax; + ;; since we are telling `org-capture' to use the template entry + ;; (by setting `org-capture-entry'), and Org-roam converts the + ;; whole template list, we must do the conversion of the entry + ;; ourselves + (props (--> (or props (list :finalize 'find-file)) + (plist-put it :call-location (point-marker)))) + (org-capture-entry + (org-roam-capture--convert-template template props)) + (citekey-ref (format + (pcase orb-roam-ref-format + ('org-ref-v2 "cite:%s") + ('org-ref-v3 "cite:&%s") + ('org-cite "@%s") + ((pred stringp) orb-roam-ref-format) + (_ (user-error "Invalid format `orb-roam-ref-format'"))) + citekey)) + (title + (or (funcall orb-bibtex-entry-get-value-function "title" entry) + (and + (orb-warning "Title not found for this entry") + ;; this is not critical, the user may input their own + ;; title + "No title"))) + (node (org-roam-node-create :title title))) + (org-roam-capture- + :node node + :info (list :ref citekey-ref)) + (user-error "Abort"))) + +;;;###autoload +(defun orb-edit-note (citekey) + "Open an Org-roam note associated with the CITEKEY or create a new one. + +This function allows to use Org-roam as a backend for managing +bibliography notes. It relies on `bibtex-completion' to get +retrieve bibliographic information from a BibTeX file. + +Implementation details and features: + +1. This function first calls `org-roam-find-ref' trying to find +the note file associated with the CITEKEY. The Org-roam key can +be set with '#+ROAM_KEY:' in-buffer keyword. + +2. If the Org-roam reference has not been found, the function +calls `org-roam-node-find' passing to it the title associated +with the CITEKEY as retrieved by `bibtex-completion-get-entry'. +The prompt presented by `org-roam-node-find' will thus be +pre-populated with the record title. + +3. Optionally, when `orb-preformat-templates' is non-nil, any +prompt wildcards in `orb-templates' or +`org-roam-capture-templates', associated with the bibtex record +fields as specified in `orb-preformat-templates', will be +preformatted. Both `org-capture-templates' (%^{}) and +`org-roam-capture-templates' (`s-format', ${}) prompt syntaxes +are supported. + +See `orb-preformat-keywords' for more details on how +to properly specify prompts for replacement. + +Please pay attention when using this feature that by setting +title for preformatting, it will be impossible to change it in +the `org-roam-node-find' interactive prompt since all the +template expansions will have taken place by then. All the title +wildcards will be replace with the BibTeX field value. + +4. Optionally, if you are using Projectile and Persp-mode and +have a dedicated workspace to work with your Org-roam collection, +you may want to set the perspective name and project path in +`orb-persp-project' and `orb-switch-persp' to t. In this case, +the perspective will be switched to the Org-roam notes project +before calling any Org-roam functions. + +If optional argument ENTRY is non-nil, use it to fetch the +bibliographic information." + ;; Optionally switch to the notes perspective + (when orb-switch-persp + (orb--switch-perspective)) + (orb-make-notes-cache) + (if-let ((node (orb-note-exists-p citekey))) + (ignore-errors (org-roam-node-visit node)) + ;; fix some Org-ref related stuff + (orb--store-link-functions-advice 'add) + ;; TODO: consider using unwind-protect and let the errors through + (condition-case error-msg + (orb--new-note citekey) + ((debug error) + (orb--store-link-functions-advice 'remove) + (message "%s" + (concat (and (eq (car error-msg) 'error) + "orb-edit-note caught an error during capture: ") + (error-message-string error-msg))))))) + +;; FIXME: this does not work anymore +;; (defun orb--get-non-ref-path-completions () +;; "Return a list of cons for titles of non-ref notes to absolute path. +;; CANDIDATES is a an alist of candidates to consider. Defaults to +;; `org-roam--get-title-path-completions' otherwise." +;; (let* ((rows (org-roam-db-query +;; [:select [titles:file titles:title tags:tags] +;; :from titles +;; :left :join tags +;; :on (= titles:file tags:file) +;; :left :join refs :on (= titles:file refs:file) +;; :where refs:file :is :null])) +;; completions) +;; (dolist (row rows completions) +;; (pcase-let ((`(,file-path ,title ,tags) row)) +;; (let ((title (or title +;; (list (org-roam--path-to-slug file-path))))) +;; (let ((k (concat +;; (when tags +;; (format "(%s) " (s-join org-roam-tag-separator tags))) +;; title)) +;; (v (list :path file-path :title title))) +;; (push (cons k v) completions))))))) + + +;; ============================================================================ +;;; Orb insert +;; ============================================================================ + +(defvar orb-insert-lowercase nil + "Internal. Dynamic variable for `orb-insert-link' and `orb-insert--link'.") + +(defun orb-insert--link (node info) + "Insert a link to NODE. +INFO contains additional information." + ;; citekey &optional description lowercase region-text beg end + (-let (((&plist :region :orb-link-description :orb-citekey :orb-entry) info)) + (when region + (org-roam-unshield-region (car region) (cdr region)) + (delete-region (car region) (cdr region)) + (set-marker (car region) nil) + (set-marker (cdr region) nil)) + (pcase orb-link-description + ((pred stringp) + (let ((description + (--> + (s-format orb-link-description + (lambda (template entry) + (funcall orb-bibtex-entry-get-value-function + (orb-resolve-field-alias template) entry)) + orb-entry) + (if (and it orb-insert-lowercase) (downcase it) it)))) + (insert (org-link-make-string + (concat "id:" (org-roam-node-id node)) description)))) + (`citation-org-cite + (insert (format "[cite:@%s]" orb-citekey))) + (ref-format + (let ((cite-link (if (boundp 'org-ref-default-citation-link) + (concat org-ref-default-citation-link ":") + "cite:"))) + (insert (concat cite-link + (when (eq ref-format 'citation-org-ref-3) "&") + orb-citekey))))))) + +(defun org-roam-capture--finalize-orb-insert-link () + "Insert a link to a just captured note. +This function is used by ORB calls to `org-roam-capture-' instead +of `org-roam-capture--finalize-insert-link'." + (let* ((mkr (org-roam-capture--get :call-location)) + (buf (marker-buffer mkr)) + (region (org-roam-capture--get :region)) + (node (org-roam-populate (org-roam-node-create :id (org-roam-capture--get :id)))) + (citekey (org-capture-get :orb-citekey)) + (entry (bibtex-completion-get-entry citekey))) + (with-current-buffer buf + (org-with-point-at mkr + (orb-insert--link node (list + :region region + :orb-citekey citekey + :orb-entry entry + :orb-link-description (org-capture-get :orb-link-description))))))) + +(defun orb--insert-captured-ref-h () + "Insert value of `:ref' key from `org-roam-capture--info'. +Internal function. To be installed in `org-roam-capture-new-node-hook'." + (when-let ((ref (plist-get org-roam-capture--info :ref))) + (org-roam-ref-add ref))) + +(defun orb-insert-edit-note (citekey) + "Insert a link to a note with citation key CITEKEY. +Capture a new note if it does not exist yet. + +CITEKEY can be a list of citation keys (for compatibility with +Bibtex-completion), in which case only the first element of that +list is used." + (unwind-protect + ;; Group functions together to avoid inconsistent state on quit + (atomic-change-group + (let* ((citekey (cl-typecase citekey + (string citekey) + (list (car citekey)) + (t (user-error "Invalid citation key data type: %s. \ +String or list of strings expected" citekey)))) + (entry (bibtex-completion-get-entry citekey)) + (title + (funcall orb-bibtex-entry-get-value-function "title" entry "")) + (node (or (orb-note-exists-p citekey) + (org-roam-node-create :title title))) + region-text + beg end + (_ (when (region-active-p) + (setq beg (set-marker (make-marker) (region-beginning))) + (setq end (set-marker (make-marker) (region-end))) + (setq region-text + (buffer-substring-no-properties beg end)))) + (description (--> orb-insert-link-description + (cl-case it + (title "${title}") + (citekey "${citekey}") + (t it)) + (or region-text it))) + (info (--> (list :orb-link-description description + :orb-citekey citekey + :orb-entry entry + :finalize 'orb-insert-link) + (if (and beg end) + (append it (list :region (cons beg end))) + it)))) + (if (org-roam-node-id node) + (orb-insert--link node info) + (orb--new-note citekey info))) + (deactivate-mark))) + (when (and orb-insert-follow-link + (looking-at org-link-any-re)) + (org-open-at-point))) + +(defun orb-insert-generic (&optional arg) + "Present a list of BibTeX entries for completion. +This is a generic completion function for `orb-insert-link', which +runs `orb-insert-edit-note' on the selected entry. The list is +made by `bibtex-completion-candidates'. + +The appearance of selection candidates is determined by +`orb-insert-generic-candidates-format'. + +This function is not interactive, set `orb-insert-interface' to +`generic' and call `orb-insert-link' interactively instead. + +If ARG is non-nil, rebuild `bibtex-completion-cache'." + (when arg + (bibtex-completion-clear-cache)) + (bibtex-completion-init) + (let* ((candidates (bibtex-completion-candidates)) + (candidates2 + (if (eq orb-insert-generic-candidates-format 'key) + (mapcar (lambda (item) + (alist-get "=key=" (cdr item) nil nil #'equal)) + candidates) + (mapcar #'car candidates))) + (selection (completing-read "BibTeX entry:" candidates2 nil t)) + (citekey (if (eq orb-insert-generic-candidates-format 'key) + selection + (--> (alist-get selection candidates nil nil #'equal) + (cdr it) + (alist-get "=key=" it nil nil #'equal))))) + (orb-insert-edit-note citekey))) + +;;;###autoload +(defun orb-insert-link (&optional arg) + "Insert a link to an Org-roam bibliography note. +If the note does not exist yet, it will be created using +`orb-edit-note' function. + +\\\\ The +customization option `orb-insert-link-description' determines +what will be used as the link's description. It is possible to +override the default value of the variable with a numerical +prefix ARG: + +`C-1' \\[orb-insert-link] will force `title' +`C-2' \\[orb-insert-link] will force `citekey' + +`C-0' \\[orb-insert-link] will force `citation-org-ref-2' +`C-9' \\[orb-insert-link] will force `citation-org-ref-3' +`C-8' \\[orb-insert-link] will force `citation-org-cite' + +If a region of text is active (selected) when calling `orb-insert-link', +the text in the region will be replaced with the link and the +text string will be used as the link's description — similar to +`org-roam-node-insert'. + +Normally, the case of the link description will be preserved. It +is possible to force lowercase by supplying either one or three +universal arguments `\\[universal-argument]'. + +Finally, `bibtex-completion-cache' will be re-populated if either +two or three universal arguments `\\[universal-argument]' are supplied. + +The customization option `orb-insert-interface' allows to set the +completion interface backend for the candidates list." + (interactive "P") + ;; parse arg + ;; C-u or C-u C-u C-u => force lowercase + ;; C-u C-u or C-u C-u C-u => force `bibtex-completion-clear-cache' + ;; C-1 force title in description + ;; C-2 force citekey in description + ;; C-0,C-9,C-8 force inserting the link as Org-ref org Org-cite citation + (let* ((lowercase (or (equal arg '(4)) + (equal arg '(64)))) + (clear-cache (or (equal arg '(16)) + (equal arg '(64)))) + (link-type (cl-case arg + (1 'title) + (2 'citekey) + (0 'citation-org-ref-2) + (9 'citation-org-ref-3) + (8 'citation-org-cite) + (t nil))) + (orb-insert-link-description + (or link-type orb-insert-link-description)) + (orb-insert-lowercase (or lowercase orb-insert-lowercase))) + (orb-make-notes-cache) + (cl-case orb-insert-interface + (helm-bibtex + (cond + ((fboundp 'orb-helm-insert) + (orb-helm-insert clear-cache)) + (t + (orb-warning "helm-bibtex not available; using generic completion") + (orb-insert-generic clear-cache)))) + (ivy-bibtex + (cond + ((fboundp 'orb-ivy-insert) + (orb-ivy-insert clear-cache)) + (t + (orb-warning "ivy-bibtex not available; using generic completion") + (orb-insert-generic clear-cache)))) + (t + (orb-insert-generic clear-cache))))) + + +;; ============================================================================ +;;; Non-ref functions +;; ============================================================================ + +;; ;;;###autoload +;; (defun orb-find-non-ref-file (&optional initial-prompt) +;; "Find and open an Org-roam, non-ref file. +;; INITIAL-PROMPT is the initial title prompt. +;; See `org-roam-node-finds' and +;; `orb--get-non-ref-path-completions' for details." +;; (interactive) +;; (org-roam-node-find initial-prompt +;; (orb--get-non-ref-path-completions))) + +;; ;;;###autoload +;; (defun orb-insert-non-ref () +;; "Find a non-ref Org-roam file, and insert a relative org link to it at point. +;; If PREFIX, downcase the title before insertion. See +;; `org-roam-insert' and `orb--get-non-ref-path-completions' for +;; details." +;; (interactive) +;; ;; FIXME: this is not correct +;; (org-roam-node-insert (orb--get-non-ref-path-completions))) + + +;; ============================================================================ +;;; Orb note actions +;; ============================================================================ + +(orb-note-actions-defun default + (let ((f (cdr (assoc (completing-read name candidates) candidates)))) + (funcall f (list citekey)))) + +(orb-note-actions-defun ido + (let* ((c (cl-map 'list 'car candidates)) + (f (cdr (assoc (ido-completing-read name c) candidates)))) + (funcall f (list citekey)))) + +(declare-function orb-note-actions-hydra/body "org-roam-bibtex" nil t) +(orb-note-actions-defun hydra + ;; we don't use candidates here because for a nice hydra we need each + ;; group of completions separately (default, extra, user), so just + ;; silence the compiler + (ignore candidates) + (let ((k ?a) + actions) + (dolist (type (list "Default" "Extra" "User")) + (let ((actions-var + (intern (concat "orb-note-actions-" (downcase type))))) + (dolist (action (symbol-value actions-var)) + ;; this makes defhydra HEADS list of the form: + ;; ("a" (some-action citekey-value) "Some-action description" + ;; :column "Type") + (cl-pushnew + `(,(format "%c" k) (,(cdr action) (list ,citekey)) + ,(car action) :column ,(concat type " actions")) + actions) + ;; increment key a->b->c... + (setq k (1+ k))))) ; TODO: figure out a way to supply + ; mnemonic keys + (setq actions (nreverse actions)) + ;; yes, we redefine hydra on every call + (eval + `(defhydra orb-note-actions-hydra (:color blue :hint nil) + ;; defhydra docstring + ,(format "^\n %s \n\n^" + (s-word-wrap (- (window-body-width) 2) name)) + ;; defhydra HEADS + ,@actions))) + (orb-note-actions-hydra/body)) + +(defun orb-note-actions--run (interface citekey ) + "Run note actions on CITEKEY with INTERFACE." + (when (and (memq interface '(ivy helm hydra)) + (not (featurep interface))) + (orb-warning + (format "Feature `%s' not available, using default interface" interface)) + (setq interface 'default)) + (funcall (intern (concat "orb-note-actions-" (symbol-name interface))) + citekey)) + +;;;###autoload +(defun orb-note-actions () + "Run an interactive prompt to offer note-related actions. +The prompt interface can be set in `orb-note-actions-interface'. +In addition to default actions, which are not supposed to be +modified, there is a number of prefined extra actions +`orb-note-actions-extra' that can be customized. Additionally, +user actions can be set in `orb-note-actions-user'." + (interactive) + (if-let ((non-default-interfaces (list 'hydra 'ido 'ivy 'helm)) + (citekey (orb-get-node-citekey nil 'assert))) + (cond ((memq orb-note-actions-interface non-default-interfaces) + (orb-note-actions--run orb-note-actions-interface citekey)) + ((functionp orb-note-actions-interface) + (funcall orb-note-actions-interface citekey)) + (t + (unless (eq orb-note-actions-interface 'default) + (orb-warning + (format "Feature `%s' not available, using default interface" + orb-note-actions-interface))) + (orb-note-actions--run 'default citekey))) + (user-error "Could not retrieve the citekey. Check ROAM_REFS property \ +of current node"))) + +(defun orb-note-actions-copy-citekey (citekey) + "Save note's citation key to `kill-ring' and copy it to clipboard. +CITEKEY is a list whose car is a citation key." + (with-temp-buffer + (insert (car citekey)) + (copy-region-as-kill (point-min) (point-max)))) + +(defun orb-note-actions-scrape-pdf (citekey) + "Wrapper around `refuse-run'. +CITEKEY is a list whose car is a citation key." + (require 'refuse) + (refuse-run (car citekey))) + + +;; ============================================================================ +;;; Plug-in functions for other packages +;; ============================================================================ + +(defvar orb--external-vars-original-values nil + "Variable to hold original values of variables from external packages. +Internal use.") + +;;;###autoload +(defun orb-org-ref-edit-note (citekey) + "Open an Org-roam note associated with the CITEKEY or create a new one. +Set `org-ref-notes-function' to this function if your +bibliography notes are managed by Org-roam and you want some +extra integration between the two packages. + +This is a wrapper function around `orb-edit-note' intended for +use with Org-ref. + +NOTE: This function is no longer needed for Org-ref v3." + (when (require 'org-ref nil t) + (let ((bibtex-completion-bibliography (org-ref-find-bibliography))) + (orb-edit-note citekey)))) + +;;;###autoload +(defun orb-citar-edit-note (citekey _entry) + "Open an Org-roam note associated with the CITEKEY or create a new one. +This is a wrapper function around `orb-edit-note' meant to be used with +`citar-file-open-note-function'. +Argument ENTRY is ignored." + (orb-edit-note citekey)) + +;;;###autoload +(defun orb-bibtex-completion-edit-note (keys) + "Open or create an Org-roam note. + +This is a wrapper function around `orb-edit-note' meant to be +used with `bibtex-completion-edit-notes-function'. + +Only the first KEY of the list KEYS will actually be used. KEY +must be a string." + (orb-edit-note (car keys))) + (provide 'orb-core) ;;; orb-core.el ends here ;; Local Variables: diff --git a/orb-helm.el b/orb-helm.el index 4946e7a..45c6f1e 100644 --- a/orb-helm.el +++ b/orb-helm.el @@ -5,8 +5,6 @@ ;; Author: Mykhailo Shevchuk ;; 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 @@ -23,10 +21,8 @@ ;; this program; see the file LICENSE. If not, visit ;; . -;;; Commentary: - ;;; Code: - +;; ;; ============================================================================ ;;;; Dependencies ;; ============================================================================ @@ -36,7 +32,7 @@ (require 'helm-bibtex) (require 'helm-source) -(declare-function orb-insert-edit-note "org-roam-bibtex" (citekey)) +(declare-function orb-insert-edit-note "orb-core" (citekey)) (defvar orb-note-actions-default) (defvar orb-note-actions-extra) diff --git a/orb-ivy.el b/orb-ivy.el index a4a46cc..4705b98 100644 --- a/orb-ivy.el +++ b/orb-ivy.el @@ -5,8 +5,6 @@ ;; Author: Mykhailo Shevchuk ;; 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 @@ -23,10 +21,8 @@ ;; this program; see the file LICENSE. If not, visit ;; . -;;; Commentary: - ;;; Code: - +;; ;; ============================================================================ ;;;; Dependencies ;; ============================================================================ @@ -35,7 +31,7 @@ (require 'ivy-bibtex) -(declare-function orb-insert-edit-note "org-roam-bibtex" (citekey)) +(declare-function orb-insert-edit-note "orb-core" (citekey)) (defvar orb-note-actions-default) (defvar orb-note-actions-extra) diff --git a/org-roam-bibtex.el b/org-roam-bibtex.el index ed55178..1b27e29 100644 --- a/org-roam-bibtex.el +++ b/org-roam-bibtex.el @@ -48,1028 +48,21 @@ ;; an associated BibTeX entry ;; - `orb-note-actions': call a dispatcher of useful note actions ;; -;; Soft dependencies: Org-ref, Citar, Helm, Ivy, Hydra, Projectile, Persp-mode +;; Soft dependencies: Org-ref, Citar, Helm, Ivy, Hydra, Projectile, Persp-mode, +;; Refuse, Bibkey ;;; Code: - +;; ;; ============================================================================ ;;;; Dependencies ;; ============================================================================ (require 'orb-core) -(require 'cl-lib) - -(eval-when-compile - (require 'subr-x)) - -;; declare own functions and variables -(declare-function orb-helm-insert "orb-helm") -(declare-function orb-note-actions-helm "orb-helm") -(declare-function orb-ivy-insert "orb-ivy") -(declare-function orb-note-actions-ivy "orb-ivy") -(declare-function refuse-run "refuse" (key)) - -;; declare external functions and variables - -;; Projectile, Perspective-mode -(declare-function projectile-relevant-open-projects "ext:projectile") -(declare-function persp-switch "ext:persp-mode") -(declare-function persp-names "ext:persp-mode") - -;; Org-ref -(defvar org-ref-notes-function) -(declare-function org-ref-find-bibliography "ext:org-ref-core") -;; -;; Citar -(defvar citar-open-note-function) - -;; Hydra -(declare-function defhydra "ext:hydra") - -;; ============================================================================ -;;;; Customize definitions -;; ============================================================================ - -(defcustom orb-preformat-templates t - "Non-nil to enable template pre-expanding. -See `orb-edit-note' for details." - :type '(choice - (const :tag "Yes" t) - (const :tag "No" nil)) - :group 'org-roam-bibtex) - -(defcustom orb-preformat-keywords - '("citekey" "entry-type" "date" "pdf?" "note?" "file" - "author" "editor" "author-abbrev" "editor-abbrev" - "author-or-editor-abbrev") - "A list of template placeholders for pre-expanding. -Any BibTeX field can be set for pre-expanding including -Bibtex-completion virtual fields such as '=key=' and '=type='. -BibTeX fields can be referred to by means of their aliases -defined in `orb-bibtex-field-aliases'. - -Usage example: - -\(setq orb-preformat-keywords '(\"citekey\" \"author\" \"date\")) -\(setq orb-templates - '((\"r\" \"reference\" plain - \"#+ROAM_KEY: %^{citekey}%? -%^{author} published %^{entry-type} in %^{date}: fullcite:%\\1.\" - :target - (file+head \"references/${citekey.org}\" \"#+title: ${title}\n\") - :unnarrowed t))) - -Special cases: - -The \"file\" keyword will be treated specially if the value of -`orb-process-file-keyword' is non-nil. See its docstring for an -explanation. - -This variable takes effect when `orb-preformat-templates' is set -to t (default). See also `orb-edit-note' for further details. - -Consult Bibtex-completion documentation for additional -information on BibTeX field names." - :type '(repeat :tag "BibTeX field names" string) - :group 'org-roam-bibtex) - -(defcustom orb-process-file-keyword t - "Whether to treat the file keyword specially during template pre-expanding. -When this variable is non-nil, the \"%^{file}\" and \"${file}\" -wildcards will be processed by `org-process-file-field' rather -than simply replaced with the field value. This may be useful in -situations when the file field contains several file names and -only one file name is desirable for retrieval. The \"file\" -keyword must be set for pre-expanding in `orb-preformat-keywords' -as usual. - -If this variable is `string', for example \"my-file\", use its -value as the wildcard keyword instead of the default \"file\" -keyword. Thus, it will be possible to get both the raw file -field value by expanding the %^{file} and ${file} wildcards and a -single file name by expanding the %^{my-file} and ${my-file} -wildcards. The keyword, e.g. \"my-file\", must be set for -pre-expanding in `orb-preformat-keywords' as usual. - -The variable `orb-attached-file-extensions' controls filtering of -file names based on file extensions." - ;; TODO: check if a custom string is really working as described - :group 'org-roam-bibtex - :type '(choice - (const :tag "Yes" t) - (const :tag "No" nil) - (string :tag "Custom wildcard keyword"))) - -(defcustom orb-roam-ref-format 'org-ref-v2 - "Defines the format of citation key in the `ROAM_REFS' property. -Should be one of the following symbols: -- `org-ref-v2': Old Org-ref `cite:links' -- `org-ref-v3': New Org-ref `cite:&links' -- `org-cite' : Org-cite `@elements' - -This can also be a custom `format' string with a single `%s' specifier." - :type '(radio - (const :tag "Org-ref v2" org-ref-v2) - (const :tag "Org-ref v3" org-ref-v3) - (const :tag "Org-cite" org-cite) - (string :tag "Custom format string")) - :group 'org-roam-bibtex) - -(defcustom orb-bibtex-entry-get-value-function #'bibtex-completion-apa-get-value - "Function to be used by ORB for values from a BibTeX entry. - -The default value of this variable is `bibtex-completion-apa-get-value', -which offers some post-formatting for author fields. - -Another possible choice available out of the box is -`bibtex-completion-get-value', which returns a verbatim value. - -Set this to a custom function if you need more flexibility. -This function should take two arguments FIELD-NAME and ENTRY. -FIELD-NAME is the name of the field whose value should be retrieved. -ENTRY is a BibTeX entry as returned by `bibtex-completion-get-entry'." - :risky t - :group 'org-roam-bibtex - :type '(radio (function-item bibtex-completion-apa-get-value) - (function-item bibtex-completion-get-value) - (function :tag "Custom function"))) - -(defcustom orb-persp-project `("notes" . ,org-roam-directory) - "Perspective name and path to the project with bibliography notes. -A cons cell (PERSP-NAME . PROJECT-PATH). Only relevant when -`orb-switch-persp' is set to t. - -PERSP-NAME should be a valid Perspective name, PROJECT-PATH should be -an open Projectile project. - -See `orb-edit-note' for details" - :type '(cons (string :tag "Perspective name") - (directory :tag "Projectile directory")) - :group 'org-roam-bibtex) - -(defcustom orb-switch-persp nil - "Non-nil to enable switching to the notes perspective. -Set the name of the perspective and the path to the notes project -in `orb-persp-project' for this to take effect. - -Perspective switching works with Pers-mode and Projectile." - :type '(choice - (const :tag "Yes" t) - (const :tag "No" nil)) - :group 'org-roam-bibtex) - -(defcustom orb-ignore-bibtex-store-link-functions - '(org-bibtex-store-link) - "Functions to override with `ignore' during note creation process. - -Org Ref defines function `org-ref-bibtex-store-link' to store -links to a BibTeX buffer, e.g. with `org-store-link'. At the -same time, Org ref requires `ol-bibtex' library, which defines -`org-bibtex-store-link' to do the same. When creating a note -with `orb-edit-note' from a BibTeX buffer, for example by calling -`org-ref-open-bibtex-notes', the initiated `org-capture' process -implicitly calls `org-store-link'. The latter loops through all -the functions for storing links, and if more than one function -can store links to the location, the BibTeX buffer in this -particular case, the user will be prompted to choose one. This -is definitely annoying, hence ORB will advise all functions in -this list to return nil to trick `org-capture' and get rid of the -prompt. - -The default value is `(org-bibtex-store-link)', which means this -function will be ignored and `org-ref-bibtex-store-link' will be -used to store a link to the BibTeX buffer. See -`org-capture-templates' on how to use the link in your templates." - :type '(repeat (function)) - :risky t - :group 'org-roam-bibtex) - -(defcustom orb-insert-interface 'generic - "Interface frontend to use with `orb-insert-link'. -Possible values are the symbols `helm-bibtex', `ivy-bibtex', or -`generic' (default). In the first two cases the respective -commands will be used, while in the latter case the command -`orb-insert-generic' will be used. - -When using `helm-bibtex' or `ivy-bibtex' as `orb-insert-interface', -choosing the action \"Edit note & insert a link\" will insert the -desired link. For convenience, this action is made default for -the duration of an `orb-insert-link' session. It will not -persist when `helm-bibtex' or `ivy-bibtex' proper are run. -Otherwise, the command is just the usual `helm-bibtex'/`ivy-bibtex'. -For example, it is possible to run other `helm-bibtex' or -`ivy-bibtex' actions. When action other than \"Edit note & -insert a link\" is run, no link will be inserted, although the -session can be resumed later with `helm-resume' or `ivy-resume', -respectively, where it will be possible to select the \"Edit note -& insert a link\" action. - -When using the `generic' interface, a simple list of available -citation keys is presented using `completion-read' and after -choosing a candidate the appropriate link will be inserted. - -Please note that this variable should be set using the Customize -interface, `use-package''s `:custom' keyword, or Doom's `setq!' -macro. Simple `setq' will not work." - :group 'org-roam-bibtex - :type '(radio - (const helm-bibtex) - (const ivy-bibtex) - (const generic)) - :set (lambda (var value) - (cond - ((eq value 'ivy-bibtex) - (require 'orb-ivy)) - ((eq value 'helm-bibtex) - (require 'orb-helm))) - (set-default var value))) - -(defcustom orb-insert-link-description 'title - "Link description format for links created with `orb-insert-link'. -The command `orb-insert-link' can be used to create Org-mode -links to bibliographic notes of type [[id:note_id][Description]]. -This variable determines the 'Description' part from the example -above. It is an `s-format' string, where special placeholders of -form '${field}' will be expanded with data from the respective -BibTeX field of the associated BibTeX entry. If the field's -value cannot be retrieved, the user will be prompted to input a -value interactively. When retrieving BibTeX data, the user -options `orb-bibtex-field-aliases' and -`orb-bibtex-entry-get-value-function' are respected. - -This variable can also be one of the following symbols: - -`title' - equivalent to \"${title}\" -`citekey' - equivalent to \"${citekey}\" -`citation-org-ref-2' - create Org-ref v2 'cite:citekey' citation instead -`citation-org-ref-3' - create Org-ref v3 'cite:&citekey' citation instead -`citation-org-cite' - create Org-cite '[cite:@citekey]' citation instead - -The default value set by this variable can be overriden by -calling `orb-insert-link' with an appropriated numerical prefix -argument. See its docstring for more information." - :group 'org-roam-bibtex - :type '(choice - (string :tag "Format string") - (const :tag "Title" title) - (const :tag "Citation key" citekey) - (const :tag "Citation link" citation-org-ref-2) - (const :tag "Citation link" citation-org-ref-3) - (const :tag "Citation link" citation-org-cite))) - -(defcustom orb-insert-follow-link nil - "Whether to follow a newly inserted link." - :group 'orb-roam-bibtex - :type '(choice - (const :tag "Yes" t) - (const :tag "No" nil))) - -(defcustom orb-insert-generic-candidates-format 'key - "Format of selection candidates for `orb-insert-generic' interface. -Possible values are `key' and `entry'." - :group 'org-roam-bibtex - :type '(choice - (const key) - (const entry))) - -(defcustom orb-note-actions-interface 'default - "Interface frontend for `orb-note-actions'. -Supported values (interfaces) are symbols `default', `ido', -`hydra', `ivy' and `helm'. - -Alternatively, it can be set to a function, in which case the -function should expect one argument CITEKEY, which is a list -whose car is the citation key associated with the org-roam note -the current buffer is visiting. Also, it should ideally make use -of `orb-note-actions-default', `orb-note-actions-extra' and -`orb-note-actions-user' for providing an interactive interface, -through which the combined set of note actions is presented as a -list of candidates and the function associated with the candidate -is executed upon selecting it. - -This variable should be set using the Customize interface, -`use-package''s `:custom' keyword, or Doom's `setq!' macro. -Simple `setq' will not work." - :risky t - :type '(radio - (const :tag "Default" default) - (const :tag "Ido" ido) - (const :tag "Hydra" hydra) - (const :tag "Ivy" ivy) - (const :tag "Helm" helm) - (function :tag "Custom function")) - :set (lambda (var value) - (cond - ((eq value 'ivy) - (require 'orb-ivy)) - ((eq value 'helm) - (require 'orb-helm)) - ((eq value 'hydra) - (require 'hydra))) - (set-default var value)) - :group 'orb-note-actions) - -(defcustom orb-note-actions-default - '(("Open PDF file(s)" . orb-open-attached-file) - ("Add PDF to library" . bibtex-completion-add-pdf-to-library) - ("Open URL or DOI in browser" . bibtex-completion-open-url-or-doi) - ("Show record in the bibtex file" . bibtex-completion-show-entry)) - "Default actions for `orb-note-actions'. -Each action is a cons cell DESCRIPTION . FUNCTION." - :risky t - :type '(alist - :tag "Default actions for `orb-note-actions'" - :key-type (string :tag "Description") - :value-type (function :tag "Function")) - :group 'orb-note-actions) - -(defcustom orb-note-actions-extra - '(("Save citekey to kill-ring and clipboard" . orb-note-actions-copy-citekey) - ("Run Orb PDF Scrapper" . orb-note-actions-scrape-pdf)) - "Extra actions for `orb-note-actions'. -Each action is a cons cell DESCRIPTION . FUNCTION." - :risky t - :type '(alist - :tag "Extra actions for `orb-note-actions'" - :key-type (string :tag "Description") - :value-type (function :tag "Function")) - :group 'orb-note-actions) - -(defcustom orb-note-actions-user nil - "User actions for `orb-note-actions'. -Each action is a cons cell DESCRIPTION . FUNCTION." - :risky t - :type '(alist - :tag "User actions for `orb-note-actions'" - :key-type (string :tag "Description") - :value-type (function :tag "Function")) - :group 'orb-note-actions) - -;; ============================================================================ -;;;; Orb edit notes -;; ============================================================================ - -(defun orb--switch-perspective () - "Helper function for `orb-edit-note'." - (when (and (require 'projectile nil t) - (require 'persp-mode nil t)) - (let ((notes-project (cdr orb-persp-project)) - (projects (projectile-relevant-open-projects)) - openp) - (dolist (project projects openp) - (setq openp (or (f-equal? project notes-project) openp))) - (when openp - (let ((p-names (cdr (persp-names)))) - (dolist (p-name p-names) - (when (s-equals? p-name (car orb-persp-project)) - (persp-switch p-name)))))))) - -(defun orb--store-link-functions-advice (action) - "Add or remove advice for each of `orb-ignore-bibtex-store-link-functions'. -ACTION should be a symbol `add' or `remove'. A piece of advice -is the function `ignore', it is added as `:override'." - (when orb-ignore-bibtex-store-link-functions - (let ((advice-func (intern (format "advice-%s" action))) - (advice (cl-case action - (add (list :override #'ignore)) - (remove (list #'ignore)) - (t (user-error "Action type not recognised: %s" action))))) - (dolist (advisee orb-ignore-bibtex-store-link-functions) - (apply advice-func (push advisee advice)))))) - -(defun orb--pre-expand-template (template entry) - "Helper function for `orb--new-note'. -TEMPLATE is an element of `org-roam-capture-templates' and ENTRY -is a BibTeX entry as returned by `bibtex-completion-get-entry'." - ;; Handle org-roam-capture part - (letrec (;; Org-capture templates: handle different types of - ;; org-capture-templates: string, file and function; this is - ;; a stripped down version of `org-capture-get-template' - (org-template - (pcase (nth 3 template) ; org-capture template is here - (`nil 'nil) - ((and (pred stringp) tmpl) tmpl) - (`(file ,file) - (let ((flnm (expand-file-name file org-directory))) - (if (file-exists-p flnm) (f-read-text flnm) - (format "Template file %S not found" file)))) - (`(function ,fun) - (if (functionp fun) (funcall fun) - (format "Template function %S not found" fun))) - (_ (user-error "ORB: Invalid capture template")))) - ;; org-roam capture properties are here - (plst (cddddr template)) - ;; regexp for org-capture prompt wildcard - (rx "\\(%\\^{[[:alnum:]-_]*}\\)") - (file-keyword (when orb-process-file-keyword - (or (and (stringp orb-process-file-keyword) - orb-process-file-keyword) - "file"))) - ;; inline function to handle :target list expansion - (expand-roam-template - (lambda (roam-template-list old new) - (let (elements) - (dolist (el roam-template-list) - (if (listp el) - (setq elements - (nreverse - (append elements - (list (funcall expand-roam-template - el old new))))) - (push (s-replace old new el) elements))) - (nreverse elements)))) - (lst nil)) - ;; First run: - ;; 1) Make a list of (org-wildcard field-value match-position) for the - ;; second run - ;; 2) replace org-roam-capture wildcards - (dolist (keyword orb-preformat-keywords) - (let* (;; prompt wildcard keyword - (keyword (cond - ;; for some backward compatibility with old - ;; `orb-preformat-keywords' - ((consp keyword) (car keyword)) - ((stringp keyword) keyword) - (t (user-error "Error in `orb-preformat-keywords': \ -Keyword \"%s\" has invalid type (string was expected)" keyword)))) - ;; bibtex field name - (field-name (orb-resolve-field-alias keyword)) - ;; get the bibtex field value - (field-value - ;; maybe process file keyword - (or (if (and file-keyword (string= field-name file-keyword)) - (prog1 - (orb-get-attached-file - (funcall orb-bibtex-entry-get-value-function "=key=" entry)) - ;; we're done so don't even compare file-name with - ;; file-keyword in the successive cycles - (setq file-keyword nil)) - ;; do the usual processing otherwise - ;; condition-case to temporary workaround an upstream bug - (condition-case nil - (funcall orb-bibtex-entry-get-value-function field-name entry) - (error ""))) - "")) - ;; org-capture prompt wildcard - (org-wildcard (concat "%^{" (or keyword "citekey") "}")) - ;; org-roam-capture prompt wildcard - (roam-wildcard (concat "${" (or keyword "citekey") "}")) - ;; org-roam-capture :target property - (roam-template (or (plist-get plst :if-new) (plist-get plst :target))) - (i 1) ; match counter - pos) - ;; Search for org-wildcard, set flag m if found - (when org-template - (while (string-match rx org-template pos) - (if (string= (match-string 1 org-template) org-wildcard) - (progn - (setq pos (length org-template)) - (cl-pushnew (list org-wildcard field-value i) lst )) - (setq pos (match-end 1) - i (1+ i))))) - ;; Replace placeholders in org-roam-capture-templates :target property - (when roam-template - (setcdr roam-template - (funcall expand-roam-template - (cdr roam-template) roam-wildcard field-value))))) - ;; Second run: replace prompts and prompt matches in org-capture - ;; template string - (dolist (l lst) - (when (and org-template (nth 1 l)) - (let ((pos (concat "%\\" (number-to-string (nth 2 l))))) - ;; replace prompt match wildcards with prompt wildcards - ;; replace prompt wildcards with BibTeX field value - (setq org-template (s-replace pos (car l) org-template) - org-template (s-replace (car l) (nth 1 l) org-template)))) - (setf (nth 3 template) org-template)) - template)) - -(defun orb--new-note (citekey &optional props) - "Process templates and run `org-roam-capture-'. -CITEKEY is the citation key of an entry for which the note is -created. PROPS are additional properties for `org-roam-capture-'." - ;; Check if the requested BibTeX entry actually exists and fail - ;; gracefully otherwise - (if-let* ((entry (or (bibtex-completion-get-entry citekey) - (orb-warning - "Could not find the BibTeX entry" citekey))) - ;; Depending on the templates used: run - ;; `org-roam-capture--capture' or call `org-roam-node-find' - (org-capture-templates org-roam-capture-templates) - ;; hijack org-capture-templates - ;; entry is our bibtex entry, it just happens that - ;; `org-capture' calls a single template entry "entry"; - (template (--> (if (null (cdr org-capture-templates)) - ;; if only one template is defined, use it - (car org-capture-templates) - (org-capture-select-template)) - (when (listp it) - (copy-tree it)) - ;; optionally pre-expand templates - (if (and it orb-preformat-templates) - (orb--pre-expand-template it entry) - it))) - ;; pretend we had only one template - ;; `org-roam-capture--capture' behaves specially in this case - ;; NOTE: this circumvents using functions other than - ;; `org-capture', see `org-roam-capture-function'. - ;; If the users start complaining, we may revert previous - ;; implementation - (org-roam-capture-templates (list template)) - ;; Org-roam coverts the templates to its own syntax; - ;; since we are telling `org-capture' to use the template entry - ;; (by setting `org-capture-entry'), and Org-roam converts the - ;; whole template list, we must do the conversion of the entry - ;; ourselves - (props (--> (or props (list :finalize 'find-file)) - (plist-put it :call-location (point-marker)))) - (org-capture-entry - (org-roam-capture--convert-template template props)) - (citekey-ref (format - (pcase orb-roam-ref-format - ('org-ref-v2 "cite:%s") - ('org-ref-v3 "cite:&%s") - ('org-cite "@%s") - ((pred stringp) orb-roam-ref-format) - (_ (user-error "Invalid format `orb-roam-ref-format'"))) - citekey)) - (title - (or (funcall orb-bibtex-entry-get-value-function "title" entry) - (and - (orb-warning "Title not found for this entry") - ;; this is not critical, the user may input their own - ;; title - "No title"))) - (node (org-roam-node-create :title title))) - (org-roam-capture- - :node node - :info (list :ref citekey-ref)) - (user-error "Abort"))) - -;;;###autoload -(defun orb-edit-note (citekey) - "Open an Org-roam note associated with the CITEKEY or create a new one. - -This function allows to use Org-roam as a backend for managing -bibliography notes. It relies on `bibtex-completion' to get -retrieve bibliographic information from a BibTeX file. - -Implementation details and features: - -1. This function first calls `org-roam-find-ref' trying to find -the note file associated with the CITEKEY. The Org-roam key can -be set with '#+ROAM_KEY:' in-buffer keyword. - -2. If the Org-roam reference has not been found, the function -calls `org-roam-node-find' passing to it the title associated -with the CITEKEY as retrieved by `bibtex-completion-get-entry'. -The prompt presented by `org-roam-node-find' will thus be -pre-populated with the record title. - -3. Optionally, when `orb-preformat-templates' is non-nil, any -prompt wildcards in `orb-templates' or -`org-roam-capture-templates', associated with the bibtex record -fields as specified in `orb-preformat-templates', will be -preformatted. Both `org-capture-templates' (%^{}) and -`org-roam-capture-templates' (`s-format', ${}) prompt syntaxes -are supported. - -See `orb-preformat-keywords' for more details on how -to properly specify prompts for replacement. - -Please pay attention when using this feature that by setting -title for preformatting, it will be impossible to change it in -the `org-roam-node-find' interactive prompt since all the -template expansions will have taken place by then. All the title -wildcards will be replace with the BibTeX field value. - -4. Optionally, if you are using Projectile and Persp-mode and -have a dedicated workspace to work with your Org-roam collection, -you may want to set the perspective name and project path in -`orb-persp-project' and `orb-switch-persp' to t. In this case, -the perspective will be switched to the Org-roam notes project -before calling any Org-roam functions. - -If optional argument ENTRY is non-nil, use it to fetch the -bibliographic information." - ;; Optionally switch to the notes perspective - (when orb-switch-persp - (orb--switch-perspective)) - (orb-make-notes-cache) - (if-let ((node (orb-note-exists-p citekey))) - (ignore-errors (org-roam-node-visit node)) - ;; fix some Org-ref related stuff - (orb--store-link-functions-advice 'add) - ;; TODO: consider using unwind-protect and let the errors through - (condition-case error-msg - (orb--new-note citekey) - ((debug error) - (orb--store-link-functions-advice 'remove) - (message "%s" - (concat (and (eq (car error-msg) 'error) - "orb-edit-note caught an error during capture: ") - (error-message-string error-msg))))))) - -;; FIXME: this does not work anymore -;; (defun orb--get-non-ref-path-completions () -;; "Return a list of cons for titles of non-ref notes to absolute path. -;; CANDIDATES is a an alist of candidates to consider. Defaults to -;; `org-roam--get-title-path-completions' otherwise." -;; (let* ((rows (org-roam-db-query -;; [:select [titles:file titles:title tags:tags] -;; :from titles -;; :left :join tags -;; :on (= titles:file tags:file) -;; :left :join refs :on (= titles:file refs:file) -;; :where refs:file :is :null])) -;; completions) -;; (dolist (row rows completions) -;; (pcase-let ((`(,file-path ,title ,tags) row)) -;; (let ((title (or title -;; (list (org-roam--path-to-slug file-path))))) -;; (let ((k (concat -;; (when tags -;; (format "(%s) " (s-join org-roam-tag-separator tags))) -;; title)) -;; (v (list :path file-path :title title))) -;; (push (cons k v) completions))))))) - + ;; ============================================================================ -;;;; Orb insert +;;; Org-roam-bibtex minor mode ;; ============================================================================ -(defvar orb-insert-lowercase nil - "Internal. Dynamic variable for `orb-insert-link' and `orb-insert--link'.") - -(defun orb-insert--link (node info) - "Insert a link to NODE. -INFO contains additional information." - ;; citekey &optional description lowercase region-text beg end - (-let (((&plist :region :orb-link-description :orb-citekey :orb-entry) info)) - (when region - (org-roam-unshield-region (car region) (cdr region)) - (delete-region (car region) (cdr region)) - (set-marker (car region) nil) - (set-marker (cdr region) nil)) - (pcase orb-link-description - ((pred stringp) - (let ((description - (--> - (s-format orb-link-description - (lambda (template entry) - (funcall orb-bibtex-entry-get-value-function - (orb-resolve-field-alias template) entry)) - orb-entry) - (if (and it orb-insert-lowercase) (downcase it) it)))) - (insert (org-link-make-string - (concat "id:" (org-roam-node-id node)) description)))) - (`citation-org-cite - (insert (format "[cite:@%s]" orb-citekey))) - (ref-format - (let ((cite-link (if (boundp 'org-ref-default-citation-link) - (concat org-ref-default-citation-link ":") - "cite:"))) - (insert (concat cite-link - (when (eq ref-format 'citation-org-ref-3) "&") - orb-citekey))))))) - -(defun org-roam-capture--finalize-orb-insert-link () - "Insert a link to a just captured note. -This function is used by ORB calls to `org-roam-capture-' instead -of `org-roam-capture--finalize-insert-link'." - (let* ((mkr (org-roam-capture--get :call-location)) - (buf (marker-buffer mkr)) - (region (org-roam-capture--get :region)) - (node (org-roam-populate (org-roam-node-create :id (org-roam-capture--get :id)))) - (citekey (org-capture-get :orb-citekey)) - (entry (bibtex-completion-get-entry citekey))) - (with-current-buffer buf - (org-with-point-at mkr - (orb-insert--link node (list - :region region - :orb-citekey citekey - :orb-entry entry - :orb-link-description (org-capture-get :orb-link-description))))))) - -(defun orb--insert-captured-ref-h () - "Insert value of `:ref' key from `org-roam-capture--info'. -Internal function. To be installed in `org-roam-capture-new-node-hook'." - (when-let ((ref (plist-get org-roam-capture--info :ref))) - (org-roam-ref-add ref))) - -(defun orb-insert-edit-note (citekey) - "Insert a link to a note with citation key CITEKEY. -Capture a new note if it does not exist yet. - -CITEKEY can be a list of citation keys (for compatibility with -Bibtex-completion), in which case only the first element of that -list is used." - (unwind-protect - ;; Group functions together to avoid inconsistent state on quit - (atomic-change-group - (let* ((citekey (cl-typecase citekey - (string citekey) - (list (car citekey)) - (t (user-error "Invalid citation key data type: %s. \ -String or list of strings expected" citekey)))) - (entry (bibtex-completion-get-entry citekey)) - (title - (funcall orb-bibtex-entry-get-value-function "title" entry "")) - (node (or (orb-note-exists-p citekey) - (org-roam-node-create :title title))) - region-text - beg end - (_ (when (region-active-p) - (setq beg (set-marker (make-marker) (region-beginning))) - (setq end (set-marker (make-marker) (region-end))) - (setq region-text - (buffer-substring-no-properties beg end)))) - (description (--> orb-insert-link-description - (cl-case it - (title "${title}") - (citekey "${citekey}") - (t it)) - (or region-text it))) - (info (--> (list :orb-link-description description - :orb-citekey citekey - :orb-entry entry - :finalize 'orb-insert-link) - (if (and beg end) - (append it (list :region (cons beg end))) - it)))) - (if (org-roam-node-id node) - (orb-insert--link node info) - (orb--new-note citekey info))) - (deactivate-mark))) - (when (and orb-insert-follow-link - (looking-at org-link-any-re)) - (org-open-at-point))) - -(defun orb-insert-generic (&optional arg) - "Present a list of BibTeX entries for completion. -This is a generic completion function for `orb-insert-link', which -runs `orb-insert-edit-note' on the selected entry. The list is -made by `bibtex-completion-candidates'. - -The appearance of selection candidates is determined by -`orb-insert-generic-candidates-format'. - -This function is not interactive, set `orb-insert-interface' to -`generic' and call `orb-insert-link' interactively instead. - -If ARG is non-nil, rebuild `bibtex-completion-cache'." - (when arg - (bibtex-completion-clear-cache)) - (bibtex-completion-init) - (let* ((candidates (bibtex-completion-candidates)) - (candidates2 - (if (eq orb-insert-generic-candidates-format 'key) - (mapcar (lambda (item) - (alist-get "=key=" (cdr item) nil nil #'equal)) - candidates) - (mapcar #'car candidates))) - (selection (completing-read "BibTeX entry:" candidates2 nil t)) - (citekey (if (eq orb-insert-generic-candidates-format 'key) - selection - (--> (alist-get selection candidates nil nil #'equal) - (cdr it) - (alist-get "=key=" it nil nil #'equal))))) - (orb-insert-edit-note citekey))) - -;;;###autoload -(defun orb-insert-link (&optional arg) - "Insert a link to an Org-roam bibliography note. -If the note does not exist yet, it will be created using -`orb-edit-note' function. - -\\\\ The -customization option `orb-insert-link-description' determines -what will be used as the link's description. It is possible to -override the default value of the variable with a numerical -prefix ARG: - -`C-1' \\[orb-insert-link] will force `title' -`C-2' \\[orb-insert-link] will force `citekey' - -`C-0' \\[orb-insert-link] will force `citation-org-ref-2' -`C-9' \\[orb-insert-link] will force `citation-org-ref-3' -`C-8' \\[orb-insert-link] will force `citation-org-cite' - -If a region of text is active (selected) when calling `orb-insert-link', -the text in the region will be replaced with the link and the -text string will be used as the link's description — similar to -`org-roam-node-insert'. - -Normally, the case of the link description will be preserved. It -is possible to force lowercase by supplying either one or three -universal arguments `\\[universal-argument]'. - -Finally, `bibtex-completion-cache' will be re-populated if either -two or three universal arguments `\\[universal-argument]' are supplied. - -The customization option `orb-insert-interface' allows to set the -completion interface backend for the candidates list." - (interactive "P") - ;; parse arg - ;; C-u or C-u C-u C-u => force lowercase - ;; C-u C-u or C-u C-u C-u => force `bibtex-completion-clear-cache' - ;; C-1 force title in description - ;; C-2 force citekey in description - ;; C-0,C-9,C-8 force inserting the link as Org-ref org Org-cite citation - (let* ((lowercase (or (equal arg '(4)) - (equal arg '(64)))) - (clear-cache (or (equal arg '(16)) - (equal arg '(64)))) - (link-type (cl-case arg - (1 'title) - (2 'citekey) - (0 'citation-org-ref-2) - (9 'citation-org-ref-3) - (8 'citation-org-cite) - (t nil))) - (orb-insert-link-description - (or link-type orb-insert-link-description)) - (orb-insert-lowercase (or lowercase orb-insert-lowercase))) - (orb-make-notes-cache) - (cl-case orb-insert-interface - (helm-bibtex - (cond - ((fboundp 'orb-helm-insert) - (orb-helm-insert clear-cache)) - (t - (orb-warning "helm-bibtex not available; using generic completion") - (orb-insert-generic clear-cache)))) - (ivy-bibtex - (cond - ((fboundp 'orb-ivy-insert) - (orb-ivy-insert clear-cache)) - (t - (orb-warning "ivy-bibtex not available; using generic completion") - (orb-insert-generic clear-cache)))) - (t - (orb-insert-generic clear-cache))))) - -;; ============================================================================ -;;;; Non-ref functions -;; ============================================================================ - -;; ;;;###autoload -;; (defun orb-find-non-ref-file (&optional initial-prompt) -;; "Find and open an Org-roam, non-ref file. -;; INITIAL-PROMPT is the initial title prompt. -;; See `org-roam-node-finds' and -;; `orb--get-non-ref-path-completions' for details." -;; (interactive) -;; (org-roam-node-find initial-prompt -;; (orb--get-non-ref-path-completions))) - -;; ;;;###autoload -;; (defun orb-insert-non-ref () -;; "Find a non-ref Org-roam file, and insert a relative org link to it at point. -;; If PREFIX, downcase the title before insertion. See -;; `org-roam-insert' and `orb--get-non-ref-path-completions' for -;; details." -;; (interactive) -;; ;; FIXME: this is not correct -;; (org-roam-node-insert (orb--get-non-ref-path-completions))) - -;; ============================================================================ -;;;; Orb note actions -;; ============================================================================ - -(orb-note-actions-defun default - (let ((f (cdr (assoc (completing-read name candidates) candidates)))) - (funcall f (list citekey)))) - -(orb-note-actions-defun ido - (let* ((c (cl-map 'list 'car candidates)) - (f (cdr (assoc (ido-completing-read name c) candidates)))) - (funcall f (list citekey)))) - -(declare-function orb-note-actions-hydra/body "org-roam-bibtex" nil t) -(orb-note-actions-defun hydra - ;; we don't use candidates here because for a nice hydra we need each - ;; group of completions separately (default, extra, user), so just - ;; silence the compiler - (ignore candidates) - (let ((k ?a) - actions) - (dolist (type (list "Default" "Extra" "User")) - (let ((actions-var - (intern (concat "orb-note-actions-" (downcase type))))) - (dolist (action (symbol-value actions-var)) - ;; this makes defhydra HEADS list of the form: - ;; ("a" (some-action citekey-value) "Some-action description" - ;; :column "Type") - (cl-pushnew - `(,(format "%c" k) (,(cdr action) (list ,citekey)) - ,(car action) :column ,(concat type " actions")) - actions) - ;; increment key a->b->c... - (setq k (1+ k))))) ; TODO: figure out a way to supply - ; mnemonic keys - (setq actions (nreverse actions)) - ;; yes, we redefine hydra on every call - (eval - `(defhydra orb-note-actions-hydra (:color blue :hint nil) - ;; defhydra docstring - ,(format "^\n %s \n\n^" - (s-word-wrap (- (window-body-width) 2) name)) - ;; defhydra HEADS - ,@actions))) - (orb-note-actions-hydra/body)) - -(defun orb-note-actions--run (interface citekey ) - "Run note actions on CITEKEY with INTERFACE." - (when (and (memq interface '(ivy helm hydra)) - (not (featurep interface))) - (orb-warning - (format "Feature `%s' not available, using default interface" interface)) - (setq interface 'default)) - (funcall (intern (concat "orb-note-actions-" (symbol-name interface))) - citekey)) - -;;;###autoload -(defun orb-note-actions () - "Run an interactive prompt to offer note-related actions. -The prompt interface can be set in `orb-note-actions-interface'. -In addition to default actions, which are not supposed to be -modified, there is a number of prefined extra actions -`orb-note-actions-extra' that can be customized. Additionally, -user actions can be set in `orb-note-actions-user'." - (interactive) - (if-let ((non-default-interfaces (list 'hydra 'ido 'ivy 'helm)) - (citekey (orb-get-node-citekey nil 'assert))) - (cond ((memq orb-note-actions-interface non-default-interfaces) - (orb-note-actions--run orb-note-actions-interface citekey)) - ((functionp orb-note-actions-interface) - (funcall orb-note-actions-interface citekey)) - (t - (unless (eq orb-note-actions-interface 'default) - (orb-warning - (format "Feature `%s' not available, using default interface" - orb-note-actions-interface))) - (orb-note-actions--run 'default citekey))) - (user-error "Could not retrieve the citekey. Check ROAM_REFS property \ -of current node"))) - -;;;;;; Note actions - -(defun orb-note-actions-copy-citekey (citekey) - "Save note's citation key to `kill-ring' and copy it to clipboard. -CITEKEY is a list whose car is a citation key." - (with-temp-buffer - (insert (car citekey)) - (copy-region-as-kill (point-min) (point-max)))) - -(defun orb-note-actions-scrape-pdf (citekey) - "Wrapper around `refuse-run'. -CITEKEY is a list whose car is a citation key." - (require 'refuse) - (refuse-run (car citekey))) - -;; ============================================================================ -;;;; Org-roam-bibtex minor mode -;; ============================================================================ -;; - -(defvar orb--external-vars-original-values nil - "Variable to hold original values of variables from external packages. -Internal use.") - -;;;###autoload -(defun orb-org-ref-edit-note (citekey) - "Open an Org-roam note associated with the CITEKEY or create a new one. -Set `org-ref-notes-function' to this function if your -bibliography notes are managed by Org-roam and you want some -extra integration between the two packages. - -This is a wrapper function around `orb-edit-note' intended for -use with Org-ref. - -NOTE: This function is no longer needed for Org-ref v3." - (when (require 'org-ref nil t) - (let ((bibtex-completion-bibliography (org-ref-find-bibliography))) - (orb-edit-note citekey)))) - -;;;###autoload -(defun orb-citar-edit-note (citekey _entry) - "Open an Org-roam note associated with the CITEKEY or create a new one. -This is a wrapper function around `orb-edit-note' meant to be used with -`citar-file-open-note-function'. -Argument ENTRY is ignored." - (orb-edit-note citekey)) - -;;;###autoload -(defun orb-bibtex-completion-edit-note (keys) - "Open or create an Org-roam note. - -This is a wrapper function around `orb-edit-note' meant to be -used with `bibtex-completion-edit-notes-function'. - -Only the first KEY of the list KEYS will actually be used. KEY -must be a string." - (orb-edit-note (car keys))) (defvar org-roam-bibtex-mode-map (make-sparse-keymap) From ef6303a86a1978b96e1dbc696cdc13f400b8c1bd Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Sat, 8 Jan 2022 21:57:50 +0100 Subject: [PATCH 4/5] (int,doc) remove ORB PDF Scrapper and Autokey docs - remove ORB PDF Scrapper, ORB Anystyle and Orb Autokey sections from the manual - rename doc/ into docs/ - update .gitignore --- .gitignore | 4 + doc/orb-manual.org | 857 -------------------------------------------- docs/orb-manual.org | 434 ++++++++++++++++++++++ 3 files changed, 438 insertions(+), 857 deletions(-) delete mode 100644 doc/orb-manual.org create mode 100644 docs/orb-manual.org diff --git a/.gitignore b/.gitignore index 20914f5..0ee114b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ *.elc +*.info +*.html + +.DS_Store # Added automatically by ‘eldev init’. /.eldev diff --git a/doc/orb-manual.org b/doc/orb-manual.org deleted file mode 100644 index b636424..0000000 --- a/doc/orb-manual.org +++ /dev/null @@ -1,857 +0,0 @@ -#+TITLE: Org Roam BibTeX manual -#+STARTUP: entitiesplain noindent -#+OPTIONS: ^:nil todo:nil tags:nil num:2 H:1 prop:nil p:t broken-links:mark - -This manual is work in progress and is not complete. For basic commands see -[[file:../README.md]]. - -The following sections use Emacs Lisp examples to configure Org Roam -BibTeX. User options can also be set via the Customize interface: run =M-x -customize= or from menu click =Options -> Customize Emacs -> Top Level -Customization Group= and search for =org-roam-bibtex=. - -* Org Roam BibTeX - BibTeX aware capture template expansion -:PROPERTIES: -:CUSTOM_ID: org-roam-bibtex---bibtex-aware-capture-template-expansion -:END: -** Template pre-expansion -:PROPERTIES: -:CUSTOM_ID: templates -:END: - -Org Roam BibTeX makes it possible to automatically pre-expand Org-capture -=%^{...}= and Org Roam-style =${...}= template placeholders with values of -field or fields of a BibTeX entry for which the note is being created. - -Here's an example of how to add a basic template for a bibliography note to -=org-roam-capture-templates=: - -#+begin_src elisp -(setq org-roam-capture-templates - '(;; ... other templates - ;; bibliography note template - ("r" "bibliography reference" plain "%?" - :target - (file+head "references/${citekey}.org" "#+title: ${title}\n") - :unnarrowed t))) -#+end_src - -If there are more than one template in =org-roam-capture-templates=, you will -be prompted for the key of the template you want to use (=r= in the example -above). Otherwise, the only template will be used without prompting. - -*** User option =orb-roam-ref-format= - -This option defines the format style of a citation key in the =ROAM_REFS= -property. Supported are Org-ref v2, Org-ref v3 and Org-cite styles: - -- =org-ref-v2= (default): use the old Org-ref =cite:link= format -- =org-ref-v3=: use the new Org-ref =cite:&link= format -- =org-cite=: use the Org-cite =@element= format - -This can also be a custom =format= string. - -It should be noted that for a typical Org-roam use these styles are mostly -cosmetic. - -*** User option =orb-preformat-keywords= -:PROPERTIES: -:CUSTOM_ID: orb-preformat-keywords -:END: - -A list of template placeholders for pre-expanding. Any BibTeX field can be set -for preformatting including Bibtex-completion virtual fields such as =key=' and -'=type='. BibTeX fields can be referred by their aliases defined in -[[#orb-bibtex-field-aliases][=orb-bibtex-field-aliases=]]. - -Usage example: - -#+begin_src elisp -(setq orb-preformat-keywords '("citekey" "author" "date")) -(setq org-roam-capture-templates - '(("r" "bibliography reference" plain - "%? -%^{author} published %^{entry-type} in %^{date}: fullcite:%\\1." - :target - (file+head "references/${citekey}.org" "#+title: ${title}\n") - :unnarrowed t))) -#+end_src - -By default, =orb-preformat-keywords= is configured to expand the following -BibTeX fields: "citekey", "date", "entry-type", "pdf?", "note?", "author", -"editor", "author-abbrev", "editor-abbrev", "author-or-editor-abbrev". - -Special cases: - -- The "file" keyword will be treated specially if the value of - `orb-process-file-keyword' is non-nil. See its docstring for an - explanation. -- The "title" keyword needs not to be set for preformatting if it is used only - within the =:target= section of a template. - -This variable takes effect when =orb-preformat-templates= is set to t -(default). See also =orb-edit-note= for further details. - -Consult the [[https://github.com/tmalsburg/helm-bibtex][=bibtex-completion=]] package for additional information about BibTeX -field names. - -*** User option =orb-bibtex-field-aliases= -:PROPERTIES: -:CUSTOM_ID: orb-bibtex-field-aliases -:END: - -** Handling BibTeX attachments -*** User option =orb-process-file-keyword= - -If =orb-process-file-keyword= is non-nil, the "file" field will be treated -specially. If the field contains only one file name, its value will be used for -template expansion. If it contains several file names, the user will be -prompted to choose one. The file names can be filtered based on their -extensions by setting the =orb-attached-file-extensions= variable, so that only -those matching the extension or extensions will be considered for -retrieval. The "file" keyword must be set for preformatting as usual. Consult -the docstrings of these variables for additional customization options. - -*** User option =orb-abbreviate-file-name= - -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. - -*** User option =orb-attached-file-extensions= - -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. - -*** User option =orb-use-bibdesk-attachments= - -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=. - -** Command =orb-insert-link= - -The command =orb-insert-link= can be used to create Org-mode links to -bibliographic notes of type =[[id:note_id][Description]]=. It is similar to -the Org-roam's command =org-roam-node-insert=. The difference between the two -is that the Org-roam's version creates a link to any existing Org-roam note -("node") or creates a new note if it does not exist. The ORB's version -consults the bibliography file and lets you create a link to an existing note -associated with a BibTeX entry or create a new note for an entry that doesn't -have one yet. - -The =Description= part of the link is controlled by the user option -=orb-insert-link-description=, which see. The global setting can be overriden -for a single invocation with a numerical prefix: - -- =C-1 M-x orb-insert-link= forces =title= -- =C-2 M-x orb-insert-link= forces =citekey= -- =C-8 M-x orb-insert-link= forces =citation-org-cite= -- =C-9 M-x orb-insert-link= forces =citation-org-ref-3= -- =C-0 M-x orb-insert-link= forces =citation-org-ref-2= - -If a region of text is active (selected) when calling =orb-insert-link=, the -text in the region will be replaced with a link and the region's text will be -used as link description — similar to =org-roam-node-insert=. - -Normally, the case of the link description will be preserved. It is possible -to force lowercase by supplying either one or three universal arguments =C-u=. - -Finally, =bibtex-completion-cache= will be re-populated if either two or three -universal arguments =C-u= are supplied. - -** =orb-insert= configuration -:PROPERTIES: -:CUSTOM_ID: orb-insert-configuration -:END: - -** User option =orb-insert-interface= -:PROPERTIES: -:CUSTOM_ID: orb-insert-interface -:END: - -Interface to use with =orb-insert=. Supported interfaces are =helm-bibtex=, -=ivy-bibtex=, and =generic= (=orb-insert-generic=) - -When using =helm-bibtex= or =ivy-bibtex= as =orb-insert-interface=, choosing -the action \"Edit note & insert a link\" will insert the desired link. For -convenience, this action is made default for the duration of an -=orb-insert-link= session. It will not persist when =helm-bibtex= or -=ivy-bibtex= proper are run. Otherwise, the command is just the usual -=helm-bibtex=/=ivy-bibtex=. For example, it is possible to run other -=helm-bibtex= or =ivy-bibtex= actions. When action other than \"Edit note & -insert a link\" is run, no link will be inserted, although the session can be -resumed later with =helm-resume= or =ivy-resume=, respectively, where it will -be possible to select the \"Edit note & insert a link\" action. - -When using the =generic= interface, a simple list of available citation keys is -presented using =completion-read= and after choosing a candidate the -appropriate link will be inserted. - -Please note that this variable should be set using the Customize interface, -=use-package='s =:custom= keyword, or Doom's =setq!= macro. Simple =setq= will -not work. - -** User option =orb-insert-link-description= -:PROPERTIES: -:CUSTOM_ID: orb-insert-link-description -:END: - -This variable determines what piece of information should be used as link -description when creating a link with =orb-insert-link=: - -This variable determines the 'Description' part from the example above. It is -an =s-format= string, where special placeholders of form "${field}" will be -expanded with data from the respective BibTeX field of the associated BibTeX -entry. If the value of the field cannot be retrieved, the user will be -prompted to input a value interactively. When retrieving BibTeX data, the user -options =orb-bibtex-field-aliases= and =orb-bibtex-entry-get-value-function= -are respected. - -This variable can also be one of the following symbols: - -- =title= - equivalent to "${title}" -- =citekey= - equivalent to "${citekey}" - -When this is set to one of the following symbols, create a citation instead of -an Org-mode link: - -- =citation-org-ref-2= - insert an Org-ref v2 citation link, use - =org-ref-default-citation-link=, default "cite:citation-key" -- =citation-org-ref-3= - insert an Org-ref v3 citation link, use - =org-ref-default-citation-link=, default "cite:&citation-key" -- =citation-org-cite= - insert an Org-cite citation [cite:@citation-key] - -In other words, =orb-insert-link= can behave like a BibTeX-aware version of -=org-roam-node-insert= and like an Org-roam-aware version of =org-cite-insert= -(or =org-ref-insert-cite-link= or =citar-insert-citation=) depending on the -user choice. - -The global vale of this option can be overriden for a single invocation of -=orb-insert-link= with a numerical prefix: - -- =C-1 M-x orb-insert-link= forces =title= -- =C-2 M-x orb-insert-link= forces =citekey= -- =C-8 M-x orb-insert-link= forces =citation-org-cite= -- =C-9 M-x orb-insert-link= forces =citation-org-ref-3= -- =C-0 M-x orb-insert-link= forces =citation-org-ref-2= - -** User option =orb-insert-follow-link= -:PROPERTIES: -:CUSTOM_ID: orb-insert-follow-link -:END: - -Whether to follow the newly created link. - -** User option =orb-insert-generic-candidates-format= -:PROPERTIES: -:CUSTOM_ID: orb-insert-generic-candidates-format -:END: -How the selection candidates should be presented when using =generic= -interface: - -- =key= - only citation keys. Fast and pretty, but too little contextual - information -- =entry= - formatted entry. More information, but not particluarly - pretty. Consider using =helm-bibtex= or =ivy-bibtex= instead. - -** Tips and tricks -:PROPERTIES: -:CUSTOM_ID: tips-and-tricks -:END: -*** Handling long templates -:PROPERTIES: -:CUSTOM_ID: handling-long-templates -:END: -Long templates can be placed in a separate file, with template expansion -of BibTeX fields working as usual: - -#+begin_src elisp -(setq org-roam-capture-templates - '(("r" "bibliography reference" plain - (file "/path/to/template.org") ; <-- template store in a separate file - :target - (file+head "references/${citekey}.org" "#+title: ${title}\n") - :unnarrowed t))) -#+end_src - -Content of =path/to/template.org=: - -#+begin_src org -,#+PROPERTY: type %^{entry-type} -,#+FILETAGS: %^{keywords} -,#+PROPERTY: authors %^{author} - -In this %\1 %\3 concluded that %? - -fullcite:%\1 -#+end_src - -You can also use a function to generate the template on the fly, see -=org-capture-templates= for details. - -*** Org-noter integration. Special treatment of the "file" keyword -:PROPERTIES: -:CUSTOM_ID: org-noter-integration.-special-treatment-of-the-file-keyword -:END: - -Below is an example of a template ready for use with [[https://github.com/weirdNox/org-noter][org-noter]] or [[https://github.com/rudolfochrist/interleave][interleave]]: - -#+begin_src elisp -(setq orb-preformat-keywords - '("citekey" "title" "url" "author-or-editor" "keywords" "file") - orb-process-file-keyword t - orb-attached-file-extensions '("pdf")) - -(setq org-roam-capture-templates - '(("r" "bibliography reference" plain - (file "/path/to/template") - :target - (file+head "references/${citekey}.org" "#+title: ${title}\n")))) -#+end_src - -Content of =path/to/template.org=: - -#+begin_src org -- tags :: -- keywords :: %^{keywords} - -,* %^{title} -:PROPERTIES: -:Custom_ID: %^{citekey} -:URL: %^{url} -:AUTHOR: %^{author-or-editor} -:NOTER_DOCUMENT: %^{file} ; <== special file keyword: if more than one filename -:NOTER_PAGE: ; is available, the user will be prompted to choose -:END: -#+end_src - -* ORB Note Actions - BibTeX record-related commands -:PROPERTIES: -:CUSTOM_ID: orb-note-actions---bibtex-record-related-commands -:END: -** Overview -:PROPERTIES: -:CUSTOM_ID: overview -:END: - -Type =M-x orb-note-actions= or bind this command to a key such as =C-c n a= to -quickly access additional commands that take the note's BibTeX key as an input -and process it to perform some useful actions. - -Note actions are divided into three groups: =default=, =extra=, and =user= set -via =orb-note-actions-default=, =orb-note-actions-extra=, -=orb-note-actions-user=, respectively. There is no big conceptual difference -between the three except that the =default= note actions are commands provided -by =bibtex-completion=, =extra= note actions are extra commands provided by -=org-roam-bibtex=, and =user= note actions are left for user customization. - -** Note actions interface -:PROPERTIES: -:CUSTOM_ID: note-actions-interface -:END: -There is a number of interfaces available for displaying the available -note actions: =default= (using =completing-read=), =ido=, =ivy=, =helm= -and =hydra=. The interface can be set via the -=orb-note-actions-interface= user variable. - -#+begin_example - (setq orb-note-actions-interface 'hydra) -#+end_example - -Alternatively, =orb-note-actions-interface= can be set to a custom function -that will provide completion for available note actions. The function must take -one argument CITEKEY, which is a list whose =car= is the current note's -citation key: - -#+begin_example - (setq orb-note-actions-interface #'my-orb-note-actions-interface) -#+end_example - -NOTE: This variable should be set using the Customize interface, -=use-package='s =:custom= keyword, or Doom's =setq!= macro. Simple =setq= will -not work. - -#+begin_src org -:PROPERTIES: -:ID: uuid1234-... -:ROAM_REFS: cite:Doe2020 -:END: -,#+title: My note -#+end_src - -#+begin_example - (defun my-orb-note-actions-interface (citekey) - ;;; For the above note, (car citekey) => "Doe2020" - ...) -#+end_example - -** Adding new note actions -:PROPERTIES: -:CUSTOM_ID: adding-new-note-actions -:END: -To install a note action, add a cons cell of format -=(DESCRIPTION . FUNCTION)= to one of the note actions variables: - -#+begin_example - (with-eval-after-load 'orb-note-actions - (add-to-list 'orb-note-actions-user (cons "My note action" #'my-note-action))) -#+end_example - -A note action must take a single argument CITEKEY, which is a list whose -car is the current note's citation key: - -#+begin_example - (defun my-note-action (citekey) - (let ((key (car citekey))) - ...)) -#+end_example - -* ORB PDF Scrapper - Retrieve references from PDFs -:PROPERTIES: -:CUSTOM_ID: orb-pdf-scrapper---retrieve-references-from-pdfs -:END: -** Overview -:PROPERTIES: -:CUSTOM_ID: overview-1 -:END: -ORB PDF Scrapper is an Emacs interface to -[[https://github.com/inukshuk/anystyle][=anystyle=]], an open-source -software based on powerful machine-learning algorithms. It requires -=anystyle-cli=, which can be installed with -=[sudo] gem install anystyle-cli=. Note that =ruby= and =gem= must -already be present in the system. =ruby= is shipped with MacOS, but you -will have to install it on other operating systems; please refer to the -relevant section in the official documentation for =ruby=. You may also -want to consult the [[https://rubydoc.info/gems/anystyle][=anystyle= -documentation]] to learn more about how it works. - -Once =anystyle-cli= is installed, ORB PDF Scrapper can be launched with -=orb-note-actions= while in an Org-roam buffer containing a -=#+ROAM_KEY:= BibTeX key. References are retrieved from a PDF file -associated with the note which is retrieved from the corresponding -BibTeX record. - -The reference-retrieval process consists of three interactive steps -described below. - -** Text mode -:PROPERTIES: -:CUSTOM_ID: text-mode -:END: -In the first step, the PDF file is searched for references, which are -eventually output in the ORB PDF Scrapper buffer as plain text. The -buffer is in the =text-mode= major-mode for editing general text files. - -You need to review the retrieved references and prepare them for the -next step in such a way that there is only one reference per line. You -may also need to remove any extra text captured together with the -references. Some PDF files will produce a nicely-formed list of -references that will require little to no manual editing, while others -will need a different degree of manual intervention. - -Generally, it is possible to train a custom =anystyle= finder model -responsible for PDF-parsing to improve the output quality, but this is -not currently supported by ORB PDF Scrapper. As a small and somewhat -naïve aid, the =sanitize text= command bound to =C-c C-u= may assist in -putting each reference onto a separate line. - -After you are finished with editing the text data, press =C-c C-c= to -proceed to the second step. - -Press =C-x C-s= to save your progress or =C-x C-w= to write the text -references into a file. - -Press =C-c C-k= anytime to abort the ORB PDF Scrapper process. - -** BibTeX mode -:PROPERTIES: -:CUSTOM_ID: bibtex-mode -:END: -In the second step, the obtained list of plain text references, one -reference per line, is parsed and converted into BibTeX format. The -resulting BibTeX records are presented to the user in the ORB PDF -Scrapper buffer replacing the text references. The buffer's major mode -switches to =bibtex-mode=, which is helpful for reviewing and editing -the BibTeX data and correcting possible parsing errors. - -Again, depending on the citation style used in the particular book or -article, the parsing quality can vary greatly and might require more or -less manual post-editing. It is possible to train a custom =anystyle= -parser model to improve the parsing quality. See -[[#training-a-parser-model][Training a Parser model]] for more details. - -Press =C-c C-u= to generate BibTeX keys for the records in the buffer or -=C-u C-c C-u= to generate a key for the record at point. See -[[#orb-autokey-configuration][ORB Autokey configuration]] on how to -configure the BibTeX key generation. During key generation, it is also -possible to automatically set the values of BibTeX fields: see -=orb-pdf-scrapper-set-fields= docstring for more details. - -Press =C-x C-s= to save your progress or =C-x C-w= to write the BibTeX -entries into a file. - -Press =C-c C-r= to return to the text-editing mode in its last state. -Note that all the progress in BibTeX mode will be lost. - -Press =C-c C-c= to proceed to the third step. If the BibTeX buffer was -edited and the changes were not saved, e.g. by pressing =C-x C-s=, you -will be prompted to generated BibTeX keys by default. The variable -=orb-pdf-prompt-to-generate-keys= more finely controls this behaviour. - -** Org mode -:PROPERTIES: -:CUSTOM_ID: org-mode -:END: -In the third step, the BibTeX records are processed internally by ORB -PDF Scrapper, and the result replaces the BibTeX data in the ORB PDF -Scrapper, which switches to =org-mode=. - -The processing involves sorting the references into four groups under -the respective Org-mode headlines: =in-roam=, =in-bib=, =valid=, and -=invalid=, and inserting the grouped references as either an Org-mode -plain-list of =org-ref=-style citations, or an Org-mode table with -columns corresponding to different BibTeX fields. - -- =in-roam= --- These references have notes with the respective - =#+ROAM_KEY:= citation keys in the =org-roam= database. -- =in-bib= --- These references are not yet in the =org-roam= database - but they are present in user BibTeX file(s) (see - =bibtex-completion-bibliography=). -- =invalid= --- These references matched against - =orb-pdf-scrapper-invalid-key-pattern= and are considered invalid. - Adjust this variable to your criteria of validity. -- =valid= --- All other references fall into this group. They look fine - but are not yet in user Org-roam and BibTeX databases. - -Set =orb-pdf-scrapper-group-references= to nil if you do not need -reference grouping. - -Review and edit the generated Org-mode data, or press =C-c C-c= to -insert the references into the note's buffer and finish the ORB PDF -Scrapper. - -Press =C-x C-s= to save your progress or =C-x C-w= to write the Org data -into a file. - -Press =C-c C-r= to return to BibTeX editing mode in its last state. Note -that all the progress in current mode will be lost. - -The following user variables control the appearance of the generated -Org-mode data: =orb-pdf-scrapper-group-references=, -=orb-pdf-scrapper-grouped-export=, =orb-pdf-scrapper-ungrouped-export=, -=orb-pdf-scrapper-table-export-fields=, =orb-pdf-scrapper-list-style=, -=orb-pdf-scrapper-reference-numbers=, =orb-pdf-scrapper-citekey-format=. -These variables can be set through the Customize interface or with -=setq=. Refer to their respective docstrings in Emacs for more -information. - -** Exporting data generated by ORB PDF Scrapper -:PROPERTIES: -:CUSTOM_ID: exporting-data-generated-by-orb-pdf-scrapper -:END: -The different types of data generated by ORB PDF Scrapper -- text, -BibTeX and Org - can be exported to the buffer of origin or an external -file. By default, only the Org data is exported to the buffer of origin. -Different export options can be set in -=orb-pdf-scrapper-export-options=. Consult its docstring for a detailed -explanation. The following example demonstrates various possibilities. - -#+begin_example - (setq orb-pdf-scrapper-export-options - '((org ;; <= TYPE - ;; Export to a heading in the buffer of origin - (heading "References (extracted by ORB PDF Scrapper)" - ;; ^ ^ - ;; TARGET LOCATION - ;; PROPERTIES - ;; v - :property-drawer ("PDF_SCRAPPER_TYPE" - "PDF_SCRAPPER_SOURCE" - "PDF_SCRAPPER_DATE"))) - (txt - ;; Export to a file "references.org" - (path "references.org" - ;; under a heading "New references" - :placement - (heading "New references" - :property-drawer ("PDF_SCRAPPER_TYPE" - "PDF_SCRAPPER_SOURCE" - "PDF_SCRAPPER_DATE") - ;; Put the new heading in front of other headings - :placement prepend))) - (bib - ;; Export to a file in an existing directory. The file name will be CITEKEY.bib - (path "/path/to/references-dir/" - :placement prepend - ;; Include only the references that are not in the target file - ;; *and* the file(s) specified in bibtex-completion-bibliography - :filter-bib-entries bibtex-completion-bibliography)))) -#+end_example - -** Training a Parser model -:PROPERTIES: -:CUSTOM_ID: training-a-parser-model -:END: -*** Prerequisites -:PROPERTIES: -:CUSTOM_ID: prerequisites -:END: -Currently, the core data set (explained below) must be installed -manually by the user as follows: - -1. Use =find=, =locate= or similar tools to find the file =core.xml= - buried in =res/parser/= subdirectory of =anystyle= gem, - e.g. =locate core.xml | grep anystyle=. On MacOS, with =anystyle= - installed as a system gem, the file path would look similar to: - - ="/Library/Ruby/Gems/2.6.0/gems/anystyle-1.3.11/res/parser/core.xml"= - - The actual path will vary slightly depending on the - currently-installed versions of =ruby= and =anystyle=. - - On Linux and Windows, this path will be different. - -2. Copy this file into the location specified in - =orb-anystyle-parser-training-set=, or anywhere else where you have - disk-write access, and adjust the aforementioned variable - accordingly. - -*** Running a training session -:PROPERTIES: -:CUSTOM_ID: running-a-training-session -:END: -Training a custom parser model on custom user data will greatly improve -the parsing of plain-text references. A training session can be -initiated by pressing =C-c C-t= in the ORB PDF Scrapper buffer in either -text-mode or BibTeX-mode. In each case, the plain-text references -obtained in the =text mode= step described above will be used to -generate source XML data for a training set. - -The generated XML data replaces the text or the BibTeX references in the -ORB PDF Scrapper buffer, and the major-mode switches to =xml-mode=. - -The XML data must be edited manually---this is the whole point of -creating a custom training model---which usually consists in simply -correcting the placement of bibliographic data within the XML elements -(data fields). It is extremely important to review the source data -carefully since any mistakes here will make its way into the model, -thereby leading to poorer parsing in the future. - -It would be quite tedious to create the whole data-set by hand--- -hundreds or thousands of individual bibliographic records---so the best -workflow for making a good custom data-set is to use the core data-set -shipped with =anystyle= and append to it several data-sets generated in -ORB PDF Scrapper training sessions from individual PDF files, -incrementally re-training the model in between. This approach is -implemented in ORB PDF Scrapper. From personal experience, adding -references data incrementally from 4--5 PDF files raises the parser -success rate to virtually 100%. Follow the instructions described in -[[#parser-model-prerequisites][Prerequisites]] to install the core -data-set. - -Once the editing is done, press =C-c C-c= to train the model. The XML -data in the ORB PDF Scrapper buffer will be automatically appended to -the custom =core.xml= file which will be used for training. -Alternatively, press =C-c C-t= to review the updated =core.xml= file and -press =C-c C-c= when finished. - -The major mode will now switch to =fundamental-mode=, and the =anystyle= -=stdout= output will appear in the buffer. Training the model can take -/several minutes/, depending on the size of the training data-set and -the computing resources available on your device. The process is run in -a shell subprocess, so you will be able to continue your work and return -to ORB PDF Scrapper buffer later. - -Once the training is complete, press =C-c C-c= to return to the previous -editing-mode. You can now re-generate the BibTeX data and see the -improvements achieved with the re-trained model. - -* ORB Autokey - Automatically generate citation keys -:PROPERTIES: -:CUSTOM_ID: orb-autokey-configuration -:END: -** =orb-autokey-format= -:PROPERTIES: -:CUSTOM_ID: orb-autokey-format -:END: -You can specify the format of autogenerated BibTeX keys by setting the -=orb-autokey-format= variable through the Customize interface, or by -adding a =setq= form in your Emacs configuration file. - -ORB Autokey format currently supports the following wildcards: - -*** Basic -:PROPERTIES: -:CUSTOM_ID: basic -:END: -| Wildcard | Field | Description | -|------------+--------+----------------------------------------| -| %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 (date or year field) | -| %p | page | first page | -| %e{(expr)} | elisp | elisp expression | - -#+begin_example - (setq orb-autokey-format "%a%y") => "doe2020" -#+end_example - -*** Extended -:PROPERTIES: -:CUSTOM_ID: extended -:END: - -1. Capitalized versions: - -| Wildcard | Field | Description | -|-----------+--------+--------------------------------------| -| %A | author | | -| %T | title | Same as %a,%t,%f{field} but | -| %F{field} | field | preserve the original capitalization | - -#+begin_example - (setq orb-autokey-format "%A%y") => "Doe2020" -#+end_example - -2. Starred versions - -| Wildcard | Field | Description | -|----------+--------+--------------------------------------------------------| -| %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" | - -#+begin_example - (setq orb-autokey-format "%A*%y") => "DoeJohn2020" -#+end_example - -3. Optional parameters - -| Wildcard | Field | Description | -|--------------------+--------+---------------------------------------------------| -| %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. - -#+begin_example - (setq orb-autokey-format "%A*[1][4][-]%y") => "DoeJ2020" - (setq orb-autokey-format "%A*[2][7][-]:%y") => "DoeJohn-DoeJane:2020" -#+end_example - -4. Elisp expression - -- can be anything -- should return a string or nil -- will be evaluated before expanding other wildcards and therefore can - be used to 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. - -#+begin_example - %e{(or (bibtex-completion-get-value "volume" entry) "N/A")} - %e{(my-function entry)} -#+end_example - -*** Other variables -:PROPERTIES: -:CUSTOM_ID: other-variables -:END: -Check variables =orb-autokey-invalid-symbols=, -=orb-autokey-empty-field-token=, =orb-autokey-titlewords-ignore= for -additional settings. - -* Orb Anystyle - Emacs interfeace to Anystyle-CLI -:PROPERTIES: -:CUSTOM_ID: orb-anystyle -:END: -The function =orb-anystyle= provides a convenient Elisp key--value -interface to =anystyle-cli=, and can be used anywhere else within Emacs. -Check its docstring for more information. You may also want to consult -[[https://rubydoc.info/gems/anystyle][=anystyle-cli= documentation]]. - -** Example -:PROPERTIES: -:CUSTOM_ID: example -:END: -This Elisp expression: - -#+begin_example - (orb-anystyle 'parse - :format 'bib - :stdout nil - :overwrite t - :input "Doe2020.txt " - :output "bib" - :parser-model "/my/custom/model.mod") -#+end_example - -...executes the following anystyle call: - -#+begin_example - anystyle --no-stdout --overwrite -F "/my/custom/model.mod" -f bib parse "Doe2020.txt" "bib" -#+end_example - -The following variables can be used to configure =orb-anystyle= and the -default command-line options that will be passed to =anystyle=: - -** =orb-anystyle= -:PROPERTIES: -:CUSTOM_ID: orb-anystyle-1 -:END: - -- =orb-anystyle-executable= -- =orb-anystyle-user-directory= -- =orb-anystyle-default-buffer= - -** Default command-line options -:PROPERTIES: -:CUSTOM_ID: default-command-line-options -:END: - -- =orb-anystyle-find-crop= -- =orb-anystyle-find-layout= -- =orb-anystyle-find-solo= -- =orb-anystyle-finder-training-set= -- =orb-anystyle-finder-model= -- =orb-anystyle-parser-model= -- =orb-anystyle-parser-training-set= -- =orb-anystyle-pdfinfo-executable= -- =orb-anystyle-pdftotext-executable= diff --git a/docs/orb-manual.org b/docs/orb-manual.org new file mode 100644 index 0000000..755a853 --- /dev/null +++ b/docs/orb-manual.org @@ -0,0 +1,434 @@ +#+TITLE: Org Roam BibTeX manual +#+STARTUP: entitiesplain noindent +#+OPTIONS: ^:nil todo:nil tags:nil num:2 H:1 prop:nil p:t broken-links:mark + +This manual is work in progress and is not complete. For basic commands see +[[file:../README.md]]. + +The following sections use Emacs Lisp examples to configure Org Roam +BibTeX. User options can also be set via the Customize interface: run =M-x +customize= or from menu click =Options -> Customize Emacs -> Top Level +Customization Group= and search for =org-roam-bibtex=. + +* Org Roam BibTeX - BibTeX aware capture template expansion +:PROPERTIES: +:CUSTOM_ID: org-roam-bibtex---bibtex-aware-capture-template-expansion +:END: +** Template pre-expansion +:PROPERTIES: +:CUSTOM_ID: templates +:END: + +Org Roam BibTeX makes it possible to automatically pre-expand Org-capture +=%^{...}= and Org Roam-style =${...}= template placeholders with values of +field or fields of a BibTeX entry for which the note is being created. + +Here's an example of how to add a basic template for a bibliography note to +=org-roam-capture-templates=: + +#+begin_src elisp +(setq org-roam-capture-templates + '(;; ... other templates + ;; bibliography note template + ("r" "bibliography reference" plain "%?" + :target + (file+head "references/${citekey}.org" "#+title: ${title}\n") + :unnarrowed t))) +#+end_src + +If there are more than one template in =org-roam-capture-templates=, you will +be prompted for the key of the template you want to use (=r= in the example +above). Otherwise, the only template will be used without prompting. + +*** User option =orb-roam-ref-format= + +This option defines the format style of a citation key in the =ROAM_REFS= +property. Supported are Org-ref v2, Org-ref v3 and Org-cite styles: + +- =org-ref-v2= (default): use the old Org-ref =cite:link= format +- =org-ref-v3=: use the new Org-ref =cite:&link= format +- =org-cite=: use the Org-cite =@element= format + +This can also be a custom =format= string. + +It should be noted that for a typical Org-roam use these styles are mostly +cosmetic. + +*** User option =orb-preformat-keywords= +:PROPERTIES: +:CUSTOM_ID: orb-preformat-keywords +:END: + +A list of template placeholders for pre-expanding. Any BibTeX field can be set +for preformatting including Bibtex-completion virtual fields such as =key=' and +'=type='. BibTeX fields can be referred by their aliases defined in +[[#orb-bibtex-field-aliases][=orb-bibtex-field-aliases=]]. + +Usage example: + +#+begin_src elisp +(setq orb-preformat-keywords '("citekey" "author" "date")) +(setq org-roam-capture-templates + '(("r" "bibliography reference" plain + "%? +%^{author} published %^{entry-type} in %^{date}: fullcite:%\\1." + :target + (file+head "references/${citekey}.org" "#+title: ${title}\n") + :unnarrowed t))) +#+end_src + +By default, =orb-preformat-keywords= is configured to expand the following +BibTeX fields: "citekey", "date", "entry-type", "pdf?", "note?", "author", +"editor", "author-abbrev", "editor-abbrev", "author-or-editor-abbrev". + +Special cases: + +- The "file" keyword will be treated specially if the value of + `orb-process-file-keyword' is non-nil. See its docstring for an + explanation. +- The "title" keyword needs not to be set for preformatting if it is used only + within the =:target= section of a template. + +This variable takes effect when =orb-preformat-templates= is set to t +(default). See also =orb-edit-note= for further details. + +Consult the [[https://github.com/tmalsburg/helm-bibtex][=bibtex-completion=]] package for additional information about BibTeX +field names. + +*** User option =orb-bibtex-field-aliases= +:PROPERTIES: +:CUSTOM_ID: orb-bibtex-field-aliases +:END: + +** Handling BibTeX attachments +*** User option =orb-process-file-keyword= + +If =orb-process-file-keyword= is non-nil, the "file" field will be treated +specially. If the field contains only one file name, its value will be used for +template expansion. If it contains several file names, the user will be +prompted to choose one. The file names can be filtered based on their +extensions by setting the =orb-attached-file-extensions= variable, so that only +those matching the extension or extensions will be considered for +retrieval. The "file" keyword must be set for preformatting as usual. Consult +the docstrings of these variables for additional customization options. + +*** User option =orb-abbreviate-file-name= + +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. + +*** User option =orb-attached-file-extensions= + +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. + +*** User option =orb-use-bibdesk-attachments= + +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=. + +* Command =orb-insert-link= + +The command =orb-insert-link= can be used to create Org-mode links to +bibliographic notes of type =[[id:note_id][Description]]=. It is similar to +the Org-roam's command =org-roam-node-insert=. The difference between the two +is that the Org-roam's version creates a link to any existing Org-roam note +("node") or creates a new note if it does not exist. The ORB's version +consults the bibliography file and lets you create a link to an existing note +associated with a BibTeX entry or create a new note for an entry that doesn't +have one yet. + +The =Description= part of the link is controlled by the user option +=orb-insert-link-description=, which see. The global setting can be overriden +for a single invocation with a numerical prefix: + +- =C-1 M-x orb-insert-link= forces =title= +- =C-2 M-x orb-insert-link= forces =citekey= +- =C-8 M-x orb-insert-link= forces =citation-org-cite= +- =C-9 M-x orb-insert-link= forces =citation-org-ref-3= +- =C-0 M-x orb-insert-link= forces =citation-org-ref-2= + +If a region of text is active (selected) when calling =orb-insert-link=, the +text in the region will be replaced with a link and the region's text will be +used as link description — similar to =org-roam-node-insert=. + +Normally, the case of the link description will be preserved. It is possible +to force lowercase by supplying either one or three universal arguments =C-u=. + +Finally, =bibtex-completion-cache= will be re-populated if either two or three +universal arguments =C-u= are supplied. + +** =orb-insert= configuration +:PROPERTIES: +:CUSTOM_ID: orb-insert-configuration +:END: + +** User option =orb-insert-interface= +:PROPERTIES: +:CUSTOM_ID: orb-insert-interface +:END: + +Interface to use with =orb-insert=. Supported interfaces are =helm-bibtex=, +=ivy-bibtex=, and =generic= (=orb-insert-generic=) + +When using =helm-bibtex= or =ivy-bibtex= as =orb-insert-interface=, choosing +the action \"Edit note & insert a link\" will insert the desired link. For +convenience, this action is made default for the duration of an +=orb-insert-link= session. It will not persist when =helm-bibtex= or +=ivy-bibtex= proper are run. Otherwise, the command is just the usual +=helm-bibtex=/=ivy-bibtex=. For example, it is possible to run other +=helm-bibtex= or =ivy-bibtex= actions. When action other than \"Edit note & +insert a link\" is run, no link will be inserted, although the session can be +resumed later with =helm-resume= or =ivy-resume=, respectively, where it will +be possible to select the \"Edit note & insert a link\" action. + +When using the =generic= interface, a simple list of available citation keys is +presented using =completion-read= and after choosing a candidate the +appropriate link will be inserted. + +Please note that this variable should be set using the Customize interface, +=use-package='s =:custom= keyword, or Doom's =setq!= macro. Simple =setq= will +not work. + +** User option =orb-insert-link-description= +:PROPERTIES: +:CUSTOM_ID: orb-insert-link-description +:END: + +This variable determines what piece of information should be used as link +description when creating a link with =orb-insert-link=: + +This variable determines the 'Description' part from the example above. It is +an =s-format= string, where special placeholders of form "${field}" will be +expanded with data from the respective BibTeX field of the associated BibTeX +entry. If the value of the field cannot be retrieved, the user will be +prompted to input a value interactively. When retrieving BibTeX data, the user +options =orb-bibtex-field-aliases= and =orb-bibtex-entry-get-value-function= +are respected. + +This variable can also be one of the following symbols: + +- =title= - equivalent to "${title}" +- =citekey= - equivalent to "${citekey}" + +When this is set to one of the following symbols, create a citation instead of +an Org-mode link: + +- =citation-org-ref-2= - insert an Org-ref v2 citation link, use + =org-ref-default-citation-link=, default "cite:citation-key" +- =citation-org-ref-3= - insert an Org-ref v3 citation link, use + =org-ref-default-citation-link=, default "cite:&citation-key" +- =citation-org-cite= - insert an Org-cite citation [cite:@citation-key] + +In other words, =orb-insert-link= can behave like a BibTeX-aware version of +=org-roam-node-insert= and like an Org-roam-aware version of =org-cite-insert= +(or =org-ref-insert-cite-link= or =citar-insert-citation=) depending on the +user choice. + +The global vale of this option can be overriden for a single invocation of +=orb-insert-link= with a numerical prefix: + +- =C-1 M-x orb-insert-link= forces =title= +- =C-2 M-x orb-insert-link= forces =citekey= +- =C-8 M-x orb-insert-link= forces =citation-org-cite= +- =C-9 M-x orb-insert-link= forces =citation-org-ref-3= +- =C-0 M-x orb-insert-link= forces =citation-org-ref-2= + +** User option =orb-insert-follow-link= +:PROPERTIES: +:CUSTOM_ID: orb-insert-follow-link +:END: + +Whether to follow the newly created link. + +** User option =orb-insert-generic-candidates-format= +:PROPERTIES: +:CUSTOM_ID: orb-insert-generic-candidates-format +:END: +How the selection candidates should be presented when using =generic= +interface: + +- =key= - only citation keys. Fast and pretty, but too little contextual + information +- =entry= - formatted entry. More information, but not particluarly + pretty. Consider using =helm-bibtex= or =ivy-bibtex= instead. + +** Tips and tricks +:PROPERTIES: +:CUSTOM_ID: tips-and-tricks +:END: +*** Handling long templates +:PROPERTIES: +:CUSTOM_ID: handling-long-templates +:END: +Long templates can be placed in a separate file, with template expansion +of BibTeX fields working as usual: + +#+begin_src elisp +(setq org-roam-capture-templates + '(("r" "bibliography reference" plain + (file "/path/to/template.org") ; <-- template store in a separate file + :target + (file+head "references/${citekey}.org" "#+title: ${title}\n") + :unnarrowed t))) +#+end_src + +Content of =path/to/template.org=: + +#+begin_src org +,#+PROPERTY: type %^{entry-type} +,#+FILETAGS: %^{keywords} +,#+PROPERTY: authors %^{author} + +In this %\1 %\3 concluded that %? + +fullcite:%\1 +#+end_src + +You can also use a function to generate the template on the fly, see +=org-capture-templates= for details. + +*** Org-noter integration. Special treatment of the "file" keyword +:PROPERTIES: +:CUSTOM_ID: org-noter-integration.-special-treatment-of-the-file-keyword +:END: + +Below is an example of a template ready for use with [[https://github.com/weirdNox/org-noter][org-noter]] or [[https://github.com/rudolfochrist/interleave][interleave]]: + +#+begin_src elisp +(setq orb-preformat-keywords + '("citekey" "title" "url" "author-or-editor" "keywords" "file") + orb-process-file-keyword t + orb-attached-file-extensions '("pdf")) + +(setq org-roam-capture-templates + '(("r" "bibliography reference" plain + (file "/path/to/template") + :target + (file+head "references/${citekey}.org" "#+title: ${title}\n")))) +#+end_src + +Content of =path/to/template.org=: + +#+begin_src org +- tags :: +- keywords :: %^{keywords} + +,* %^{title} +:PROPERTIES: +:Custom_ID: %^{citekey} +:URL: %^{url} +:AUTHOR: %^{author-or-editor} +:NOTER_DOCUMENT: %^{file} ; <== special file keyword: if more than one filename +:NOTER_PAGE: ; is available, the user will be prompted to choose +:END: +#+end_src + +* ORB Note Actions - BibTeX record-related commands +:PROPERTIES: +:CUSTOM_ID: orb-note-actions---bibtex-record-related-commands +:END: +** Overview +:PROPERTIES: +:CUSTOM_ID: overview +:END: + +Type =M-x orb-note-actions= or bind this command to a key such as =C-c n a= to +quickly access additional commands that take the note's BibTeX key as an input +and process it to perform some useful actions. + +Note actions are divided into three groups: =default=, =extra=, and =user= set +via =orb-note-actions-default=, =orb-note-actions-extra=, +=orb-note-actions-user=, respectively. There is no big conceptual difference +between the three except that the =default= note actions are commands provided +by =bibtex-completion=, =extra= note actions are extra commands provided by +=org-roam-bibtex=, and =user= note actions are left for user customization. + +** Note actions interface +:PROPERTIES: +:CUSTOM_ID: note-actions-interface +:END: +There is a number of interfaces available for displaying the available +note actions: =default= (using =completing-read=), =ido=, =ivy=, =helm= +and =hydra=. The interface can be set via the +=orb-note-actions-interface= user variable. + +#+begin_example + (setq orb-note-actions-interface 'hydra) +#+end_example + +Alternatively, =orb-note-actions-interface= can be set to a custom function +that will provide completion for available note actions. The function must take +one argument CITEKEY, which is a list whose =car= is the current note's +citation key: + +#+begin_example + (setq orb-note-actions-interface #'my-orb-note-actions-interface) +#+end_example + +NOTE: This variable should be set using the Customize interface, +=use-package='s =:custom= keyword, or Doom's =setq!= macro. Simple =setq= will +not work. + +#+begin_src org +:PROPERTIES: +:ID: uuid1234-... +:ROAM_REFS: cite:Doe2020 +:END: +,#+title: My note +#+end_src + +#+begin_example + (defun my-orb-note-actions-interface (citekey) + ;;; For the above note, (car citekey) => "Doe2020" + ...) +#+end_example + +** Adding new note actions +:PROPERTIES: +:CUSTOM_ID: adding-new-note-actions +:END: +To install a note action, add a cons cell of format +=(DESCRIPTION . FUNCTION)= to one of the note actions variables: + +#+begin_example + (with-eval-after-load 'orb-note-actions + (add-to-list 'orb-note-actions-user (cons "My note action" #'my-note-action))) +#+end_example + +A note action must take a single argument CITEKEY, which is a list whose +car is the current note's citation key: + +#+begin_example + (defun my-note-action (citekey) + (let ((key (car citekey))) + ...)) +#+end_example + From a8cf2832fca35cebfec71fdf8c1e66573f181bbf Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Sat, 8 Jan 2022 22:53:16 +0100 Subject: [PATCH 5/5] (int) reformat orb-core.el --- orb-core.el | 507 +++++++++++++++++++++++---------------------- org-roam-bibtex.el | 10 +- 2 files changed, 258 insertions(+), 259 deletions(-) diff --git a/orb-core.el b/orb-core.el index b0c2965..b3b5d0e 100644 --- a/orb-core.el +++ b/orb-core.el @@ -87,9 +87,6 @@ ;; ============================================================================ ;;; 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." @@ -101,191 +98,13 @@ :group 'org-roam-bibtex :prefix "orb-note-actions-") - -;; ============================================================================ -;;; 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-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 `refuse' (formerly `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 (file-truename attachment)) - (message "No PDF(s) found for this entry: %s" key)))) - ;; ============================================================================ ;;; Customize definitions ;; ============================================================================ +;; Templates + (defcustom orb-preformat-templates t "Non-nil to enable template pre-expanding. See `orb-edit-note' for details." @@ -329,33 +148,6 @@ information on BibTeX field names." :type '(repeat :tag "BibTeX field names" string) :group 'org-roam-bibtex) -(defcustom orb-process-file-keyword t - "Whether to treat the file keyword specially during template pre-expanding. -When this variable is non-nil, the \"%^{file}\" and \"${file}\" -wildcards will be processed by `org-process-file-field' rather -than simply replaced with the field value. This may be useful in -situations when the file field contains several file names and -only one file name is desirable for retrieval. The \"file\" -keyword must be set for pre-expanding in `orb-preformat-keywords' -as usual. - -If this variable is `string', for example \"my-file\", use its -value as the wildcard keyword instead of the default \"file\" -keyword. Thus, it will be possible to get both the raw file -field value by expanding the %^{file} and ${file} wildcards and a -single file name by expanding the %^{my-file} and ${my-file} -wildcards. The keyword, e.g. \"my-file\", must be set for -pre-expanding in `orb-preformat-keywords' as usual. - -The variable `orb-attached-file-extensions' controls filtering of -file names based on file extensions." - ;; TODO: check if a custom string is really working as described - :group 'org-roam-bibtex - :type '(choice - (const :tag "Yes" t) - (const :tag "No" nil) - (string :tag "Custom wildcard keyword"))) - (defcustom orb-roam-ref-format 'org-ref-v2 "Defines the format of citation key in the `ROAM_REFS' property. Should be one of the following symbols: @@ -371,6 +163,22 @@ This can also be a custom `format' string with a single `%s' specifier." (string :tag "Custom format string")) :group 'org-roam-bibtex) +(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-bibtex-entry-get-value-function #'bibtex-completion-apa-get-value "Function to be used by ORB for values from a BibTeX entry. @@ -390,55 +198,85 @@ ENTRY is a BibTeX entry as returned by `bibtex-completion-get-entry'." (function-item bibtex-completion-get-value) (function :tag "Custom function"))) -(defcustom orb-persp-project `("notes" . ,org-roam-directory) - "Perspective name and path to the project with bibliography notes. -A cons cell (PERSP-NAME . PROJECT-PATH). Only relevant when -`orb-switch-persp' is set to t. +;; Handling BibTeX file field and attachments -PERSP-NAME should be a valid Perspective name, PROJECT-PATH should be -an open Projectile project. +(defcustom orb-process-file-keyword t + "Whether to treat the file keyword specially during template pre-expanding. +When this variable is non-nil, the \"%^{file}\" and \"${file}\" +wildcards will be processed by `org-process-file-field' rather +than simply replaced with the field value. This may be useful in +situations when the file field contains several file names and +only one file name is desirable for retrieval. The \"file\" +keyword must be set for pre-expanding in `orb-preformat-keywords' +as usual. -See `orb-edit-note' for details" - :type '(cons (string :tag "Perspective name") - (directory :tag "Projectile directory")) - :group 'org-roam-bibtex) +If this variable is `string', for example \"my-file\", use its +value as the wildcard keyword instead of the default \"file\" +keyword. Thus, it will be possible to get both the raw file +field value by expanding the %^{file} and ${file} wildcards and a +single file name by expanding the %^{my-file} and ${my-file} +wildcards. The keyword, e.g. \"my-file\", must be set for +pre-expanding in `orb-preformat-keywords' as usual. -(defcustom orb-switch-persp nil - "Non-nil to enable switching to the notes perspective. -Set the name of the perspective and the path to the notes project -in `orb-persp-project' for this to take effect. +The variable `orb-attached-file-extensions' controls filtering of +file names based on file extensions." + ;; TODO: check if a custom string is really working as described + :group 'org-roam-bibtex + :type '(choice + (const :tag "Yes" t) + (const :tag "No" nil) + (string :tag "Custom wildcard keyword"))) -Perspective switching works with Pers-mode and Projectile." +(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)) - :group 'org-roam-bibtex) + (const :tag "No" nil))) -(defcustom orb-ignore-bibtex-store-link-functions - '(org-bibtex-store-link) - "Functions to override with `ignore' during note creation process. +(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 `refuse' (formerly `orb-pdf-scrapper'). -Org Ref defines function `org-ref-bibtex-store-link' to store -links to a BibTeX buffer, e.g. with `org-store-link'. At the -same time, Org ref requires `ol-bibtex' library, which defines -`org-bibtex-store-link' to do the same. When creating a note -with `orb-edit-note' from a BibTeX buffer, for example by calling -`org-ref-open-bibtex-notes', the initiated `org-capture' process -implicitly calls `org-store-link'. The latter loops through all -the functions for storing links, and if more than one function -can store links to the location, the BibTeX buffer in this -particular case, the user will be prompted to choose one. This -is definitely annoying, hence ORB will advise all functions in -this list to return nil to trick `org-capture' and get rid of the -prompt. +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. -The default value is `(org-bibtex-store-link)', which means this -function will be ignored and `org-ref-bibtex-store-link' will be -used to store a link to the BibTeX buffer. See -`org-capture-templates' on how to use the link in your templates." - :type '(repeat (function)) - :risky t - :group 'org-roam-bibtex) +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))) + +;; ORB Insert (defcustom orb-insert-interface 'generic "Interface frontend to use with `orb-insert-link'. @@ -528,6 +366,8 @@ Possible values are `key' and `entry'." (const key) (const entry))) +;; ORB Note Actions + (defcustom orb-note-actions-interface 'default "Interface frontend for `orb-note-actions'. Supported values (interfaces) are symbols `default', `ido', @@ -601,6 +441,173 @@ Each action is a cons cell DESCRIPTION . FUNCTION." :value-type (function :tag "Function")) :group 'orb-note-actions) +;; Miscellaneous + +(defcustom orb-ignore-bibtex-store-link-functions + '(org-bibtex-store-link) + "Functions to override with `ignore' during note creation process. + +Org Ref defines function `org-ref-bibtex-store-link' to store +links to a BibTeX buffer, e.g. with `org-store-link'. At the +same time, Org ref requires `ol-bibtex' library, which defines +`org-bibtex-store-link' to do the same. When creating a note +with `orb-edit-note' from a BibTeX buffer, for example by calling +`org-ref-open-bibtex-notes', the initiated `org-capture' process +implicitly calls `org-store-link'. The latter loops through all +the functions for storing links, and if more than one function +can store links to the location, the BibTeX buffer in this +particular case, the user will be prompted to choose one. This +is definitely annoying, hence ORB will advise all functions in +this list to return nil to trick `org-capture' and get rid of the +prompt. + +The default value is `(org-bibtex-store-link)', which means this +function will be ignored and `org-ref-bibtex-store-link' will be +used to store a link to the BibTeX buffer. See +`org-capture-templates' on how to use the link in your templates." + :type '(repeat (function)) + :risky t + :group 'org-roam-bibtex) + +(defcustom orb-persp-project `("notes" . ,org-roam-directory) + "Perspective name and path to the project with bibliography notes. +A cons cell (PERSP-NAME . PROJECT-PATH). Only relevant when +`orb-switch-persp' is set to t. + +PERSP-NAME should be a valid Perspective name, PROJECT-PATH should be +an open Projectile project. + +See `orb-edit-note' for details" + :type '(cons (string :tag "Perspective name") + (directory :tag "Projectile directory")) + :group 'org-roam-bibtex) + +(defcustom orb-switch-persp nil + "Non-nil to enable switching to the notes perspective. +Set the name of the perspective and the path to the notes project +in `orb-persp-project' for this to take effect. + +Perspective switching works with Pers-mode and Projectile." + :type '(choice + (const :tag "Yes" t) + (const :tag "No" nil)) + :group 'org-roam-bibtex) + + +;; ============================================================================ +;;; BibTeX fields and their special handling +;; ============================================================================ + +(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 (file-truename attachment)) + (message "No PDF(s) found for this entry: %s" key)))) + ;; ============================================================================ ;;; Orb edit notes diff --git a/org-roam-bibtex.el b/org-roam-bibtex.el index 1b27e29..16d48da 100644 --- a/org-roam-bibtex.el +++ b/org-roam-bibtex.el @@ -53,24 +53,16 @@ ;;; Code: ;; -;; ============================================================================ -;;;; Dependencies -;; ============================================================================ (require 'orb-core) - -;; ============================================================================ -;;; Org-roam-bibtex minor mode -;; ============================================================================ - (defvar org-roam-bibtex-mode-map (make-sparse-keymap) "Keymap for `org-roam-bibtex-mode'.") ;;;###autoload (define-minor-mode org-roam-bibtex-mode - "Sets an appropriate function for editing bibliography notes. + "Configures integration of Org-roam with BibTeX frontends. Supports Org-ref, Helm-bibtex/Ivy-bibtex, and Citar. When called interactively, toggle `org-roam-bibtex-mode'. with