├── test ├── bs-1629.org ├── manual │ ├── run.sh │ ├── citar.el │ └── install.el ├── citar-test.el ├── citar-format-test.el ├── citar-file-test.el └── test.bib ├── images ├── capf-md.png ├── oc-styles.png ├── orderless.png ├── vertico.png ├── indicators.png ├── ui-segments.png ├── rich-ui-icons.png ├── selectrum-embark.png ├── file-browser-embark.png ├── indicators-nerd-icons.png └── org-cite-embark-point.png ├── .auto-changelog ├── Makefile ├── .gitignore ├── .dir-locals.el ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── check.yml ├── Eldev ├── CONTRIBUTING.org ├── CHANGELOG.md ├── citar-capf.el ├── citar-markdown.el ├── citar-embark.el ├── citar-citeproc.el ├── citar-format.el ├── citar-latex.el ├── citar-cache.el ├── citar-file.el ├── citar-org.el ├── README.org └── LICENSE /test/bs-1629.org: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/capf-md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/capf-md.png -------------------------------------------------------------------------------- /images/oc-styles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/oc-styles.png -------------------------------------------------------------------------------- /images/orderless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/orderless.png -------------------------------------------------------------------------------- /images/vertico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/vertico.png -------------------------------------------------------------------------------- /images/indicators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/indicators.png -------------------------------------------------------------------------------- /images/ui-segments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/ui-segments.png -------------------------------------------------------------------------------- /images/rich-ui-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/rich-ui-icons.png -------------------------------------------------------------------------------- /images/selectrum-embark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/selectrum-embark.png -------------------------------------------------------------------------------- /images/file-browser-embark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/file-browser-embark.png -------------------------------------------------------------------------------- /images/indicators-nerd-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/indicators-nerd-icons.png -------------------------------------------------------------------------------- /images/org-cite-embark-point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emacs-citar/citar/HEAD/images/org-cite-embark-point.png -------------------------------------------------------------------------------- /.auto-changelog: -------------------------------------------------------------------------------- 1 | { 2 | "output": "CHANGELOG.md", 3 | "template": "keepachangelog", 4 | "unreleased": true, 5 | "starting-version": "1.0" 6 | } 7 | -------------------------------------------------------------------------------- /test/manual/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | /usr/bin/env \ 5 | emacs -q --no-site-file --no-site-lisp --no-splash -l install.el -l citar.el 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: clean 3 | clean: 4 | eldev clean all 5 | 6 | .PHONY: lint 7 | lint: 8 | eldev -C --unstable -T lint 9 | 10 | .PHONY: test 11 | test: 12 | eldev -C --unstable -T test 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled 2 | *.elc 3 | 4 | # Packaging 5 | .cask 6 | /.eldev 7 | /Eldev-local 8 | 9 | # Backup files 10 | *~ 11 | 12 | # Undo-tree save-files 13 | *.~undo-tree 14 | 15 | # Autoloads 16 | /citar-autoloads.el 17 | -------------------------------------------------------------------------------- /test/citar-test.el: -------------------------------------------------------------------------------- 1 | ;;; citar-file-test.el --- Tests for citar.el -*- lexical-binding: t; -*- 2 | 3 | ;;; Commentary: 4 | 5 | ;;; Code: 6 | 7 | (require 'ert) 8 | (require 'citar) 9 | (require 'map) 10 | 11 | (ert-deftest citar-test--check-notes-sources () 12 | ;; This should run without signalling an error 13 | (should-not (ignore (map-do #'citar--check-notes-source citar-notes-sources)))) 14 | 15 | (provide 'citar-test) 16 | ;;; citar-test.el ends here 17 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((emacs-lisp-mode . ((sentence-end-double-space . nil) 5 | (fill-column . 110) 6 | (indent-tabs-mode . nil) 7 | (elisp-lint-indent-specs . ((describe . 1) 8 | (it . 1) 9 | (thread-first . 0) 10 | (cl-flet . 1) 11 | (cl-flet* . 1)))))) 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Emacs version:** 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /test/manual/citar.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t -*- 2 | ;; SPDX-License-Identifier: GPL-3.0-or-later 3 | ;; Setup completion style which is responsible for candidate filtering 4 | (setq completion-styles '(orderless)) 5 | 6 | ;; Enable Selectrum and Marginalia 7 | (vertico-mode) 8 | (marginalia-mode) 9 | 10 | ;; activate additional packages we need, including bibtex-actions 11 | (require 'embark) 12 | (require 'citar) 13 | (require 'citar-embark) 14 | 15 | (citar-embark-mode 1) 16 | 17 | ;; set binding for Embark context menu 18 | (global-set-key (kbd "M-;") #'embark-act) 19 | (global-set-key (kbd "M-.") #'embark-dwim) 20 | 21 | ;; replace CRM with consult alernative 22 | ;; (advice-add #'completing-read-multiple :override #'consult-completing-read-multiple) 23 | 24 | (with-eval-after-load "oc" 25 | (setq org-cite-follow-processor 'citar 26 | org-cite-insert-processor 'citar 27 | org-cite-activate-processor 'citar)) 28 | 29 | ;; load the test bib file 30 | (setq citar-bibliography '("../test.bib")) 31 | (setq citar-notes-paths '("../")) 32 | 33 | (setq vertico-count 20) 34 | 35 | ;; theme 36 | (load-theme 'modus-operandi t) 37 | -------------------------------------------------------------------------------- /Eldev: -------------------------------------------------------------------------------- 1 | ; -*- mode: emacs-lisp; lexical-binding: t -*- 2 | ;; this and related files adapted from org-roam 3 | ;; explicitly set main file 4 | (setf eldev-project-main-file "citar.el") 5 | 6 | (eldev-use-package-archive 'gnu-elpa) 7 | (eldev-use-package-archive 'melpa) 8 | 9 | (eldev-use-plugin 'autoloads) 10 | 11 | ;; TODO what to do with these excluded files? 12 | (setf eldev-standard-excludes `(:or ,eldev-standard-excludes "./test/manual/" "./citar-capf.el" "./citar-filenotify.el")) 13 | 14 | ;; (setf eldev-test-fileset '("./test/" "!./test/manual/")) 15 | (eldev-add-extra-dependencies '(build test lint) 'embark 'auctex) 16 | 17 | ;; allow to load test helpers 18 | ;; (eldev-add-loading-roots 'test "test/utils") 19 | 20 | ;;; Linting settings 21 | 22 | ;; Tell checkdoc not to demand two spaces after a period. 23 | (setq sentence-end-double-space nil) 24 | (setq eldev-lint-default '(elisp)) 25 | (setq eldev-lint-stop-mode 'linter) 26 | 27 | (with-eval-after-load 'elisp-lint 28 | ;; Used eldev lint package | checkdoc 29 | (setf elisp-lint-ignored-validators '("package-lint" "checkdoc") 30 | enable-local-variables :all) 31 | ;; Emacs 29 snapshot has new indentation convention for cl-letf 32 | (when (> emacs-major-version 28) 33 | (push "indent" elisp-lint-ignored-validators))) 34 | 35 | ;; Currently, package-lint has no other way of ignoring checks. 36 | ;; See https://github.com/purcell/package-lint/issues/125 37 | (advice-add #'package-lint--check-eval-after-load :override #'ignore) 38 | -------------------------------------------------------------------------------- /test/manual/install.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t -*- 2 | ;; SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | ;; Use a temporary emacs.d directory for testing 5 | (setq citar--tmp-install-dir "/tmp/ba-test-emacs.d/") 6 | (setq user-emacs-directory citar--tmp-install-dir) 7 | 8 | ;; Setup package archive 9 | (package-initialize) 10 | (setq package-archives '(("elpa" . "https://elpa.gnu.org/packages/") 11 | ("melpa" . "https://melpa.org/packages/"))) 12 | (package-refresh-contents) 13 | 14 | ;; Install packages 15 | 16 | (package-install 'load-relative) 17 | (package-install 'parsebib) 18 | 19 | ;; completion 20 | (package-install 'vertico) 21 | 22 | ;; completion style 23 | (package-install 'orderless) 24 | (package-install 'consult) 25 | ;; supporting packages for contextual commands 26 | (package-install 'embark) 27 | (package-install 'marginalia) 28 | 29 | ;; Add binding for embark-act 30 | (global-set-key (kbd "C-.") 'embark-act) 31 | 32 | ;; citar 33 | ;; Modify load path so that requires in citar.el are handled 34 | (add-to-list 'load-path "../../") 35 | ;; we load this locally, to facilitate development testing on branches 36 | (load-relative "../../citar.el") 37 | (load-relative "../../citar-org.el") 38 | (load-relative "../../citar-embark.el") 39 | 40 | ;; theme that supports selectrum and vertico 41 | (package-install 'modus-themes) 42 | 43 | ;; load full-screen, since this package exploits horizontal real estate 44 | (add-to-list 'initial-frame-alist '(fullscreen . maximized)) 45 | 46 | (message "Installed packages to %s" citar--tmp-install-dir) 47 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'releases/**' 8 | paths: 9 | - '**.el' 10 | - Eldev 11 | pull_request: 12 | paths: 13 | - '**.el' 14 | - Eldev 15 | workflow_dispatch: 16 | 17 | jobs: 18 | check: 19 | runs-on: ubuntu-latest 20 | continue-on-error: ${{ matrix.emacs_version == 'snapshot' }} 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | emacs_version: 25 | - 30.2 26 | - 29.2 27 | - 28.2 28 | - snapshot 29 | action: 30 | - compile 31 | - test 32 | - lint 33 | exclude: 34 | - emacs_version: snapshot 35 | action: compile 36 | - emacs_version: 30.2 37 | action: lint 38 | - emacs_version: 29.2 39 | action: lint 40 | - emacs_version: 28.2 41 | action: lint 42 | steps: 43 | - name: Set up Emacs ${{matrix.emacs_version}} 44 | uses: purcell/setup-emacs@master 45 | with: 46 | version: ${{matrix.emacs_version}} 47 | - name: Install Eldev 48 | run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh 49 | - name: Check out project 50 | uses: actions/checkout@v3 51 | - name: Install dependencies 52 | run: | 53 | eldev --color --trace prepare build 54 | - name: Byte-compile 55 | if: ${{ matrix.action == 'compile' }} 56 | run: | 57 | eldev --color --debug --backtrace --packaged \ 58 | compile --load-before-compiling --keep-going --warnings-as-errors 59 | - name: Run tests 60 | if: ${{ matrix.action == 'test' }} 61 | run: | 62 | eldev --color --packaged --debug --time test 63 | - name: Lint Elisp 64 | if: ${{ matrix.action == 'lint' }} 65 | run: | 66 | eldev --color --packaged lint elisp 67 | - name: Lint documentation 68 | if: ${{ matrix.action == 'lint' }} 69 | run: | 70 | eldev --color lint doc 71 | - name: Lint regex 72 | if: ${{ matrix.action == 'lint' }} 73 | run: | 74 | eldev --color lint re 75 | - name: Lint package metadata 76 | if: ${{ matrix.action == 'lint' }} 77 | continue-on-error: true 78 | run: | 79 | eldev --color lint package 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.org: -------------------------------------------------------------------------------- 1 | * Contributing 2 | :PROPERTIES: 3 | :CUSTOM_ID: contributing 4 | :END: 5 | 6 | If you would like to contribute, details: 7 | 8 | - For more significant potential changes, file an issue first to get feedback on the basic idea. 9 | - If you do submit a PR, follow the [[https://github.com/bbatsov/emacs-lisp-style-guide][elisp style guide]], and use [[https://www.conventionalcommits.org/en/v1.0.0/][conventional commits]]. 10 | - For working on lists and such, we primarily use the =seq= functions, and occasionally ~dolist~. 11 | 12 | ** Basic Architecture 13 | 14 | Citar uses a cache, which stores two hash tables for each bibliography file: 15 | 16 | - entries :: as returned by =parsebib-parse=, keys are citekeys, values are alists of entry fields 17 | - pre-formatted :: values are partially-formatted completion strings 18 | 19 | The =citar--ref-completion-table= function returns a hash table from the bibliographic cache, and ~citar--get-entry~ and ~-citar--get-value~ provide access to those data. 20 | Most user-accessible citar functions take an argument ~key~ or ~keys~. 21 | Some functions also take an ~entry~ argument, and ~citar--get-value~ takes either. 22 | When using these functions, you should keep in mind that unless you pass an entry alist to ~citar--get-value~, and instead use a key, each call to that function will query the cache. 23 | This, therefore, is a better pattern to use: 24 | 25 | #+begin_src emacs-lisp 26 | 27 | (let* ((entry (citar--get-entry key)) 28 | (title (citar--get-value entry "title"))) 29 | (message title)) 30 | 31 | #+end_src 32 | 33 | 34 | ** Extending citar 35 | 36 | You can use ~citar-select-ref~ or ~citar-select-refs~ to write custom commands. 37 | An example: 38 | 39 | #+begin_src emacs-lisp 40 | 41 | 42 | (defun my/citar-insert-annots (keys) 43 | "insert annotations as org text from KEYS-ENTRIES" 44 | (interactive (list (citar-select-refs))) 45 | (let* ((files 46 | (seq-mapcat (lambda (key) 47 | (citar-file--files-for-entry 48 | key (citar--get-entry key) 49 | '("/") '("pdf"))) 50 | keys )) 51 | (output (seq-map 52 | (lambda (file) 53 | (pdf-annot-markups-as-org-text ;; you'll still need to write this function! 54 | file) 55 | files))) 56 | (save-excursion 57 | (org-forward-element) 58 | (-each output (lambda (out) 59 | (insert (format "\n%s\n" out)))) 60 | output))) 61 | 62 | 63 | (defun my/independent-insert-annots (key) 64 | "helper function to insert annotations without the bibtex-actins apparatus" 65 | (my/citar-insert-annots (list key))) 66 | 67 | 68 | #+end_src 69 | 70 | Then bind the first function to the appropriate keymap, e.g., 71 | #+begin_src emacs-lisp 72 | (define-key oc-citar-map (kbd "a") '("insert file annotations" . my/citar-insert-annots)) 73 | #+end_src 74 | -------------------------------------------------------------------------------- /test/citar-format-test.el: -------------------------------------------------------------------------------- 1 | ;;; citar-format-test.el --- Tests for citar-format.el -*- lexical-binding: t; -*- 2 | 3 | ;;; Commentary: 4 | 5 | ;;; Code: 6 | 7 | (require 'ert) 8 | (require 'citar-format) 9 | 10 | (ert-deftest citar-format-test--star-widths () 11 | "Test `citar-format--star-widths'." 12 | 13 | (should (string-empty-p (citar-format--star-widths 80 nil))) 14 | 15 | ;; For single string, return the original string; not a copy 16 | (let ((strings '("foo"))) 17 | (should (eq (car strings) (citar-format--star-widths 80 strings)))) 18 | 19 | (let ((strings '("foo" "bar" "baz"))) 20 | (should (equal "foobaz" (citar-format--star-widths 0 strings))) 21 | (should (equal "foobabaz" (citar-format--star-widths 2 strings))) 22 | (should (equal "foob…baz" (citar-format--star-widths 2 strings nil "…"))) 23 | (should (equal "foobarbaz" (citar-format--star-widths 3 strings))) 24 | (should (equal "foobar baz" (citar-format--star-widths 4 strings))) 25 | 26 | ;; When hide-elided is t, the actual string contents should be equal 27 | (cl-loop for w from 0 to 3 28 | do (should (equal "foobarbaz" (citar-format--star-widths w strings t)))) 29 | ;; ...unless the allocated width is greater than the string length 30 | (should (equal "foobar baz" (citar-format--star-widths 4 strings))) 31 | 32 | ;; When hide-elided is t, the hidden text should have the 'display "" 33 | ;; property. N.B. equal-including-properties is slightly broken; see 34 | ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=6581 35 | (should (ert-equal-including-properties #("foobarbaz" 5 6 (display "")) 36 | (citar-format--star-widths 2 strings t))) 37 | 38 | ;; Test with ellipsis 39 | (should (ert-equal-including-properties #("foobarbaz" 4 6 (display "…")) 40 | (citar-format--star-widths 2 strings t "…")))) 41 | 42 | (let ((strings '("foo" "bar" "baz" "qux"))) 43 | (should (equal "foobaz" (citar-format--star-widths 0 strings))) 44 | (should (equal "foobbaz" (citar-format--star-widths 1 strings))) 45 | (should (equal "foobbazq" (citar-format--star-widths 2 strings))) 46 | (should (equal "foobabazq" (citar-format--star-widths 3 strings))) 47 | (should (equal "foobabazqu" (citar-format--star-widths 4 strings))) 48 | 49 | ;; Test with ellipsis 50 | (should (equal "foob…baz…" (citar-format--star-widths 3 strings nil "…"))) 51 | (should (ert-equal-including-properties 52 | #("foobarbazqux" 4 6 (display "…") 9 12 (display "…")) 53 | (citar-format--star-widths 3 strings t "…")))) 54 | 55 | (should (equal '((nil "author" "editor") " " (nil "year")) 56 | (citar-format--parse "${author editor} ${year}"))) 57 | (should (equal '((nil "author" "editor") " " ((:width 4) "year")) 58 | (citar-format--parse "${author editor} ${year:4}"))) 59 | (should (equal '(((:width *) "author" "editor") " " ((:width 4) "year")) 60 | (citar-format--parse "${author editor:*} ${year:4}"))) 61 | (should (equal '(((:width * :transform (citar--shorten-names)) "author" "editor") " " ((:width 4) "year")) 62 | (citar-format--parse "${author editor: * % sn} ${year:4}")))) 63 | 64 | (provide 'citar-format-test) 65 | ;;; citar-format-test.el ends here 66 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 9 | 10 | ## [v1.2.0](https://github.com/emacs-citar/citar/compare/v1.1...v1.2.0) - 2023-03-15 11 | 12 | ### Fixed 13 | 14 | - citeproc: Use Citar cache [`#702`](https://github.com/emacs-citar/citar/issues/702) 15 | - cache: Don't restrict fields parsed [`#741`](https://github.com/emacs-citar/citar/issues/741) 16 | - citar-citeproc-format-reference: Add style arg [`#737`](https://github.com/emacs-citar/citar/issues/737) 17 | - Make citar-open-entry configurable [`#736`](https://github.com/emacs-citar/citar/issues/736) 18 | - Don't use setf with buffer-substring [`#729`](https://github.com/emacs-citar/citar/issues/729) 19 | 20 | ### Commits 21 | 22 | - Add formatting options for citar--shorten-names [`b9d6b75`](https://github.com/emacs-citar/citar/commit/b9d6b75c80a90bc1b9b830d4ce112a1f8fa75b54) 23 | - Allow transform modifiers in templates [`15431d4`](https://github.com/emacs-citar/citar/commit/15431d4294f6b7599dba8288ed401ccd2f27e23e) 24 | - capf: Add citar-capf-setup [`2f61054`](https://github.com/emacs-citar/citar/commit/2f610542bd69bb271529a01b402820e3f27caa6f) 25 | 26 | ## [v1.1](https://github.com/emacs-citar/citar/compare/v1.0...v1.1) - 2023-02-16 27 | 28 | ### Merged 29 | 30 | - github-ci: install all dependencies in the Eldev "prepare" step [`#718`](https://github.com/emacs-citar/citar/pull/718) 31 | - Fix linter warnings on indentation and long docstring lines. [`#698`](https://github.com/emacs-citar/citar/pull/698) 32 | 33 | ### Fixed 34 | 35 | - capf: Refactor to complete on key, use cache [`#660`](https://github.com/emacs-citar/citar/issues/660) 36 | - Add previous inputs to defaults in multi-selection [`#714`](https://github.com/emacs-citar/citar/issues/714) 37 | - test/manual: Clarify license [`#712`](https://github.com/emacs-citar/citar/issues/712) 38 | - Call predicate on candidate display string (fix #708) [`#708`](https://github.com/emacs-citar/citar/issues/708) 39 | - citar-file: Unescape filenames in citar-file--parser-default [`#693`](https://github.com/emacs-citar/citar/issues/693) 40 | - Fix linter warnings on indentation and long docstring lines. (#698) [`#697`](https://github.com/emacs-citar/citar/issues/697) 41 | - Add binding hint to multi UI [`#686`](https://github.com/emacs-citar/citar/issues/686) 42 | 43 | ### Commits 44 | 45 | - Update docstrings, KEY arg to CITEKEY [`52050ca`](https://github.com/emacs-citar/citar/commit/52050ca5aeaffa7d08ea3e3cc20042a9bcbeee05) 46 | - Fix indent formatting [`b0605c3`](https://github.com/emacs-citar/citar/commit/b0605c30a98539ea717a80cd1572f3296b7af6aa) 47 | - Revert "Add previous inputs ..." [`ea489ec`](https://github.com/emacs-citar/citar/commit/ea489eca7f5321b5ede1ea294abf47ecbc9f11ca) 48 | 49 | ## [v1.0](https://github.com/emacs-citar/citar/compare/v0.9.7...v1.0) - 2022-08-09 50 | 51 | ### Merged 52 | 53 | - Minor refactor of `citar-add-file-to-library` [`#671`](https://github.com/emacs-citar/citar/pull/671) 54 | 55 | ### Fixed 56 | 57 | - Regeneralize citar-open-entry [`#644`](https://github.com/emacs-citar/citar/issues/644) 58 | - Make file opening more customizable [`#507`](https://github.com/emacs-citar/citar/issues/507) 59 | - Allow `citar-select-refs` to be used with `embark-act`. [`#655`](https://github.com/emacs-citar/citar/issues/655) 60 | 61 | ### Commits 62 | 63 | - Remove extra entries argument to API functions [`1257ebf`](https://github.com/emacs-citar/citar/commit/1257ebf29faa48a61b772554154641fe949df56a) 64 | - `citar-open` and `citar-open-notes` now allow creating notes. [`8475c1f`](https://github.com/emacs-citar/citar/commit/8475c1fd2b8d2171b7c9dd4318aa67d09c62b820) 65 | - Update `citar-open` and related functions to properly handle notes. [`9115f8d`](https://github.com/emacs-citar/citar/commit/9115f8dbd49a0e2c758628ebd90b0e9f2fad7908) 66 | 67 | -------------------------------------------------------------------------------- /citar-capf.el: -------------------------------------------------------------------------------- 1 | ;;; citar-capf.el --- citar completion-at-point -*- lexical-binding: t; -*- 2 | ;; 3 | ;; SPDX-FileCopyrightText: 2022 Bruce D'Arcus, Colin McLear 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | ;; 6 | ;; This file is not part of GNU Emacs. 7 | ;; 8 | ;;; Commentary: 9 | ;; 10 | ;; Citation key 'completion-at-point' for org, markdown, or latex. 11 | ;; 12 | ;; Supports org-cite in org, and pandoc in markdown. 13 | ;; 14 | ;;; Code: 15 | (require 'citar) 16 | 17 | ;;;; Utility Vars & Functions 18 | ;; Declare org-element related vars & functions 19 | (defvar org-element-citation-key-re) 20 | (defvar org-element-citation-prefix-re) 21 | (declare-function org-element-at-point "ext:org-element") 22 | (declare-function org-element-property "ext:org-element") 23 | (declare-function org-element-type "ext:org-element") 24 | (declare-function org-element-context "ext:org-element") 25 | ;; Declare function from citar 26 | (declare-function citar--completion-table "citar") ;; pending cache revisions 27 | 28 | ;;;; Citar-Capf 29 | ;;;###autoload 30 | (defun citar-capf () 31 | "Complete citation key at point for org, markdown, or latex." 32 | (let ((citar-capf-latex-regexp 33 | "\\(?:cite\\(?:\\(?:[pt]\\*\\|[pt]\\)?{\\)\\)\\([[:alnum:]_-]*,\\)*\\([[:alnum:]_-]*\\)") 34 | (citar-capf-markdown-regexp 35 | (concat "-?@" ; @ preceded by optional - 36 | "\\(?:" 37 | "{\\(?1:.*?\\)}" ; brace-delimited key 38 | "\\|" 39 | "\\(?1:[[:alnum:]_][[:alnum:]]*\\(?:[:.#$%&+?<>~/-][[:alnum:]]+\\)*\\)" 40 | "\\)"))) 41 | ;; only list candidates in certain contexts 42 | (when 43 | ;; conditional recognition of citation key by mode 44 | (cond 45 | ;; latex-mode 46 | ((derived-mode-p 'latex-mode) 47 | (looking-back citar-capf-latex-regexp 2)) 48 | ;; org-mode 49 | ((and (derived-mode-p 'org-mode) 50 | (let ((element (org-element-at-point))) 51 | (or (eq 'citation (org-element-type (org-element-context element))) 52 | (and (or (eq ?@ (char-before)) 53 | (looking-back org-element-citation-key-re 54 | (line-beginning-position))) 55 | (let ((origin (point))) 56 | (save-excursion 57 | (and (re-search-backward org-element-citation-prefix-re 58 | (org-element-property 59 | :begin element) 60 | t) 61 | (not (search-forward "]" origin t)))))))))) 62 | ;; markdown-mode 63 | ((and (derived-mode-p 'markdown-mode) 64 | (or (eq ?@ (char-before)) 65 | (looking-back citar-capf-markdown-regexp 66 | (line-beginning-position)))))) 67 | ;; Get and insert candidate 68 | (let* ((candidates (citar-get-entries)) 69 | (bounds (bounds-of-thing-at-point 'word))) 70 | (when bounds 71 | (list (car bounds) 72 | (cdr bounds) 73 | candidates 74 | :annotation-function #'citar-capf-annotate 75 | :exit-function 76 | (lambda (_str _status) 77 | (insert)))))))) 78 | 79 | (defun citar-capf-annotate (citekey) 80 | "Annotate a CITEKEY." 81 | (let* ((author (citar-get-value "author" citekey)) 82 | (editor (citar-get-value "editor" citekey)) 83 | (title (citar-get-value "title" citekey))) 84 | (concat 85 | " " 86 | (truncate-string-to-width 87 | (citar--shorten-names 88 | (or author editor "")) 20 nil 32 t) 89 | " " 90 | (truncate-string-to-width (or title "") 40 nil 32)))) 91 | 92 | ;;;###autoload 93 | (defun citar-capf-setup () 94 | "Add `citar-capf' to `completion-at-point-functions'." 95 | (add-to-list 'completion-at-point-functions 'citar-capf)) 96 | 97 | (provide 'citar-capf) 98 | ;;; citar-capf.el ends here 99 | -------------------------------------------------------------------------------- /test/citar-file-test.el: -------------------------------------------------------------------------------- 1 | ;;; citar-file-test.el --- Tests for citar-file.el -*- lexical-binding: t; -*- 2 | 3 | ;;; Commentary: 4 | 5 | ;;; Code: 6 | 7 | (require 'ert) 8 | (require 'seq) 9 | (require 'citar) 10 | 11 | (ert-deftest citar-file-test--parser-default () 12 | 13 | (should-not (citar-file--parser-default " ")) 14 | (should (equal '("foo") (delete-dups (citar-file--parser-default "foo")))) 15 | (should (equal '("foo" "bar") (delete-dups (citar-file--parser-default "foo;bar")))) 16 | (should (equal '("foo" "bar") (delete-dups (citar-file--parser-default " foo ; bar ; ")))) 17 | (should (equal '("foo:bar" "baz") (delete-dups (citar-file--parser-default "foo:bar;baz")))) 18 | (should (equal '("foo:bar" "baz") (delete-dups (citar-file--parser-default "foo:bar;;baz")))) 19 | 20 | ;; Test escaped delimiters 21 | (should (equal '("foo;bar" "foo\\;bar") (delete-dups (citar-file--parser-default "foo\\;bar")))) 22 | (should (equal '("foo" "bar\\") (delete-dups (citar-file--parser-default "foo;bar\\")))) 23 | (should (equal '("foo;bar" "foo\\;bar" "baz") 24 | (delete-dups (citar-file--parser-default "foo\\;bar;baz"))))) 25 | 26 | (ert-deftest citar-file-test--parser-triplet () 27 | 28 | (should-not (citar-file--parser-triplet "foo.pdf")) 29 | 30 | (should (equal '("foo.pdf") (delete-dups (citar-file--parser-triplet ":foo.pdf:PDF")))) 31 | (should (equal '("foo.pdf:PDF,:bar.pdf" "foo.pdf" "bar.pdf") 32 | (delete-dups (citar-file--parser-triplet ":foo.pdf:PDF,:bar.pdf:PDF")))) 33 | 34 | ;; Don't trim spaces in triplet parser since file is delimited by : 35 | (should (equal '(" foo.pdf :PDF, : bar.pdf " " foo.pdf " " bar.pdf ") 36 | (delete-dups (citar-file--parser-triplet ": foo.pdf :PDF, : bar.pdf :PDF")))) 37 | 38 | ;; Test escaped delimiters 39 | (should (equal '("title.pdf") 40 | (delete-dups (citar-file--parser-triplet "Title\\: Subtitle:title.pdf:application/pdf")))) 41 | (should (equal '("C:\\title.pdf" "C\\:\\\\title.pdf") 42 | (delete-dups (citar-file--parser-triplet "Title\\: Subtitle:C\\:\\\\title.pdf:PDF")))) 43 | 44 | ;; Calibre doesn't escape any special characters in filenames, so try that 45 | (should (equal '("C:title.pdf" "C:\\title.pdf") 46 | (delete-dups (citar-file--parser-triplet "Title\\: Subtitle:C:\\title.pdf:PDF"))))) 47 | 48 | (ert-deftest citar-file-test--parse-file-field () 49 | 50 | (let* ((citar-file-variable "file") 51 | (citekey "foo") 52 | (fieldvalue "foo.pdf") 53 | (dirs '("/home/user/library/")) 54 | (citar-file-parser-functions (list #'citar-file--parser-default)) 55 | lastmessage) 56 | 57 | (cl-letf (((symbol-function 'message) 58 | (lambda (format-string &rest args) 59 | (setq lastmessage (apply #'format-message format-string args)))) 60 | ((symbol-function 'current-message) 61 | (lambda () 62 | (prog1 lastmessage (setq lastmessage nil)))) 63 | ;; Pretend that all .pdf files under /home/user/library/ exist: 64 | ((symbol-function 'file-exists-p) 65 | (lambda (filename) 66 | (and (equal "pdf" (file-name-extension filename)) 67 | (member (file-name-directory filename) dirs))))) 68 | 69 | (should-not (citar-file--parse-file-field " " dirs citekey)) 70 | (should (string= 71 | (current-message) 72 | (format-message "Empty `%s' field: %s" citar-file-variable citekey))) 73 | 74 | (let ((citar-file-parser-functions nil)) 75 | (should-not (citar-file--parse-file-field fieldvalue dirs citekey)) 76 | (should (string= 77 | (current-message) 78 | (format-message 79 | "Could not parse `%s' field of `%s'; check `citar-file-parser-functions': %s" 80 | citar-file-variable citekey fieldvalue)))) 81 | 82 | (should-not (citar-file--parse-file-field "foo.html" dirs citekey)) 83 | (should (string= 84 | (current-message) 85 | (format-message 86 | (concat "None of the files for `%s' exist; check `citar-library-paths' and " 87 | "`citar-file-parser-functions': %S") 88 | citekey '("foo.html")))) 89 | 90 | (let ((citar-library-file-extensions '("html"))) 91 | (should-not (citar-file--parse-file-field fieldvalue dirs citekey)) 92 | (should (string= 93 | (current-message) 94 | (format-message 95 | "No files for `%s' with `citar-library-file-extensions': %S" 96 | citekey '("/home/user/library/foo.pdf"))))) 97 | 98 | (let ((citar-library-file-extensions nil)) 99 | (should (equal (citar-file--parse-file-field fieldvalue dirs citekey) 100 | '("/home/user/library/foo.pdf")))) 101 | 102 | (let ((citar-library-file-extensions '("pdf" "html"))) 103 | (should (equal (citar-file--parse-file-field fieldvalue dirs citekey) 104 | '("/home/user/library/foo.pdf"))))))) 105 | 106 | (provide 'citar-file-test) 107 | ;;; citar-file-test.el ends here 108 | -------------------------------------------------------------------------------- /citar-markdown.el: -------------------------------------------------------------------------------- 1 | ;;; citar-markdown.el --- Markdown adapter for citar -*- lexical-binding: t; -*- 2 | 3 | ;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | ;;; Commentary: 7 | 8 | ;; A small package that provides functions required to use citar with markdown. 9 | 10 | ;; Loading this file will enable manipulating the citations with commands 11 | ;; provided by citar. 12 | 13 | ;;; Code: 14 | 15 | (require 'citar) 16 | (require 'thingatpt) 17 | 18 | (defvar citar-major-mode-functions) 19 | 20 | (defcustom citar-markdown-prompt-for-extra-arguments t 21 | "Whether to prompt for additional arguments when inserting a citation." 22 | ;; REVIEW this maybe shouldn't be mode specific? 23 | :group 'citar-markdown 24 | :type 'boolean) 25 | 26 | (defconst citar-markdown-citation-key-regexp 27 | (concat "-?@" ; @ preceded by optional - 28 | "\\(?:" 29 | "{\\(?1:.*?\\)}" ; brace-delimited key 30 | "\\|" 31 | "\\(?1:[[:alnum:]_][[:alnum:]]*\\(?:[:.#$%&+?<>~/-][[:alnum:]]+\\)*\\)" 32 | "\\)") 33 | "Regular expression for a Pandoc citation key. 34 | Captures the actual key in group 1. Implements the syntax 35 | specified at URL 36 | `https://pandoc.org/MANUAL.html#citation-syntax'.") 37 | 38 | ;;;###autoload 39 | (defun citar-markdown-insert-keys (keys) 40 | "Insert semicolon-separated and @-prefixed KEYS in a markdown buffer." 41 | (insert (mapconcat (lambda (k) (concat "@" k)) keys "; "))) 42 | 43 | ;;;###autoload 44 | (defun citar-markdown-insert-citation (keys &optional invert-prompt) 45 | "Insert a pandoc-style citation consisting of KEYS. 46 | 47 | If the point is inside a citation, add new keys after the current 48 | key. 49 | 50 | If point is immediately after the opening \[, add new keys 51 | to the beginning of the citation. 52 | 53 | If INVERT-PROMPT is non-nil, invert the meaning of 54 | `citar-markdown-prompt-for-extra-arguments'." 55 | (let* ((citation (citar-markdown-citation-at-point)) 56 | (keys (if citation (seq-difference keys (car citation)) keys)) 57 | (keyconcat (mapconcat (lambda (k) (concat "@" k)) keys "; ")) 58 | (prompt (xor invert-prompt citar-markdown-prompt-for-extra-arguments))) 59 | (when keys 60 | (if (or (not citation) 61 | (= (point) (cadr citation)) 62 | (= (point) (cddr citation))) 63 | (let* ((prenote (when prompt (read-from-minibuffer "Prenote: "))) 64 | (postnote (when prompt (read-from-minibuffer "Postnote: "))) 65 | (prenote (if (or (not prenote) (string-empty-p prenote)) "" (concat prenote " "))) 66 | (postnote (if (or (not postnote) (string-empty-p postnote)) "" (concat ", " postnote)))) 67 | (insert (format "[%s%s%s]" prenote keyconcat postnote))) 68 | (if (= (point) (1+ (cadr citation))) 69 | (save-excursion (insert keyconcat "; ")) 70 | (skip-chars-forward "^;]" (cddr citation)) 71 | (insert "; " keyconcat)))))) 72 | 73 | ;;;###autoload 74 | (defun citar-markdown-insert-edit (&optional _arg) 75 | "Prompt for keys and call `citar-markdown-insert-citation. 76 | With ARG non-nil, rebuild the cache before offering candidates." 77 | (citar-markdown-insert-citation (citar-select-refs))) 78 | 79 | ;;;###autoload 80 | (defun citar-markdown-key-at-point () 81 | "Return citation key at point (with its bounds) for pandoc markdown citations. 82 | Returns (KEY . BOUNDS), where KEY is the citation key at point 83 | and BOUNDS is a pair of buffer positions. Citation keys are 84 | found using `citar-markdown-citation-key-regexp'. Returns nil if 85 | there is no key at point." 86 | (interactive) 87 | (when (thing-at-point-looking-at citar-markdown-citation-key-regexp) 88 | (cons (match-string-no-properties 1) 89 | (cons (match-beginning 0) (match-end 0))))) 90 | 91 | ;;;###autoload 92 | (defun citar-markdown-citation-at-point () 93 | "Return keys of citation at point. 94 | Find balanced expressions starting and ending with square 95 | brackets and containing at least one citation key (matching 96 | `citar-markdown-citation-key-regexp'). Return (KEYS . BOUNDS), 97 | where KEYS is a list of the found citation keys and BOUNDS is a 98 | pair of buffer positions indicating the start and end of the 99 | citation." 100 | (save-excursion 101 | (cond 102 | ((eq ?\[ (char-after)) (forward-char)) 103 | ((eq ?\] (char-before)) (backward-char))) 104 | (seq-some ; for each opening paren 105 | (lambda (startpos) ; return keys in balanced [ ] expr 106 | (when-let* ((endpos (and (eq ?\[ (char-after startpos)) 107 | (scan-lists startpos 1 0)))) 108 | (let (keys) 109 | (goto-char startpos) 110 | (while (re-search-forward citar-markdown-citation-key-regexp endpos t) 111 | (push (match-string-no-properties 1) keys)) 112 | (when keys 113 | (cons (nreverse keys) (cons startpos endpos)))))) 114 | (reverse (nth 9 (syntax-ppss)))))) 115 | 116 | ;;;###autoload 117 | (defun citar-markdown-list-keys () 118 | "Return a list of all keys from markdown citations in buffer." 119 | (save-excursion 120 | (let (matches) 121 | (goto-char (point-min)) 122 | (while (re-search-forward citar-markdown-citation-key-regexp nil t) 123 | (push (match-string-no-properties 1) matches)) 124 | (delete-dups (nreverse matches))))) 125 | 126 | (provide 'citar-markdown) 127 | ;;; citar-markdown.el ends here 128 | -------------------------------------------------------------------------------- /citar-embark.el: -------------------------------------------------------------------------------- 1 | ;;; citar-embark.el --- Citar/Embark integration -*- lexical-binding: t; -*- 2 | ;; 3 | ;; Author: Bruce D'Arcus 4 | ;; Maintainer: Bruce D'Arcus 5 | ;; Created: June 22, 2022 6 | ;; Modified: June 22, 2022 7 | ;; 8 | ;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus 9 | ;; SPDX-License-Identifier: GPL-3.0-or-later 10 | ;; 11 | ;; Version: 1.0 12 | ;; Keywords: bib extensions 13 | ;; Homepage: https://github.com/emacs-citar/citar 14 | ;; Package-Requires: ((emacs "27.1") (embark "0.17") (citar "0.9.7")) 15 | ;; 16 | ;; This file is not part of GNU Emacs. 17 | ;; 18 | ;;; Commentary: 19 | ;; 20 | ;; Provides a minor-mode to integrate Citar and Embark. 21 | ;; 22 | ;;; Code: 23 | 24 | (require 'citar) 25 | (require 'embark) 26 | 27 | ;;;; Keymaps 28 | 29 | (defvar citar-embark-map (make-composed-keymap citar-map embark-general-map) 30 | "Keymap for Embark actions on Citar references.") 31 | 32 | ;; TODO should this also inherit from `embark-general-map'? 33 | (defvar citar-embark-citation-map (make-composed-keymap citar-citation-map nil) 34 | "Keymap for Embark actions on Citar citations and keys.") 35 | 36 | ;;;; Variables 37 | 38 | (defvar citar-embark--target-finders 39 | (list #'citar-embark--key-finder 40 | #'citar-embark--citation-finder)) 41 | 42 | (defvar citar-embark--candidate-collectors 43 | (list #'citar-embark--selected)) 44 | 45 | (defvar citar-embark--transformer-alist 46 | (list (cons 'citar-candidate #'citar-embark--candidate-transformer))) 47 | 48 | (defvar citar-embark--keymap-alist 49 | '((citar-reference . citar-embark-map) ; minibuffer candidates 50 | (citar-key . citar-embark-citation-map) ; at-point keys 51 | (citar-citation . citar-embark-citation-map))) ; at-point citations 52 | 53 | (defvar citar-embark--multitarget-actions 54 | (list #'citar-open #'citar-open-files #'citar-attach-files #'citar-open-links 55 | #'citar-insert-bibtex #'citar-insert-citation #'citar-insert-reference 56 | #'citar-copy-reference #'citar-insert-keys #'citar-run-default-action 57 | #'citar-open-notes)) 58 | 59 | (defvar citar-embark--target-injection-hooks 60 | (list (list #'citar-insert-edit #'embark--ignore-target))) 61 | 62 | ;;;; At-point functions for Embark 63 | 64 | (defun citar-embark--key-finder () 65 | "Return the citation key at point." 66 | (when-let* ((key (and (not (minibufferp)) (citar--key-at-point)))) 67 | (cons 'citar-key key))) 68 | 69 | (defun citar-embark--citation-finder () 70 | "Return the keys of the citation at point." 71 | (when-let* ((citation (and (not (minibufferp)) (citar--citation-at-point)))) 72 | `(citar-citation ,(citar--stringify-keys (car citation)) . ,(cdr citation)))) 73 | 74 | (defun citar-embark--candidate-transformer (_type target) 75 | "Look up key for a citar-reference TYPE and TARGET." 76 | (or (get-text-property 0 'multi-category target) 77 | (cons 'citar-reference (citar--extract-candidate-citekey target)))) 78 | 79 | (defun citar-embark--selected () 80 | "Return selected candidates from `citar--select-multiple' for embark." 81 | (when-let* (((eq minibuffer-history-variable 'citar-history)) 82 | (metadata (embark--metadata)) 83 | (group-function (completion-metadata-get metadata 'group-function)) 84 | (cands (all-completions 85 | "" minibuffer-completion-table 86 | (lambda (cand) 87 | (and (equal "Selected" (funcall group-function cand nil)) 88 | (or (not minibuffer-completion-predicate) 89 | (funcall minibuffer-completion-predicate cand))))))) 90 | (cons (completion-metadata-get metadata 'category) cands))) 91 | 92 | ;;;; Enable and disable Citar/Embark integration 93 | 94 | (defun citar-embark--enable () 95 | "Add Citar-specific functions and keymaps to Embark." 96 | (mapc (apply-partially #'add-hook 'embark-target-finders) 97 | (reverse citar-embark--target-finders)) 98 | (mapc (apply-partially #'add-hook 'embark-candidate-collectors) 99 | (reverse citar-embark--candidate-collectors)) 100 | (pcase-dolist (`(,type . ,transformer) citar-embark--transformer-alist) 101 | (setf (alist-get type embark-transformer-alist) transformer)) 102 | (pcase-dolist (`(,type . ,keymap) citar-embark--keymap-alist) 103 | (setf (alist-get type embark-keymap-alist) keymap)) 104 | (cl-callf cl-union embark-multitarget-actions citar-embark--multitarget-actions) 105 | (pcase-dolist (`(,action . ,hooks) citar-embark--target-injection-hooks) 106 | (cl-callf cl-union (alist-get action embark-target-injection-hooks) hooks))) 107 | 108 | (defun citar-embark--disable () 109 | "Undo the effects of `citar-embark--enable'." 110 | (mapc (apply-partially #'remove-hook 'embark-target-finders) 111 | citar-embark--target-finders) 112 | (mapc (apply-partially #'remove-hook 'embark-candidate-collectors) 113 | citar-embark--candidate-collectors) 114 | (cl-callf cl-set-difference embark-transformer-alist citar-embark--transformer-alist :test #'equal) 115 | (cl-callf cl-set-difference embark-keymap-alist citar-embark--keymap-alist :test #'equal) 116 | (cl-callf cl-set-difference embark-multitarget-actions citar-embark--multitarget-actions) 117 | (pcase-dolist (`(,action . ,hooks) citar-embark--target-injection-hooks) 118 | (when-let* ((alistentry (assq action embark-target-injection-hooks))) 119 | (cl-callf cl-set-difference (cdr alistentry) hooks) 120 | (unless (cdr alistentry) ; if no other hooks, remove alist entry 121 | (cl-callf2 remq alistentry embark-target-injection-hooks))))) 122 | 123 | ;;;###autoload 124 | (define-minor-mode citar-embark-mode 125 | "Toggle integration between Citar and Embark." 126 | :group 'citar 127 | :global t 128 | :init-value nil 129 | :lighter " citar-embark" 130 | (if citar-embark-mode 131 | (citar-embark--enable) 132 | (citar-embark--disable))) 133 | 134 | (provide 'citar-embark) 135 | ;;; citar-embark.el ends here 136 | -------------------------------------------------------------------------------- /citar-citeproc.el: -------------------------------------------------------------------------------- 1 | ;;; citar-citeproc.el --- Citeproc reference support for citar -*- lexical-binding: t; -*- 2 | ;; 3 | ;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | ;;; Commentary: 7 | 8 | ;; Provides functions for formatting bibliographic references according to 9 | ;; Citation Style Language (CSL) styles, using 'citeproc-el'. For 10 | ;; information on using CSL, see . 11 | 12 | ;; Before using, be sure to set the two required directory paths, 13 | ;; 'citar-citeproc-csl-styles-dir' and 'citar-citeproc-csl-locales-dir'. The 14 | ;; 'styles-dir' should contain CSL style files corresponding to any citation 15 | ;; style you plan to use, for example, "chicago-author-date.csl" for Chicago 16 | ;; Manual of Style 17th edition (author-date) or "ieee.csl" for IEEE 17 | ;; citation style. CSL style files can be found in the official CSL styles 18 | ;; repository, . The 19 | ;; 'locales-dir' is a directory of files that are used to facilitate 20 | ;; localizing citations and bibliographies that are generated with CSL 21 | ;; styles. The simplest option here is to set this to a clone of the 22 | ;; official CSL locales repository, 23 | ;; . 24 | 25 | ;; After setting the style and locale directory paths, set the variable 26 | ;; 'citar-format-reference-function' to 'citar-citeproc-format-reference'. 27 | 28 | ;; Finally, set a CSL style, either by setting 'citar-citeproc-csl-style' 29 | ;; manually to the path to the desired CSL style file or by calling 30 | ;; 'citar-citeproc-select-csl-style' to choose from a style file located in 31 | ;; 'citar-citeproc-csl-styles-dir'. If a CSL style is not set before running 32 | ;; 'citar-citeproc-format-reference', the user is prompted to set a style. A 33 | ;; CSL style can also be set by calling 'citar-insert-reference' or 34 | ;; 'citar-copy-reference' with a prefix-argument. 35 | 36 | ;; Once these settings are in place, call either 'citar-insert-reference' or 37 | ;; 'citar-copy-reference' and select the key or keys to be rendered in the 38 | ;; selected CSL style. 39 | 40 | ;;; Code: 41 | (require 'xml) 42 | (require 'citar) 43 | (require 'citeproc) 44 | 45 | (defvar org-cite-csl--fallback-locales-dir) 46 | 47 | (defcustom citar-citeproc-csl-styles-dir nil 48 | "Path to CSL style directory." 49 | :group 'citar 50 | :type 'directory) 51 | 52 | (defcustom citar-citeproc-csl-locales-dir nil 53 | "Path to CSL locales dir." 54 | :group 'citar 55 | :type 'directory) 56 | 57 | (defvar citar-citeproc-csl-style nil 58 | "CSL style file to be used with `citar-citeproc-format-reference'. 59 | 60 | If file is located in the directory set to 61 | `citar-citeproc-csl-styles-dir', only the filename itself is 62 | necessary, e.g., \"chicago-author-date.csl\". Full path is also 63 | accepted.") 64 | 65 | (defun citar-citeproc-csl-metadata (file) 66 | "Return metadata value from csl FILE." 67 | (let* ((parse-tree (xml-parse-file file)) 68 | (style-node (assq 'style parse-tree)) 69 | (info (car (xml-get-children style-node 'info))) 70 | (title (caddr (car (xml-get-children info 'title))))) 71 | title)) 72 | 73 | ;;;###autoload 74 | (defun citar-citeproc-select-csl-style () 75 | "Select CSL style to be used with `citar-citeproc-format-reference'." 76 | (interactive) 77 | (unless (or citar-citeproc-csl-styles-dir org-cite-csl--fallback-locales-dir) 78 | (error "Be sure to set 'citar-citeproc-csl-styles-dir' to your CSL styles directory")) 79 | (let* ((files (directory-files citar-citeproc-csl-styles-dir t "csl")) 80 | (list (mapcar 81 | (lambda (file) 82 | (cons (citar-citeproc-csl-metadata file) (file-name-nondirectory file))) 83 | files)) 84 | (style 85 | (if (= (length list) 1) 86 | (caar list) 87 | (completing-read "Select CSL style: " list nil t))) 88 | (file (cdr (assoc style list)))) 89 | (setq citar-citeproc-csl-style file))) 90 | 91 | ;;;###autoload 92 | (defun citar-citeproc-format-reference (keys &optional style) 93 | "Return formatted reference(s) for KEYS via `citeproc-el'. 94 | Formatting follows CSL style set in `citar-citeproc-csl-style'. 95 | With prefix-argument, select CSL style. 96 | STYLE is a CSL style as a path or a string." 97 | (when (or (eq citar-citeproc-csl-style nil) 98 | current-prefix-arg) 99 | (citar-citeproc-select-csl-style)) 100 | (when-let* ((localesdir 101 | ; since org ships with default files, use those as fallback 102 | (or citar-citeproc-csl-locales-dir org-cite-csl--fallback-locales-dir)) 103 | (stylesdir (or citar-citeproc-csl-styles-dir 104 | ; this dir currently holds default locale and style file 105 | org-cite-csl--fallback-locales-dir)) 106 | (style (or style 107 | (if (string-match-p "/" citar-citeproc-csl-style) 108 | citar-citeproc-csl-style 109 | (expand-file-name 110 | citar-citeproc-csl-style stylesdir)))) 111 | (proc (citeproc-create style 112 | #'citar-citeproc--itemgetter 113 | (citeproc-locale-getter-from-dir localesdir) 114 | "en-US")) 115 | (references (car (progn 116 | (citeproc-add-uncited keys proc) 117 | (citeproc-render-bib proc 'plain))))) 118 | references)) 119 | 120 | ;; from org-cite-csl-activate; András Simonyi 121 | (defun citar-citeproc--cslize-special-vars (entry) 122 | "Convert bibtex format name and date field values in ENTRY to CSL." 123 | (mapcar 124 | (pcase-lambda (`(,var . ,value)) 125 | (cons var 126 | (cond ((memq var citeproc--date-vars) (citeproc-bt--to-csl-date value nil)) 127 | ((memq var citeproc--name-vars) (citeproc-bt--to-csl-names value)) 128 | (t value)))) 129 | entry)) 130 | 131 | (defun citar-citeproc--csl-from-entry (entry) 132 | "Return a CSL version of ENTRY." 133 | (pcase (caar entry) 134 | ('nil nil) 135 | ;; If keys are strings then it is a bib(la)tex entry, which has to be converted 136 | ;; to CSL. 137 | ((pred stringp) (citeproc-blt-entry-to-csl entry)) 138 | ;; Symbol keys indicate CSL entries, only special vars are converted. 139 | ((pred symbolp) (citar-citeproc--cslize-special-vars entry)) 140 | (_ (error "Bib entry with unknown format: %s" entry)))) 141 | 142 | (defun citar-citeproc--itemgetter (keys) 143 | "Return itemdata for KEYS from the citar cache." 144 | (mapcar 145 | (lambda (key) 146 | (let ((citar-entry (citar-get-entry key))) 147 | (cons key (citar-citeproc--csl-from-entry citar-entry)))) 148 | keys)) 149 | 150 | (provide 'citar-citeproc) 151 | ;;; citar-citeproc.el ends here 152 | -------------------------------------------------------------------------------- /citar-format.el: -------------------------------------------------------------------------------- 1 | ;;; citar-format.el --- Formatting functions for citar -*- lexical-binding: t; -*- 2 | ;; 3 | ;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus, Roshan Shariff 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | ;; 6 | ;;; Commentary: 7 | ;; 8 | ;; Functions for formatting bibliography entries. 9 | ;; 10 | ;;; Code: 11 | 12 | (eval-when-compile 13 | (require 'cl-lib)) 14 | 15 | (defvar citar-display-transform-functions) 16 | (declare-function citar-get-display-value "citar") 17 | 18 | 19 | ;;; Formatting bibliography entries 20 | 21 | 22 | (cl-defun citar-format--entry (format entry &optional width 23 | &key hide-elided ellipsis) 24 | "Format ENTRY according to FORMAT. 25 | FORMAT may be either a format string or a parsed format string as 26 | returned by `citar-format--parse'." 27 | (let* ((fieldspecs (if (stringp format) (citar-format--parse format) format)) 28 | (preform (citar-format--preformat fieldspecs entry 29 | hide-elided ellipsis))) 30 | (if width 31 | (citar-format--star-widths (- width (car preform)) (cdr preform) 32 | hide-elided ellipsis) 33 | (apply #'concat (cdr preform))))) 34 | 35 | 36 | ;;; Pre-formatting bibliography entries 37 | 38 | 39 | (defun citar-format--preformat (fieldspecs entry hide-elided ellipsis) 40 | "Pre-format ENTRY using parsed format string FIELDSPECS. 41 | FIELDSPECS should be the result of `citar-format--parse'. See the 42 | documentation of `citar-format--string' for the meaning of 43 | HIDE-ELIDED and ELLIPSIS." 44 | (let ((preformatted nil) 45 | (fields "") 46 | (width 0)) 47 | (dolist (fieldspec fieldspecs) 48 | (pcase fieldspec 49 | ((pred stringp) 50 | (cl-callf concat fields fieldspec) 51 | (cl-incf width (string-width fieldspec))) 52 | (`(,props . ,fieldnames) 53 | (let* ((fieldwidth (plist-get props :width)) 54 | (textprops (plist-get props :text-properties)) 55 | (transform (plist-get props :transform)) 56 | (value (citar-get-display-value fieldnames entry transform)) 57 | (display (citar-format--string value 58 | :width fieldwidth 59 | :text-properties textprops 60 | :hide-elided hide-elided 61 | :ellipsis ellipsis))) 62 | (cond 63 | ((eq '* fieldwidth) 64 | (push fields preformatted) 65 | (setq fields "") 66 | (push display preformatted)) 67 | (t 68 | (cl-callf concat fields display) 69 | (cl-incf width (if (numberp fieldwidth) 70 | fieldwidth 71 | (string-width value))))))))) 72 | (unless (string-empty-p fields) 73 | (push fields preformatted)) 74 | (cons width (nreverse preformatted)))) 75 | 76 | 77 | ;;; Internal implementation functions 78 | 79 | 80 | (cl-defsubst citar-format--string (string 81 | &key width text-properties hide-elided ellipsis) 82 | "Truncate STRING to WIDTH and apply TEXT-PROPERTIES. 83 | If HIDE-ELIDED is non-nil, the truncated part of STRING is 84 | covered by a display property that makes it invisible, instead of 85 | being deleted. ELLIPSIS, when non-nil, specifies a string to 86 | display instead of the truncated part of the text." 87 | (when text-properties 88 | (setq string (apply #'propertize string text-properties))) 89 | (when (numberp width) 90 | (setq string (truncate-string-to-width string width 0 ?\s ellipsis hide-elided))) 91 | string) 92 | 93 | 94 | (defun citar-format--star-widths (alloc strings &optional hide-elided ellipsis) 95 | "Concatenate STRINGS and truncate every other element to fit in ALLOC. 96 | Use this function along with `citar-format--preformat' to fit a 97 | formatted string to a desired display width; see 98 | `citar-format--entry' for how to do this. 99 | 100 | Return a string consisting of the concatenated elements of 101 | STRINGS. The odd-numbered elements are included as-is, while the 102 | even-numbered elements are padded or truncated to a total width 103 | of ALLOC, which must be an integer. All these odd-numbered 104 | elements are allocated close-to-equal widths. 105 | 106 | Perform the truncation using `citar-format--string', which see 107 | for the meaning of HIDE-ELIDED and ELLIPSIS." 108 | (let ((nstars (/ (length strings) 2))) 109 | (if (= 0 nstars) 110 | (or (car strings) "") 111 | (cl-loop 112 | with alloc = (max 0 alloc) 113 | with starwidth = (/ alloc nstars) 114 | with remainder = (% alloc nstars) 115 | with formatted = (car strings) 116 | for (starstring following) on (cdr strings) by #'cddr 117 | for nthstar from 1 118 | do (let* ((starwidth (if (> nthstar remainder) starwidth 119 | (1+ starwidth))) 120 | (starstring (citar-format--string 121 | starstring 122 | :width starwidth 123 | :hide-elided hide-elided :ellipsis ellipsis))) 124 | (cl-callf concat formatted starstring following)) 125 | finally return formatted)))) 126 | 127 | 128 | ;;; Parsing format strings 129 | 130 | (defun citar-format--get-transform (key) 131 | "Return transform spec for KEY." 132 | (cdr (assoc key citar-display-transform-functions))) 133 | 134 | (defun citar-format--parse (format-string) 135 | "Parse FORMAT-STRING." 136 | (let ((regex (concat "\\${" ; ${ 137 | "\\(.*?\\)" ; field names 138 | "\\(?::[[:blank:]]*" ; : + space 139 | "\\(.*?\\)\\)?" ; field width 140 | "[[:blank:]]*" ; space 141 | "\\(?:%[[:blank:]]*" ; % + space 142 | "\\(.*?\\)\\)?" ; display transform 143 | "[[:blank:]]*" ; space 144 | "}")) ; } 145 | (position 0) 146 | (fieldspecs nil)) 147 | (while (string-match regex format-string position) 148 | (let* ((begin (match-beginning 0)) 149 | (end (match-end 0)) 150 | (textprops (text-properties-at begin format-string)) 151 | (fieldnames (match-string-no-properties 1 format-string)) 152 | (fieldwidth (match-string-no-properties 2 format-string)) 153 | (transformkey (match-string-no-properties 3 format-string)) 154 | (width (cond 155 | ((string-equal fieldwidth "*") '*) 156 | ((or (null fieldwidth) (string-empty-p fieldwidth) 157 | (= 0 (string-to-number fieldwidth))) nil) 158 | (t (string-to-number fieldwidth)))) 159 | (transform 160 | (when (and transformkey (not (string-empty-p transformkey))) 161 | (citar-format--get-transform (intern transformkey))))) 162 | (when (< position begin) 163 | (push (substring format-string position begin) fieldspecs)) 164 | (push (cons (nconc (when width `(:width ,width)) 165 | (when textprops `(:text-properties ,textprops)) 166 | (when transform `(:transform ,transform))) 167 | (split-string-and-unquote fieldnames)) 168 | fieldspecs) 169 | (setq position end))) 170 | (when (< position (length format-string)) 171 | (push (substring format-string position) fieldspecs)) 172 | (nreverse fieldspecs))) 173 | 174 | 175 | (provide 'citar-format) 176 | ;;; citar-format.el ends here 177 | -------------------------------------------------------------------------------- /citar-latex.el: -------------------------------------------------------------------------------- 1 | ;;; citar-latex.el --- Latex adapter for citar -*- lexical-binding: t; -*- 2 | 3 | ;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | ;;; Commentary: 7 | 8 | ;; A small package that provides the functions required to use citar 9 | ;; with latex. 10 | 11 | ;; Loading this file will enable manipulating the citations with 12 | ;; commands provided by citar. 13 | 14 | ;;; Code: 15 | 16 | (require 'citar) 17 | (require 'tex nil t) 18 | (require 'reftex-cite) 19 | 20 | (defvar TeX-esc) 21 | 22 | (declare-function TeX-find-macro-boundaries "ext:tex") 23 | (declare-function TeX-parse-macro "ext:tex") 24 | 25 | (defvar citar-major-mode-functions) 26 | 27 | (defcustom citar-latex-cite-commands 28 | '((("cite" "Cite" "citet" "Citet" "citep" "Citep" "parencite" 29 | "Parencite" "footcite" "footcitetext" "textcite" "Textcite" 30 | "smartcite" "Smartcite" "cite*" "parencite*" "autocite" 31 | "Autocite" "autocite*" "Autocite*" "citeauthor" "Citeauthor" 32 | "citeauthor*" "Citeauthor*" "citetitle" "citetitle*" "citeyear" 33 | "citeyear*" "citedate" "citedate*" "citeurl" "fullcite" 34 | "footfullcite" "notecite" "Notecite" "pnotecite" "Pnotecite" 35 | "fnotecite") 36 | . (["Prenote"] ["Postnote"] t)) 37 | (("nocite" "supercite") . nil)) 38 | "Citation commands and their argument specs. 39 | 40 | The argument spec is the same as the args argument of 41 | `TeX-parse-macro'. When calling `citar-insert-citation' the keys 42 | will be inserted at the position where `TeX-parse-macro' leaves 43 | the point." 44 | :group 'citar-latex 45 | :type '(alist :key-type (repeat string) 46 | :value-type sexp)) 47 | 48 | (defcustom citar-latex-prompt-for-cite-style t 49 | "Whether to prompt for a citation command when inserting." 50 | :group 'citar 51 | :type '(radio (const :tag "Prompt for a command" t) 52 | (const :tag "Do not prompt for a command" nil)) 53 | :safe 'always) 54 | 55 | (defcustom citar-latex-default-cite-command "cite" 56 | "Default command for citations. 57 | 58 | Must be in `citar-latex-cite-commands'. Used when as a cite 59 | command when prompting for one is disabled, and as the default 60 | entry when it is enabled." 61 | :group 'citar 62 | :type 'string 63 | :safe 'always) 64 | 65 | (defcustom citar-latex-prompt-for-extra-arguments t 66 | "Whether to prompt for additional arguments when inserting a citation." 67 | :group 'citar-latex 68 | :type 'boolean) 69 | 70 | ;;;###autoload 71 | (defun citar-latex-local-bib-files () 72 | "Local bibliographic for latex retrieved using reftex." 73 | (ignore-errors (copy-sequence (reftex-get-bibfile-list)))) 74 | 75 | ;;;###autoload 76 | (defun citar-latex-key-at-point () 77 | "Return citation key at point with its bounds. 78 | 79 | The return value is (KEY . BOUNDS), where KEY is the citation key 80 | at point and BOUNDS is a pair of buffer positions. 81 | 82 | Return nil if there is no key at point." 83 | (save-excursion 84 | (when-let* ((bounds (citar-latex--macro-bounds)) 85 | (keych "^,{}") 86 | (beg (progn (skip-chars-backward keych (car bounds)) (point))) 87 | (end (progn (skip-chars-forward keych (cdr bounds)) (point))) 88 | (pre (buffer-substring-no-properties (car bounds) beg)) 89 | (post (buffer-substring-no-properties end (cdr bounds)))) 90 | (and (string-match-p "{\\([^{}]*,\\)?\\'" pre) ; preceded by { ... , 91 | (string-match-p "\\`\\(,[^{}]*\\)?}" post) ; followed by , ... } 92 | (goto-char beg) 93 | (looking-at (concat "[[:space:]]*\\([" keych "]+?\\)[[:space:]]*[,}]")) 94 | (cons (match-string-no-properties 1) 95 | (cons (match-beginning 1) (match-end 1))))))) 96 | 97 | ;;;###autoload 98 | (defun citar-latex-citation-at-point () 99 | "Find citation macro at point and extract keys. 100 | 101 | Find brace-delimited strings inside the bounds of the macro, 102 | splits them at comma characters, and trims whitespace. 103 | 104 | Return (KEYS . BOUNDS), where KEYS is a list of the found 105 | citation keys and BOUNDS is a pair of buffer positions indicating 106 | the start and end of the citation macro." 107 | (save-excursion 108 | (when-let* ((bounds (citar-latex--macro-bounds))) 109 | (let ((keylists nil)) 110 | (goto-char (car bounds)) 111 | (while (re-search-forward "{\\([^{}]*\\)}" (cdr bounds) 'noerror) 112 | (push (split-string (match-string-no-properties 1) "," t "[[:space:]]*") 113 | keylists)) 114 | (cons (apply #'append (nreverse keylists)) 115 | bounds))))) 116 | 117 | (defun citar-latex--macro-bounds () 118 | "Return the bounds of the citation macro at point. 119 | 120 | Return a pair of buffer positions indicating the beginning and 121 | end of the enclosing citation macro, or nil if point is not 122 | inside a citation macro." 123 | (unless (fboundp 'TeX-find-macro-boundaries) 124 | (error "Please install AUCTeX")) 125 | (save-excursion 126 | (when-let* ((bounds (TeX-find-macro-boundaries)) 127 | (macro (progn (goto-char (car bounds)) 128 | (looking-at (concat (regexp-quote TeX-esc) 129 | "\\([@A-Za-z]+\\)")) 130 | (match-string-no-properties 1)))) 131 | (when (citar-latex--is-a-cite-command macro) 132 | bounds)))) 133 | 134 | (defvar citar-latex-cite-command-history nil 135 | "Variable for history of cite commands.") 136 | 137 | ;;;###autoload 138 | (defun citar-latex-insert-citation (keys &optional invert-prompt command) 139 | "Insert a citation consisting of KEYS. 140 | 141 | If the command is inside a citation command keys are added to it. Otherwise 142 | a new command is started. 143 | 144 | If the optional COMMAND is provided use it (ignoring INVERT-PROMPT). 145 | Otherwise prompt for a citation command, depending on the value of 146 | `citar-latex-prompt-for-cite-style'. If INVERT-PROMPT is non-nil, invert 147 | whether or not to prompt. 148 | 149 | The availiable commands and how to provide them arguments are configured 150 | by `citar-latex-cite-commands'. 151 | 152 | If `citar-latex-prompt-for-extra-arguments' is nil, every 153 | command is assumed to have a single argument into which keys are 154 | inserted." 155 | (when keys 156 | (if-let* ((bounds (citar-latex--macro-bounds))) 157 | (pcase-exhaustive (progn (skip-chars-forward "^,{}" (cdr bounds)) 158 | (following-char)) 159 | ((guard (= (point) (cdr bounds))) ; couldn't find any of ",{}" 160 | (insert "{}") 161 | (backward-char)) 162 | ((or ?{ ?,) ; insert after following "{" or "," 163 | (forward-char) 164 | (unless (looking-at-p "[[:space:]]*[},]") 165 | (insert ",") 166 | (backward-char))) 167 | (?} ; insert before "}" 168 | (skip-chars-backward "[:space:]") 169 | (unless (member (preceding-char) '(?{ ?,)) 170 | (insert ",")))) 171 | (let ((macro 172 | (or command 173 | (if (xor invert-prompt citar-latex-prompt-for-cite-style) 174 | (citar-latex--select-command) 175 | citar-latex-default-cite-command)))) 176 | (TeX-parse-macro macro 177 | (when citar-latex-prompt-for-extra-arguments 178 | (cdr (citar-latex--is-a-cite-command macro)))))) 179 | (insert (string-join keys ",")) 180 | (skip-chars-forward "^}") (forward-char 1))) 181 | 182 | ;;;###autoload 183 | (defun citar-latex-insert-edit (&optional _arg) 184 | "Prompt for keys and call `citar-latex-insert-citation. 185 | With ARG non-nil, rebuild the cache before offering candidates." 186 | (citar-latex-insert-citation (citar-select-refs))) 187 | 188 | (defun citar-latex--select-command () 189 | "Complete a citation command for LaTeX." 190 | (completing-read "Cite command: " 191 | (seq-mapcat #'car citar-latex-cite-commands) 192 | nil nil nil 193 | 'citar-latex-cite-command-history 194 | citar-latex-default-cite-command nil)) 195 | 196 | (defun citar-latex--is-a-cite-command (command) 197 | "Return element of `citar-latex-cite-commands' containing COMMAND." 198 | (seq-find (lambda (x) (member command (car x))) 199 | citar-latex-cite-commands)) 200 | 201 | ;;;###autoload 202 | (defalias 'citar-latex-list-keys #'reftex-all-used-citation-keys) 203 | 204 | (provide 'citar-latex) 205 | ;;; citar-latex.el ends here 206 | -------------------------------------------------------------------------------- /citar-cache.el: -------------------------------------------------------------------------------- 1 | ;;; citar-cache.el --- Cache functions for citar -*- lexical-binding: t; -*- 2 | ;; 3 | ;; SPDX-FileCopyrightText: 2022 Bruce D'Arcus, Roshan Shariff 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | ;;; Commentary: 7 | ;; 8 | ;; Functions for caching bibliography files. 9 | ;; 10 | ;;; Code: 11 | 12 | (eval-when-compile 13 | (require 'cl-lib)) 14 | (require 'parsebib) 15 | (require 'citar-format) 16 | (require 'seq) 17 | (require 'map) 18 | 19 | (declare-function citar--get-template "citar" (template-name)) 20 | (declare-function citar--fields-to-parse "citar" ()) 21 | (declare-function citar--prepend-candidate-citekey "citar" (citekey candidate)) 22 | 23 | (defvar citar-ellipsis) 24 | 25 | ;;; Variables: 26 | 27 | 28 | (defvar citar-cache--bibliographies (make-hash-table :test 'equal) 29 | "Cache for parsed bibliography files. 30 | This is a hash table with keys being file names and the values 31 | being `citar-cache--bibliography' objects.") 32 | 33 | 34 | ;;; Bibliography objects 35 | 36 | 37 | (cl-defstruct (citar-cache--bibliography 38 | (:constructor citar-cache--make-bibliography (filename)) 39 | (:copier nil)) 40 | "Cached bibliography file." 41 | (filename 42 | nil 43 | :read-only t 44 | :documentation 45 | "True filename of a bibliography, as returned by `file-truename'.") 46 | (buffers 47 | nil 48 | :documentation 49 | "List of buffers that require this bibliography.") 50 | (props 51 | nil 52 | :documentation 53 | "Plist with keys :size, :mtime, :hash, and :fields; attributes 54 | of the cached file and the fields parsed from it.") 55 | (entries 56 | (make-hash-table :test 'equal) 57 | :documentation 58 | "Hash table mapping citation keys to bibliography entries, 59 | as returned by `parsebib-parse'.") 60 | (format-string 61 | nil 62 | :documentation 63 | "Format string used to generate pre-formatted strings.") 64 | (preformatted 65 | (make-hash-table :test 'equal) 66 | :documentation 67 | "Pre-formatted strings used to display bibliography entries; 68 | see `citar--preformatter'.")) 69 | 70 | 71 | (defun citar-cache--get-bibliographies (filenames &optional buffer) 72 | "Return cached bibliographies for FILENAMES and associate them with BUFFER. 73 | FILENAMES is a list of bibliography file names. If BUFFER is nil, 74 | use the current buffer. Otherwise, BUFFER should be a buffer 75 | object or name that requires these bibliographies, or a symbol 76 | like `global'. 77 | 78 | Remove any existing associations between BUFFER and cached files 79 | not included in FILENAMES. Release cached files that are no 80 | longer needed by any other buffer. 81 | 82 | Return a list of `citar-cache--bibliography' objects, one for each 83 | element of FILENAMES." 84 | (citar-cache--release-bibliographies filenames buffer) 85 | (let ((buffer (citar-cache--canonicalize-buffer buffer))) 86 | (mapcar 87 | (lambda (filename) 88 | (let ((bib (citar-cache--get-bibliography filename))) 89 | (prog1 bib 90 | ;; Associate buffer with this bibliography: 91 | (cl-pushnew buffer (citar-cache--bibliography-buffers bib)) 92 | ;; Release bibliography when buffer is killed or changes major mode: 93 | (when (bufferp buffer) 94 | (with-current-buffer buffer 95 | (dolist (hook '(change-major-mode-hook kill-buffer-hook)) 96 | (add-hook hook #'citar-cache--release-bibliographies 0 'local))))))) 97 | filenames))) 98 | 99 | (defun citar-cache--entry (key bibs) 100 | "Find the first entry for KEY in the bibliographies BIBS. 101 | BIBS should be a list of `citar-cache--bibliography' objects." 102 | (catch :found 103 | (dolist (bib bibs) 104 | (let* ((entries (citar-cache--bibliography-entries bib)) 105 | (entry (gethash key entries))) 106 | (when entry (throw :found entry)))))) 107 | 108 | (defun citar-cache--entries (bibs) 109 | "Return hash table containing merged entries of BIBS. 110 | BIBS should be a list of `citar-cache--bibliography' objects. If 111 | a key is present in multiple bibliographies in BIBS, keep the 112 | entry that appears first. Return a hash table mapping the keys of 113 | all BIBS to their entries." 114 | (apply #'map-merge '(hash-table :test equal) 115 | (nreverse (mapcar #'citar-cache--bibliography-entries bibs)))) 116 | 117 | (defun citar-cache--preformatted (bibs) 118 | "Return hash table containing pre-formatted strings from BIBS." 119 | (apply #'map-merge '(hash-table :test equal) 120 | (nreverse (mapcar #'citar-cache--bibliography-preformatted bibs)))) 121 | 122 | 123 | ;;; Creating and deleting bibliography caches 124 | 125 | 126 | (defun citar-cache--get-bibliography (filename &optional force-update) 127 | "Return cached bibliography for FILENAME. 128 | 129 | If FILENAME is not already cached, read and cache it. If 130 | FORCE-UPDATE is non-nil, re-read the bibliography even if it is 131 | has not changed. 132 | 133 | Note: This function should not be called directly; use 134 | `citar-get-bibliographies' instead. This function adds a 135 | bibliography to the cache without associating it with any buffer, 136 | so it will never be evicted from the cache. Use 137 | `citar-cache--get-bibliographies' to ensure that the cached 138 | bibliographies are removed when the associated buffers no longer 139 | need them." 140 | (let* ((cached (gethash filename citar-cache--bibliographies)) 141 | (cachedprops (and cached (citar-cache--bibliography-props cached))) 142 | (cachedfmtstr (and cached (citar-cache--bibliography-format-string cached))) 143 | (props (citar-cache--get-bibliography-props filename cachedprops)) 144 | (fmtstr (citar--get-template 'completion)) 145 | (bib (or cached (citar-cache--make-bibliography filename)))) 146 | (prog1 bib 147 | ;; Set the format string so it's correct when updating bibliography 148 | (setf (citar-cache--bibliography-format-string bib) fmtstr) 149 | ;; Update bibliography if needed or forced 150 | (if (or force-update 151 | (citar-cache--update-bibliography-p cachedprops props)) 152 | (citar-cache--update-bibliography bib props) 153 | ;; Otherwise, update props anyway in case mtime has changed: 154 | (setf (citar-cache--bibliography-props bib) props) 155 | ;; Pre-format if format string has changed even though bibliography hasn't 156 | (unless (equal-including-properties fmtstr cachedfmtstr) 157 | (citar-cache--preformat-bibliography bib))) 158 | ;; Add bibliography to cache: 159 | (unless cached 160 | (puthash filename bib citar-cache--bibliographies))))) 161 | 162 | 163 | (defun citar-cache--release-bibliographies (&optional keep-filenames buffer) 164 | "Dissociate BUFFER from cached bibliographies. 165 | If BUFFER is nil, use the current buffer. Otherwise, BUFFER 166 | should be a buffer object, buffer name, or a symbol like 167 | `global'. KEEP-FILENAMES is a list of file names that are not 168 | dissociated from BUFFER. 169 | 170 | Remove any bibliographies from the cache that are no longer 171 | needed by any other buffer." 172 | (let ((buffer (citar-cache--canonicalize-buffer buffer))) 173 | (maphash 174 | (lambda (filename bib) 175 | (unless (member filename keep-filenames) 176 | (cl-callf2 delq buffer (citar-cache--bibliography-buffers bib)) 177 | (unless (citar-cache--bibliography-buffers bib) 178 | (citar-cache--remove-bibliography filename)))) 179 | citar-cache--bibliographies))) 180 | 181 | 182 | (defun citar-cache--remove-bibliography (filename) 183 | "Remove bibliography cache entry for FILENAME." 184 | ;; TODO Perform other needed actions, like removing filenotify watches 185 | (remhash filename citar-cache--bibliographies)) 186 | 187 | 188 | ;;; Updating bibliographies 189 | 190 | (defun citar-cache--get-bibliography-props (filename &optional oldprops) 191 | "Return attributes to decide if bibliography FILENAME needs to be updated. 192 | Return a plist with keys :size, :mtime, :hash, and :fields. 193 | OLDPROPS, if given, should be a plist with the same keys. If 194 | FILENAME has the same size and modification time as in OLDPROPS, 195 | then assume that the hash value is also the same without 196 | re-hashing the file contents." 197 | (let* ((remote-file-name-inhibit-cache t) 198 | (attr (file-attributes filename 'integer)) 199 | (size (file-attribute-size attr)) 200 | (mtime (file-attribute-modification-time attr)) 201 | (fields (citar--fields-to-parse)) 202 | (oldhash (plist-get oldprops :hash)) 203 | (hash (if (and (stringp oldhash) 204 | (equal size (plist-get oldprops :size)) 205 | (equal mtime (plist-get oldprops :mtime))) 206 | oldhash ; if size and mtime are unchanged, assume hash is the same 207 | (with-temp-buffer 208 | (insert-file-contents filename) 209 | (buffer-hash))))) 210 | `(:size ,size :mtime ,mtime :hash ,hash :fields ,fields))) 211 | 212 | (defun citar-cache--update-bibliography-p (oldprops newprops) 213 | "Return whether bibliography needs to be updated. 214 | Compare NEWPROPS with OLDPROPS and decide whether the file 215 | contents have changed or the list of bibliography fields to be 216 | parsed is different." 217 | (not (and (equal (plist-get oldprops :size) (plist-get newprops :size)) 218 | (equal (plist-get oldprops :hash) (plist-get newprops :hash)) 219 | (equal (plist-get oldprops :fields) (plist-get newprops :fields))))) 220 | 221 | (defun citar-cache--update-bibliography (bib &optional props) 222 | "Update the bibliography BIB from the original file. 223 | 224 | PROPS should be a plist returned by 225 | `citar-cache--get-bibliography-props'; if PROPS is unspecified; 226 | use the value returned by that function. This argument is 227 | provided in case that function has already been called so that 228 | its return value can be reused. 229 | 230 | After updating, the `props' slot of BIB is set to PROPS." 231 | (let* ((filename (citar-cache--bibliography-filename bib)) 232 | (props (or props (citar-cache--get-bibliography-props filename))) 233 | (entries (citar-cache--bibliography-entries bib)) 234 | (messagestr (format "Updating bibliography %s" (abbreviate-file-name filename))) 235 | (starttime (current-time))) 236 | (message "%s..." messagestr) 237 | (redisplay) ; Make sure message is displayed before Emacs gets busy parsing 238 | (clrhash entries) 239 | (parsebib-parse filename :entries entries) 240 | (setf (citar-cache--bibliography-props bib) props) 241 | (citar-cache--preformat-bibliography bib) 242 | (message "%s...done (%.3f seconds)" messagestr (float-time (time-since starttime))))) 243 | 244 | 245 | (defun citar-cache--preformat-bibliography (bib) 246 | "Updated pre-formatted strings in BIB." 247 | (let* ((entries (citar-cache--bibliography-entries bib)) 248 | (formatstr (citar-cache--bibliography-format-string bib)) 249 | (fieldspecs (citar-format--parse formatstr)) 250 | (preformatted (citar-cache--bibliography-preformatted bib))) 251 | (clrhash preformatted) 252 | (maphash 253 | (lambda (citekey entry) 254 | (let* ((preformat (citar-format--preformat fieldspecs entry 255 | t citar-ellipsis)) 256 | (withkey (citar--prepend-candidate-citekey citekey (cadr preformat)))) 257 | (setcdr preformat (cons withkey (cddr preformat))) 258 | (puthash citekey preformat preformatted))) 259 | entries))) 260 | 261 | 262 | ;;; Utility functions: 263 | 264 | 265 | (defun citar-cache--canonicalize-buffer (buffer) 266 | "Return buffer object or symbol denoted by BUFFER. 267 | If BUFFER is nil, return the current buffer. Otherwise, BUFFER 268 | should be a buffer object or name, or a symbol like `global'. If 269 | it is a buffer object or symbol, it is returned as-is. Otherwise, 270 | return the buffer object whose name is BUFFER." 271 | (cond ((null buffer) (current-buffer)) 272 | ((symbolp buffer) buffer) 273 | (t (get-buffer buffer)))) 274 | 275 | 276 | (provide 'citar-cache) 277 | ;;; citar-cache.el ends here 278 | -------------------------------------------------------------------------------- /citar-file.el: -------------------------------------------------------------------------------- 1 | ;;; citar-file.el --- File functions for citar -*- lexical-binding: t; -*- 2 | ;; 3 | ;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | ;; This file is not part of GNU Emacs. 7 | ;; 8 | ;;; Commentary: 9 | ;; 10 | ;; Functions for opening and creating bibliographic files related to a source. 11 | ;; 12 | ;;; Code: 13 | 14 | (eval-when-compile 15 | (require 'cl-lib) 16 | (require 'subr-x)) 17 | (require 'seq) 18 | (require 'map) 19 | 20 | (declare-function citar-get-value "citar" (field key-or-entry)) 21 | (declare-function citar--bibliography-files "citar" (&rest buffers)) 22 | (declare-function citar--check-configuration "citar" (&rest variables)) 23 | (declare-function citar--get-resources-using-function "citar" (func &optional keys)) 24 | 25 | ;;;; File related variables 26 | 27 | (defcustom citar-file-variable "file" 28 | "The field key to look for in an entry for PDF, etc." 29 | :group 'citar 30 | :type '(string)) 31 | 32 | (defcustom citar-file-parser-functions 33 | '(citar-file--parser-default 34 | citar-file--parser-triplet) 35 | "List of functions to parse file field." 36 | :group 'citar 37 | :type '(repeat function)) 38 | 39 | (defcustom citar-file-open-functions (list (cons "html" #'citar-file-open-external) 40 | (cons t #'find-file)) 41 | "Functions used by `citar-file-open` to open files. 42 | 43 | Should be an alist where each entry is of the form (EXTENSION . 44 | FUNCTION). A file whose name ends with EXTENSION will be opened 45 | using FUNCTION. If no entries are found with a matching 46 | extension, FUNCTION associated with key t will be used as the 47 | default." 48 | :group 'citar 49 | :type '(repeat (cons 50 | (choice (string :tag "Extension") 51 | (symbol :tag "Default" t)) 52 | (function :tag "Function")))) 53 | 54 | ;; TODO move this to citar.el for consistency with `citar-library-file-extensions'? 55 | (defcustom citar-file-note-extensions '("org" "md") 56 | "List of file extensions to filter for notes. 57 | 58 | These are the extensions the `citar-open-note-function' 59 | will open, via `citar-open-notes'." 60 | :group 'citar 61 | :type '(repeat string)) 62 | 63 | (defcustom citar-file-additional-files-separator nil 64 | "Find additional library files starting with reference key. 65 | 66 | If nil, the functions `citar-open-library-files' and 67 | `citar-open-notes' only locate files with the naming scheme 68 | \".\". Otherwise, the value of this variable 69 | should be a regular expression that separates the key from 70 | optional additional text following the key in the file name. 71 | Then files named as \".\" are 72 | also located. 73 | 74 | Note: when non-nil, the value of this variable should be a 75 | separator that does not otherwise occur in citation keys." 76 | :group 'citar 77 | :type '(choice (const :tag "Ignore additional files" nil) 78 | (const :tag "Find files with space after key" "[[:space:]]") 79 | (regexp :tag "Filename separator"))) 80 | 81 | (defvar citar-notes-paths) 82 | (defvar citar-library-paths) 83 | (defvar citar-library-paths-recursive) 84 | (defvar citar-library-file-extensions) 85 | (defvar citar-note-format-function) 86 | 87 | ;;;; Convenience functions for files and paths 88 | 89 | (defun citar-file--normalize-paths (file-paths) 90 | "Return a list of FILE-PATHS normalized with truename." 91 | ;; REVIEW why hassle with this; just require a list? 92 | (let ((paths (if (stringp file-paths) 93 | (list file-paths) 94 | file-paths))) 95 | (citar-file--files-exist-p paths) 96 | (delete-dups (mapcar #'file-truename paths)))) 97 | 98 | (defun citar-file--files-exist-p (files) 99 | "Check each of a list of FILES exists." 100 | (dolist (file files) 101 | (unless (file-exists-p file) 102 | (user-error "Cannot find file: %s" file)))) 103 | 104 | ;;;; Parsing file fields 105 | 106 | (defun citar-file--parser-default (file-field) 107 | "Split FILE-FIELD by `;'. 108 | If resulting filenames might be backslash-escaped, return both 109 | escaped and unescaped versions. Zotero escapes special characters 110 | like backslashes and colons, both of which commonly appear in 111 | Windows filenames." 112 | (mapcan 113 | (lambda (filename) 114 | (let* ((trimmed (string-trim filename)) 115 | (unescaped (replace-regexp-in-string "\\\\\\(.\\)" "\\1" trimmed))) 116 | (unless (string-empty-p trimmed) 117 | (if (string= trimmed unescaped) 118 | (list trimmed) 119 | (list unescaped trimmed))))) 120 | (citar-file--split-escaped-string file-field ?\;))) 121 | 122 | (defun citar-file--parser-triplet (file-field) 123 | "Return a list of files from DIRS and a FILE-FIELD formatted as a triplet. 124 | 125 | This is file-field format seen in, for example, Calibre and Mendeley. 126 | 127 | Example: ':/path/to/test.pdf:PDF'." 128 | (let (filenames) 129 | (dolist (sepchar '(?\; ?,)) ; Mendeley and Zotero use ;, Calibre uses , 130 | (dolist (substring (citar-file--split-escaped-string file-field sepchar)) 131 | (let* ((triplet (citar-file--split-escaped-string substring ?:)) 132 | (len (length triplet))) 133 | (when (>= len 3) 134 | ;; If there are more than three components, we probably split on unescaped : in the filename. 135 | ;; Take all but the first and last components of TRIPLET and join them with : 136 | (let* ((escaped (string-join (butlast (cdr triplet)) ":")) 137 | (filename (replace-regexp-in-string "\\\\\\(.\\)" "\\1" escaped))) 138 | ;; Calibre doesn't escape file names in BIB files, so try both 139 | ;; See https://github.com/kovidgoyal/calibre/blob/master/src/calibre/library/catalogs/bibtex.py 140 | (push filename filenames) 141 | (push escaped filenames)))))) 142 | (nreverse filenames))) 143 | 144 | (defun citar-file--parse-file-field (fieldvalue dirs &optional citekey) 145 | "Return files found in file field FIELDVALUE. 146 | Relative file names are expanded from the first directory in DIRS 147 | in which they are found. Omit non-existing absolute file names 148 | and relative file names not found in DIRS. On failure, print a 149 | message explaining the cause; CITEKEY is included in this failure 150 | message." 151 | (if-let* ((files (delete-dups (mapcan (lambda (parser) 152 | (funcall parser fieldvalue)) 153 | citar-file-parser-functions)))) 154 | (if-let* ((foundfiles (citar-file--find-files-in-dirs files dirs))) 155 | (if (null citar-library-file-extensions) 156 | foundfiles 157 | (or (seq-filter (lambda (file) 158 | (member (file-name-extension file) citar-library-file-extensions)) 159 | foundfiles) 160 | (ignore 161 | (message "No files for `%s' with `citar-library-file-extensions': %S" 162 | citekey foundfiles)))) 163 | (ignore 164 | (message (concat "None of the files for `%s' exist; check `citar-library-paths' and " 165 | "`citar-file-parser-functions': %S") 166 | citekey files))) 167 | (ignore 168 | (if (string-empty-p (string-trim fieldvalue)) 169 | (message "Empty `%s' field: %s" citar-file-variable citekey) 170 | (message "Could not parse `%s' field of `%s'; check `citar-file-parser-functions': %s" 171 | citar-file-variable citekey fieldvalue))))) 172 | 173 | (defun citar-file--has-file-field () 174 | "Return predicate to test if bibliography entry in ENTRIES has a file field. 175 | Note: this function is intended to be used in 176 | `citar-has-files-functions'. Use `citar-has-files' to test 177 | whether entries have associated files." 178 | (when citar-file-variable 179 | (lambda (citekey) (and (citar-get-value citar-file-variable citekey) t)))) 180 | 181 | (defun citar-file--library-dirs () 182 | "Return all directories to be searched for library files." 183 | (apply #'append 184 | (mapcar (lambda (dir) 185 | (cons dir 186 | (when citar-library-paths-recursive 187 | (seq-filter #'file-directory-p 188 | (directory-files-recursively dir "" 189 | :include-directories))))) 190 | citar-library-paths))) 191 | 192 | (defun citar-file--get-from-file-field (&optional keys) 193 | "Return files for KEYS by parsing the `citar-file-variable' field. 194 | 195 | Return a hash table mapping each element of KEYS to a list of 196 | files given in the bibliography entry named by 197 | `citar-file-variable'. If KEYS is nil, return files for all 198 | entries. 199 | 200 | Note: this function is intended to be used in 201 | `citar-get-files-functions'. Use `citar-get-files' to get all 202 | files associated with KEYS." 203 | (when-let* ((filefield citar-file-variable)) 204 | (citar--check-configuration 'citar-library-paths 'citar-library-file-extensions 205 | 'citar-file-parser-functions) 206 | (let ((dirs (append (citar-file--library-dirs) 207 | (mapcar #'file-name-directory (citar--bibliography-files))))) 208 | (citar--get-resources-using-function 209 | (lambda (citekey entry) 210 | (when-let* ((fieldvalue (citar-get-value filefield entry))) 211 | (citar-file--parse-file-field fieldvalue dirs citekey))) 212 | keys)))) 213 | 214 | ;;;; Scanning library directories 215 | 216 | (defun citar-file--has-library-files () 217 | "Return predicate testing whether cite key has library files." 218 | (let ((files (citar-file--get-library-files))) 219 | (unless (hash-table-empty-p files) 220 | (lambda (citekey) 221 | (and (gethash citekey files) t))))) 222 | 223 | (defun citar-file--get-library-files (&optional keys) 224 | "Return list of files for KEYS in ENTRIES." 225 | (citar--check-configuration 'citar-library-paths 'citar-library-file-extensions) 226 | (citar-file--directory-files 227 | (citar-file--library-dirs) keys citar-library-file-extensions 228 | citar-file-additional-files-separator)) 229 | 230 | (defun citar-file--make-filename-regexp (keys extensions &optional additional-sep) 231 | "Regexp matching file names starting with KEYS and ending with EXTENSIONS. 232 | When ADDITIONAL-SEP is non-nil, it should be a regular expression 233 | that separates the key from optional additional text that follows 234 | it in matched file names. The returned regexp captures the key 235 | as group 1, the extension as group 2, and any additional text 236 | following the key as group 3." 237 | (when (and (null keys) (string-empty-p additional-sep)) 238 | (setq additional-sep nil)) 239 | (concat 240 | "\\`" 241 | (if keys (regexp-opt keys "\\(?1:") "\\(?1:[^z-a]*?\\)") 242 | (when additional-sep (concat "\\(?3:" additional-sep "[^z-a]*\\)?")) 243 | "\\." 244 | (if extensions (regexp-opt extensions "\\(?2:") "\\(?2:[^.]*\\)") 245 | "\\'")) 246 | 247 | (defun citar-file--directory-files (dirs &optional keys extensions additional-sep) 248 | "Return files in DIRS starting with KEYS and ending with EXTENSIONS. 249 | 250 | Return a hash table mapping keys to lists of file names present 251 | in DIRS. Each file name is divided into three parts: the key, 252 | optional additional text, and the extension: 253 | 254 | - When KEYS is non-nil, each file name must start with an element 255 | of KEYS. Otherwise file names can start with any key. 256 | 257 | - When EXTENSIONS is non-nil, the file extension must match one 258 | of its elements. Otherwise the files can have any extension. 259 | 260 | - When ADDITIONAL-SEP is non-nil, the file name can have 261 | additional text following the key. ADDITIONAL-SEP is a regexp 262 | separating the key from the additional text. 263 | 264 | When KEYS is nil and ADDITIONAL-SEP is non-nil, each file name is 265 | stored in the hash table under two keys: the base name of the 266 | file; and the portion of the file name preceding the first match 267 | of ADDITIONAL-SEP. 268 | 269 | When KEYS is nil, if ADDITIONAL-SEP is empty then it is treated 270 | as being nil. In other words, this function can only scan a 271 | directory for file names matching unknown keys if either 272 | 273 | 1. The key is not followed by any additional text except for the 274 | file extension. 275 | 276 | 2. There is a non-empty ADDITIONAL-SEP between the key and any 277 | following text. 278 | 279 | Note: when KEYS and EXTENSIONS are non-nil and ADDITIONAL-SEP is 280 | nil, this function has an optimized implementation; it checks for 281 | existing files named \"KEY.EXT\" in DIRS, with KEY and EXT being 282 | the elements of KEYS and EXTENSIONS, respectively. It does not 283 | need to scan the contents of DIRS in this case." 284 | (let ((files (make-hash-table :test 'equal)) 285 | (filematch (unless (and keys extensions (not additional-sep)) 286 | (citar-file--make-filename-regexp keys extensions additional-sep)))) 287 | (prog1 files 288 | (dolist (dir dirs) 289 | (when (file-directory-p dir) 290 | (if filematch 291 | ;; Use regexp to scan directory 292 | (dolist (file (directory-files dir nil filematch)) 293 | (let ((key (and (string-match filematch file) (match-string 1 file))) 294 | (filename (expand-file-name file dir)) 295 | (basename (file-name-base file))) 296 | (push filename (gethash key files)) 297 | (unless (or keys (string= key basename)) 298 | (push filename (gethash basename files))))) 299 | ;; Otherwise, check for files named KEY.EXT 300 | (dolist (key keys) 301 | (dolist (ext extensions) 302 | (let ((filename (expand-file-name (concat key "." ext) dir))) 303 | (when (file-exists-p filename) 304 | (push filename (gethash key files))))))))) 305 | ;; Reverse file lists because push adds elements to the front 306 | (maphash (lambda (key filelist) 307 | (puthash key (nreverse filelist) files)) 308 | files)))) 309 | 310 | ;;;; Opening and creating files functions 311 | 312 | (defun citar-file-open (file) 313 | "Open FILE." 314 | (if-let* ((ext (file-name-extension file)) 315 | (func (cdr (or (assoc-string ext citar-file-open-functions 'case-fold) 316 | (assq t citar-file-open-functions))))) 317 | (funcall func (expand-file-name file)) 318 | (user-error "Could not find extension in `citar-file-open-functions': %s" ext))) 319 | 320 | (defun citar-file-open-external (file) 321 | "Open FILE with external application." 322 | ;; Adapted from consult-file-externally. 323 | (if (and (eq system-type 'windows-nt) 324 | (fboundp 'w32-shell-execute)) 325 | (w32-shell-execute "open" file) 326 | (call-process (pcase system-type 327 | ('darwin "open") 328 | ('cygwin "cygstart") 329 | (_ "xdg-open")) 330 | nil 0 nil 331 | file))) 332 | 333 | 334 | ;;;; Note files 335 | 336 | (defun citar-file--get-notes (&optional keys) 337 | "Return note files associated with KEYS. 338 | Return hash table whose keys are elements of KEYS and values are 339 | lists of note file names found in `citar-notes-paths' having 340 | extensions in `citar-file-note-extensions'." 341 | (citar--check-configuration 'citar-notes-paths 'citar-file-note-extensions) 342 | (citar-file--directory-files 343 | citar-notes-paths keys citar-file-note-extensions 344 | citar-file-additional-files-separator)) 345 | 346 | (defun citar-file--has-notes () 347 | "Return predicate testing whether cite key has associated notes." 348 | (let ((notes (citar-file--get-notes))) 349 | (unless (hash-table-empty-p notes) 350 | (lambda (citekey) (and (gethash citekey notes) t))))) 351 | 352 | (defun citar-file--create-note (key entry) 353 | "Create a note file from KEY and ENTRY." 354 | (if-let* ((filename (citar-file--get-note-filename key))) 355 | (prog1 (find-file filename) 356 | (unless (file-exists-p filename) 357 | (citar--check-configuration 'citar-note-format-function) 358 | (funcall citar-note-format-function key entry))) 359 | (user-error "Make sure `citar-notes-paths' and `citar-file-note-extensions' are non-nil"))) 360 | 361 | (defun citar-file--get-note-filename (key) 362 | "Return existing or new note filename for KEY. 363 | 364 | This is for use in a note function where notes are one-per-file, 365 | with citekey as filename. 366 | 367 | Returns the filename whether or not the file exists, to support a 368 | function that will open a new file if the note is not present." 369 | (citar--check-configuration 'citar-notes-paths 'citar-file-note-extensions) 370 | (let* ((dirs citar-notes-paths) 371 | (exts citar-file-note-extensions) 372 | (files (citar-file--directory-files dirs (list key) exts citar-file-additional-files-separator))) 373 | (or (car (gethash key files)) 374 | (when-let* ((dir (car dirs)) 375 | (ext (car exts))) 376 | (expand-file-name (concat key "." ext) dir))))) 377 | 378 | ;;;; Utility functions 379 | 380 | (defun citar-file--split-escaped-string (string sepchar) 381 | "Split STRING into substrings at unescaped occurrences of SEPCHAR. 382 | A character is escaped in STRING if it is preceded by `\\'. The 383 | `\\' character can also escape itself. Return a list of 384 | substrings of STRING delimited by unescaped occurrences of 385 | SEPCHAR." 386 | (let ((skip (format "^\\\\%c" sepchar)) 387 | strings) 388 | (with-temp-buffer 389 | (insert string) 390 | (goto-char (point-min)) 391 | (while (progn (skip-chars-forward skip) (not (eobp))) 392 | (if (= ?\\ (following-char)) 393 | (ignore-error end-of-buffer (forward-char 2)) 394 | (push (delete-and-extract-region (point-min) (point)) strings) 395 | (delete-char 1))) 396 | (push (buffer-string) strings)) 397 | (nreverse strings))) 398 | 399 | (defun citar-file--find-files-in-dirs (files dirs) 400 | "Expand file names in FILES in DIRS and keep the ones that exist." 401 | (let (foundfiles) 402 | (dolist (file files) 403 | (if (file-name-absolute-p file) 404 | (when (file-exists-p file) (push (expand-file-name file) foundfiles)) 405 | (when-let* ((filepath (seq-some (lambda (dir) 406 | (let ((filepath (expand-file-name file dir))) 407 | (when (file-exists-p filepath) filepath))) 408 | dirs))) 409 | (push filepath foundfiles)))) 410 | (nreverse foundfiles))) 411 | 412 | (provide 'citar-file) 413 | ;;; citar-file.el ends here 414 | -------------------------------------------------------------------------------- /citar-org.el: -------------------------------------------------------------------------------- 1 | ;;; citar-org.el --- Org-cite support for citar -*- lexical-binding: t; -*- 2 | 3 | ;; SPDX-FileCopyrightText: 2021-2022 Bruce D'Arcus 4 | ;; SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | ;; This file is not part of GNU Emacs. 7 | ;; 8 | ;;; Commentary: 9 | 10 | ;; This is a small package that integrates citar and org-cite. It 11 | ;; provides a simple org-cite processor with "follow", "insert", and 12 | ;; "activate" capabilities. 13 | 14 | ;; Simply load this file (or its generated autoloads) and it will 15 | ;; make the processor available to 'org-cite'. To instruct org-cite 16 | ;; to use citar, set one or more of the customization variables 17 | ;; 'org-cite-activate-processor', 'org-cite-follow-processor', and 18 | ;; 'org-cite-insert-processor' to the symbol 'citar. 19 | 20 | ;;; Code: 21 | 22 | (require 'citar) 23 | (require 'org) 24 | (if (not (require 'org-element-ast nil t)) 25 | ;; they moved the functions we need to this file 26 | ;; if it's not present, fallback to the old one 27 | (require 'org-element)) 28 | (require 'org-id) 29 | (require 'oc) 30 | (require 'oc-basic) 31 | (require 'oc-csl) 32 | 33 | (declare-function org-open-at-point "org") 34 | ;; we need to account for the move of these functions to a different file 35 | (declare-function org-element-property nil) 36 | (declare-function org-element-type nil) 37 | 38 | (declare-function org-cite-make-insert-processor "oc") 39 | (declare-function org-cite-get-references "oc") 40 | (declare-function embark-act "ext:embark") 41 | (declare-function evil-insert "ext:evil") 42 | (declare-function org-roam-ref-add "ext:org-roam-node") 43 | (defvar citar-open-note-function) 44 | (defvar citar-file-open-function) 45 | (defvar embark-target-finders) 46 | (defvar embark-keymap-alist) 47 | (defvar embark-pre-action-hooks) 48 | 49 | (defface citar-org-style-preview 50 | ;; Not sure if this is the best parent face. 51 | '((t :inherit citar)) 52 | "Face for org-cite previews." 53 | :group 'citar-org) 54 | 55 | (defcustom citar-org-styles-format 'long 56 | "Style format; whether to use full style names or shortcuts." 57 | :group 'citar-org 58 | :type '(choice 59 | (const long) 60 | (const short))) 61 | 62 | (defcustom citar-org-style-targets nil 63 | "Export processor targets to include in styles list. 64 | 65 | If nil, use `org-cite-supported-styles'." 66 | :group 'citar-org 67 | :type '(repeat :tag "org-cite export processor" symbol)) 68 | 69 | (defcustom citar-org-activation-functions 70 | '(citar-org-cite-basic-activate 71 | citar-org-activate-keymap) 72 | "List of activation functions for a citation. 73 | Each function takes one argument, a citation." 74 | :group 'citar-org 75 | :type '(repeat function)) 76 | 77 | ;;; Keymaps 78 | 79 | (defvar citar-org-citation-map 80 | (let ((map (make-sparse-keymap))) 81 | (define-key map (kbd "") (cons "default action" #'org-open-at-point)) 82 | (with-eval-after-load 'embark 83 | (define-key map (kbd "") (cons "embark act" #'embark-act))) 84 | (define-key map (kbd "C-c C-x DEL") (cons "delete citation" #'citar-org-delete-citation)) 85 | (define-key map (kbd "C-c C-x k") (cons "kill citation" #'citar-org-kill-citation)) 86 | (define-key map (kbd "S-") (cons "shift left" #'citar-org-shift-reference-left)) 87 | (define-key map (kbd "S-") (cons "shift right" #'citar-org-shift-reference-right)) 88 | (define-key map (kbd "M-p") (cons "update prefix/suffix" #'citar-org-update-prefix-suffix)) 89 | map) 90 | "Keymap for interacting with org citations at point.") 91 | 92 | ;; TODO maybe connvert to defcustoms. But this is not really the right approach; 93 | ;; better to just run the export processors to get the previews. But we need 94 | ;; citation context for that, or some other solution to have a citation to 95 | ;; process. 96 | 97 | (defvar citar-org-style-preview-alist 98 | '(("/" . "(de Villiers et al, 2019)") 99 | ("/b" . "de Villiers et al, 2019") 100 | ("/c" . "(De Villiers et al, 2019)") 101 | ("/bc" . "de Villiers et al, 2019") 102 | ;; "text" style. 103 | ("text" . "de Villiers et al (2019)") 104 | ("text/c" . "De Villiers et al (2019)") 105 | ("text/f" . "de Villiers, Smith, Doa, and Jones (2019)") 106 | ("text/cf" . "De Villiers, Smith, Doa, and Jones (2019)") 107 | ;; "author" style. 108 | ("author" . "de Villiers et al") 109 | ("author/c" . "De Villiers et al") 110 | ("author/f" . "de Villiers, Smith, Doa, and Jones") 111 | ("author/cf" . "De Villiers, Smith, Doa, and Jones") 112 | ;; "locators" style. 113 | ("locators" . "(p23)") 114 | ("locators" . "p23") 115 | ;; "noauthor" style. 116 | ("noauthor" . "(2019)") 117 | ("noauthor/b" . "2019"))) 118 | 119 | (defun citar-org--style-candidates () 120 | "Return a list of supported styles as completion candidates." 121 | (let ((styles (citar-org--flat-styles))) 122 | (mapcar 123 | (lambda (style) 124 | (if (and (string-match "/" style) 125 | (< 1 (length style))) 126 | (propertize style 'face 'citar) 127 | (propertize style 'face 'citar-highlight))) 128 | styles))) 129 | 130 | (defun citar-org--flat-styles (&optional proc) 131 | "Return a flat list of supported styles. 132 | 133 | With PROC list, limit to specific processor(s)." 134 | (let ((styles (list))) 135 | (dolist (style-variants (org-cite-supported-styles proc)) 136 | (seq-let (style &rest variants) style-variants 137 | (let ((style-name (if (string= "nil" (car style)) "/" (car style)))) 138 | (push style-name styles) 139 | (dolist (variant variants) 140 | (let ((fstyle 141 | (concat style-name 142 | (unless (string= "/" style-name) "/") 143 | (cadr variant)))) 144 | (push fstyle styles)))))) 145 | styles)) 146 | 147 | ;;; Org-cite processors 148 | 149 | ;; NOTE I may move some or all of these to a separate project 150 | 151 | ;;;###autoload 152 | (defun citar-org-select-key (&optional multiple) 153 | "Return a list of keys when MULTIPLE, or else a key string." 154 | (if multiple 155 | (citar-select-refs) 156 | (citar-select-ref))) 157 | 158 | ;;;###autoload 159 | (defun citar-org-insert-citation (keys &optional style) 160 | "Insert KEYS in org-cite format, with STYLE." 161 | (let ((context (org-element-context))) 162 | (when style 163 | (let ((raw-style 164 | (citar-org-select-style))) 165 | (setq style 166 | (if (string-equal raw-style "") raw-style 167 | (concat "/" raw-style))))) 168 | (if-let* ((citation (citar-org--citation-at-point context))) 169 | (when-let* ((keys (seq-difference keys (org-cite-get-references citation t))) 170 | (keystring (mapconcat (lambda (key) (concat "@" key)) keys "; ")) 171 | (begin (org-element-property :contents-begin citation))) 172 | (if (<= (point) begin) 173 | (org-with-point-at begin 174 | (insert keystring ";")) 175 | (let ((refatpt (citar-org--reference-at-point))) 176 | (org-with-point-at (or (and refatpt (org-element-property :end refatpt)) 177 | (org-element-property :contents-end citation)) 178 | (if (char-equal ?\; (char-before)) 179 | (insert-before-markers keystring ";") 180 | (insert-before-markers ";" keystring)))))) 181 | (if (org-cite--allowed-p context) 182 | (insert 183 | (format "[cite%s:%s]" (or style "") 184 | (mapconcat (lambda (key) (concat "@" key)) keys "; "))) 185 | (user-error "Cannot insert a citation here"))))) 186 | 187 | ;;;###autoload 188 | (defun citar-org-insert-edit (&optional arg) 189 | "Run `org-cite-insert' with citar insert processor. 190 | ARG is used as the prefix argument." 191 | (let ((org-cite-insert-processor 'citar)) 192 | (org-cite-insert arg))) 193 | 194 | ;;;###autoload 195 | (defun citar-org-follow (_datum _arg) 196 | "Follow processor for org-cite." 197 | (call-interactively citar-at-point-function)) 198 | 199 | ;;;###autoload 200 | (defun citar-org-select-style (&optional _arg) 201 | "Complete a citation style for org-cite with preview." 202 | (let* ((oc-styles 203 | ;; Sort the list upfront, but let completion UI handle beyond that. 204 | (sort (citar-org--style-candidates) 'string-lessp)) 205 | (style 206 | (completing-read 207 | "Styles: " 208 | (lambda (str pred action) 209 | (if (eq action 'metadata) 210 | `(metadata 211 | (annotation-function . citar-org--style-preview-annote) 212 | (group-function . citar-org--styles-group-fn)) 213 | (complete-with-action action oc-styles str pred))))) 214 | (style-final (string-trim style))) 215 | (if (string= style-final "/") "" style-final))) 216 | 217 | (defun citar-org--styles-group-fn (style transform) 218 | "Return group title of STYLE or TRANSFORM the candidate. 219 | This is a group-function that groups org-cite style/variant 220 | strings by style." 221 | (let* ((style-str (string-trim style)) 222 | (short-style 223 | (if (string-match "^/[bcf]*" style-str) "default" 224 | (car (split-string style-str "/"))))) 225 | (if transform 226 | ;; Use the candidate string as is, but add back whitespace alignment. 227 | (concat " " (truncate-string-to-width style-str 20 nil 32)) 228 | ;; Transform for grouping and display. 229 | (pcase short-style 230 | ("author" "Author-Only") 231 | ("locators" "Locators-Only") 232 | ("text" "Textual/Narrative") 233 | ("nocite" "No Cite") 234 | ("year" "Year-Only") 235 | ("noauthor" "Suppress Author") 236 | (_ (upcase-initials short-style)))))) 237 | 238 | (defun citar-org--style-preview-annote (style &optional _citation) 239 | "Annotate STYLE with CITATION preview." 240 | ;; TODO rather than use the alist, run the export processors on the citation. 241 | (let* ((preview (or (cdr (assoc style citar-org-style-preview-alist)) "")) 242 | ;; TODO look at how define-face does this. 243 | (formatted-preview (truncate-string-to-width preview 50 nil 32))) 244 | (propertize formatted-preview 'face 'citar-org-style-preview))) 245 | 246 | ;;;###autoload 247 | (defun citar-org-local-bib-files (&rest _args) 248 | "Return local bib file paths for org buffer." 249 | (seq-difference (org-cite-list-bibliography-files) 250 | org-cite-global-bibliography)) 251 | 252 | (defun citar-org-cite-basic-activate (citation) 253 | "Set various text properties on CITATION object. 254 | Fontify whole citation with org-cite face. Fontify key with error face 255 | when it does not belong to known keys. Otherwise, use org-cite-key face. 256 | 257 | Moreover, when mouse is on a known key, display the corresponding 258 | bibliography. On a wrong key, suggest a list of possible keys, and offer 259 | to substitute one of them with a mouse click. 260 | 261 | This function activation function is meant to be added to 262 | `citar-org-activation-functions'. It is a modified version of the 263 | built-in `org-cite-basic-activate' that is more performant by leveraging 264 | citar's caching." 265 | (pcase-let ((`(,beg . ,end) (org-cite-boundaries citation)) 266 | ;; Use citar to retrieve all entries' keys 267 | (keys (let (keys) 268 | (maphash (lambda (key _value) (push key keys)) 269 | (citar-get-entries)) 270 | keys))) 271 | (put-text-property beg end 'font-lock-multiline t) 272 | (add-face-text-property beg end 'org-cite) 273 | (dolist (reference (org-cite-get-references citation)) 274 | (pcase-let* ((`(,beg . ,end) (org-cite-key-boundaries reference)) 275 | (key (org-element-property :key reference))) 276 | ;; Highlight key on mouse over. 277 | (put-text-property beg end 278 | 'mouse-face 279 | org-cite-basic-mouse-over-key-face) 280 | (if (member key keys) 281 | ;; Activate a correct key. Face is `org-cite-key' and `help-echo' displays bibliography entry, for 282 | ;; reference. calls `org-open-at-point'. 283 | (let* ((entry (string-trim (citar-format-reference (list key)))) ; Use citar 284 | (bibliography-entry 285 | (org-element-interpret-data entry))) 286 | (add-face-text-property beg end 'org-cite-key) 287 | (put-text-property beg end 'help-echo bibliography-entry) 288 | (org-cite-basic--set-keymap beg end nil)) 289 | ;; Activate a wrong key. Face is `error', `help-echo' displays possible suggestions. 290 | (add-face-text-property beg end 'error) 291 | (let ((close-keys (org-cite-basic--close-keys key keys))) 292 | (when close-keys 293 | (put-text-property beg end 'help-echo 294 | (concat "Suggestions (mouse-1 to substitute): " 295 | (mapconcat #'identity close-keys " ")))) 296 | ;; When the are close know keys, provides completion to fix the current one. Otherwise, 297 | ;; call `org-cite-insert'. 298 | (org-cite-basic--set-keymap beg end (or close-keys 'all)))))))) 299 | 300 | ;;; Org note function 301 | 302 | (defun citar-org--id-get-create (&optional force) 303 | "Call `org-id-get-create' while maintaining point. 304 | 305 | If point is at the beginning of the buffer and a new properties 306 | drawer is created, move point after the drawer. 307 | 308 | More generally, if `org-id-get-create' inserts text at point, 309 | move point after the insertion. 310 | 311 | With optional argument FORCE, force the creation of a new ID." 312 | (let ((point (point-marker))) 313 | (set-marker-insertion-type point t) 314 | (unwind-protect 315 | (org-id-get-create force) 316 | (goto-char point) 317 | (set-marker point nil)))) 318 | 319 | ;;;###autoload 320 | (defun citar-org-roam-make-preamble (key) 321 | "Add a preamble to org-roam note, with KEY." 322 | (when (and (derived-mode-p 'org-mode) 323 | (fboundp 'org-roam-buffer-p) 324 | (org-roam-buffer-p)) 325 | (ignore-errors (citar-org--id-get-create)) 326 | (ignore-errors (org-roam-ref-add (concat "@" key))))) 327 | 328 | ;;;###autoload 329 | (defun citar-org-format-note-default (key entry) 330 | "Format a note from KEY and ENTRY." 331 | (let* ((template (citar--get-template 'note)) 332 | (note-meta (when template 333 | (citar-format--entry template entry))) 334 | (filepath (expand-file-name 335 | (concat key ".org") 336 | (car citar-notes-paths))) 337 | (buffer (find-file filepath))) 338 | (with-current-buffer buffer 339 | ;; This just overrides other template insertion. 340 | (erase-buffer) 341 | (citar-org-roam-make-preamble key) 342 | (insert "#+title: ") 343 | (when template (insert note-meta)) 344 | (insert "\n\n|\n\n#+print_bibliography:") 345 | (search-backward "|") 346 | (delete-char 1) 347 | (when (fboundp 'evil-insert) 348 | (evil-insert 1))))) 349 | 350 | ;;; Embark target finder 351 | 352 | ;;;###autoload 353 | (defun citar-org-key-at-point () 354 | "Return key at point for org-cite citation-reference or property." 355 | (or (citar-org--key-at-point) 356 | (citar-org--prop-key-at-point))) 357 | 358 | (defun citar-org--key-at-point () 359 | "Return key at point for org-cite citation-reference." 360 | (when-let* ((reference (citar-org--reference-at-point))) 361 | (cons (org-element-property :key reference) 362 | (cons (org-element-property :begin reference) 363 | (org-element-property :end reference))))) 364 | 365 | (defun citar-org--prop-key-at-point () 366 | "Return citekey at point, when in org property drawer. 367 | 368 | Citkey must be formatted as `@key'." 369 | (when (and (equal (org-element-type (org-element-at-point)) 'node-property) 370 | (org-in-regexp (concat "[[:space:]]" org-element-citation-key-re))) 371 | (cons (substring (match-string 0) 2) 372 | (cons (match-beginning 0) 373 | (match-end 0))))) 374 | 375 | ;;;###autoload 376 | (defun citar-org-citation-at-point () 377 | "Return org-cite citation keys at point as a list for `embark'." 378 | (when-let* ((citation (citar-org--citation-at-point))) 379 | (cons (org-cite-get-references citation t) 380 | (org-cite-boundaries citation)))) 381 | 382 | ;;; Functions for editing/modifying citations 383 | 384 | (defun citar-org--reference-at-point (&optional context) 385 | "Return citation-reference org-element at point, if any. 386 | 387 | Argument CONTEXT is an org element at point, usually a citation 388 | or citation-reference." 389 | (when-let* ((context (or context (org-element-context)))) 390 | (when (eq 'citation-reference (org-element-type context)) 391 | context))) 392 | 393 | (defun citar-org--citation-at-point (&optional context) 394 | "Return citation element containing point, if any. 395 | 396 | Argument CONTEXT is an org element at point, usually a citation 397 | or citation-reference." 398 | (let ((element (or context (org-element-context)))) 399 | (while (and element (not (eq 'citation (org-element-type element)))) 400 | (setq element (org-element-property :parent element))) 401 | (when-let* ((bounds (and element (org-cite-boundaries element)))) 402 | (when (and (>= (point) (car bounds)) 403 | (<= (point) (cdr bounds))) 404 | element)))) 405 | 406 | (defun citar-org-list-keys () 407 | "List citation keys in the org buffer." 408 | (let ((org-tree (org-element-parse-buffer))) 409 | (delete-dups 410 | (org-element-map org-tree 'citation-reference 411 | (lambda (r) (org-element-property :key r)) 412 | org-tree)))) 413 | 414 | ;; most of this section is adapted from org-ref-cite 415 | 416 | (defun citar-org-activate-keymap (citation) 417 | "Activation function for CITATION to add keymap and tooltip." 418 | (pcase-let ((`(,beg . ,end) (org-cite-boundaries citation))) 419 | ;; Put the keymap on a citation 420 | (put-text-property beg end 'keymap citar-org-citation-map))) 421 | 422 | (defun citar-org--get-ref-index (refs ref) 423 | "Return index of citation-reference REF within REFS." 424 | (seq-position refs ref 425 | (lambda (r1 r2) 426 | (and (equal (org-element-property :begin r1) 427 | (org-element-property :begin r2)))))) 428 | 429 | (defun citar-org-delete-citation () 430 | "Delete the citation or citation-reference at point." 431 | (interactive) 432 | (org-cite-delete-citation (org-element-context))) 433 | 434 | (defun citar-org-kill-citation () 435 | "Kill (copy) the citation or citation-reference at point." 436 | (interactive) 437 | (let* ((datum (org-element-context))) 438 | (kill-region (org-element-property :begin datum) (org-element-property :end datum)))) 439 | 440 | (defun citar-org-cite-swap (i j lst) 441 | "Swap index I and J in the list LST." 442 | (let ((tempi (nth i lst))) 443 | (setf (nth i lst) (nth j lst)) 444 | (setf (nth j lst) tempi)) 445 | lst) 446 | 447 | (defun citar-org--shift-reference (datum direction) 448 | "When point is on a citation-reference DATUM, shift it in DIRECTION." 449 | (let* ((current-citation (if (eq 'citation (org-element-type datum)) datum 450 | (org-element-property :parent datum))) 451 | (current-ref (when (eq 'citation-reference (org-element-type datum)) datum)) 452 | (point-offset 453 | (- (point) (org-element-property :begin current-ref))) 454 | (refs (org-cite-get-references current-citation)) 455 | (index 456 | (citar-org--get-ref-index refs current-ref))) 457 | 458 | (when (= 1 (length refs)) 459 | (error "You only have one reference; you cannot shift this")) 460 | ;; TODO remove this when the shifting is updated to move to front or end of the list. 461 | (when (or (and (equal index 0) 462 | (equal direction 'left)) 463 | (and (equal (+ 1 index) (length refs)) 464 | (equal direction 'right))) 465 | (error "You cannot shift the reference in this direction")) 466 | (when (null index) 467 | (error "Nothing to shift here")) 468 | (let* 469 | ((v1 470 | (org-element-property :contents-begin current-citation)) 471 | (v2 472 | (org-element-property :contents-end current-citation)) 473 | (new-index 474 | (if (eq 'left direction) (- index 1) (+ index 1)))) 475 | (cl--set-buffer-substring v1 v2 476 | (org-element-interpret-data 477 | (citar-org-cite-swap index new-index refs))) 478 | ;; Now move point to the original ref. 479 | (goto-char (+ (org-element-property :begin (nth new-index 480 | (org-cite-get-references current-citation))) 481 | point-offset))))) 482 | 483 | (defun citar-org-shift-reference-left () 484 | "When point is on a citation-reference, shift it left." 485 | (interactive) 486 | (let ((datum (org-element-context))) 487 | (citar-org--shift-reference datum 'left))) 488 | 489 | (defun citar-org-shift-reference-right () 490 | "When point is on a citation-reference, shift it right." 491 | (interactive) 492 | (let ((datum (org-element-context))) 493 | (citar-org--shift-reference datum 'right))) 494 | 495 | (defun citar-org--update-prefix-suffix (datum) 496 | "Change the prefix and suffix text of the DATUM at point. 497 | DATUM should be a reference, otherwise throw an error." 498 | (let* ((ref-p (eq 'citation-reference (org-element-type datum))) 499 | (ref (if ref-p datum (error "Not on a reference"))) 500 | (key (org-element-property :key ref)) 501 | (citekey-str (propertize key 'face 'mode-line-emphasis)) 502 | (pre (org-element-interpret-data (org-element-property :prefix ref))) 503 | (post (org-element-interpret-data (org-element-property :suffix ref))) 504 | ;; TODO Unsure if we want to process prefix at all 505 | (prefix (read-string (concat "Prefix for " citekey-str ": ") 506 | (string-trim pre))) 507 | (suffix (string-trim-left ; Remove leading whitespace 508 | (read-string (concat "Suffix for " citekey-str ": ") 509 | (string-trim post)))) 510 | ;; Change suffix to have one space prior to the user-inputted suffix, unless suffix is already empty 511 | ;; or just whitespace 512 | (suffix-processed 513 | (concat (unless (string-empty-p suffix) " ") suffix)) 514 | (v1 515 | (org-element-property :begin ref)) 516 | (v2 517 | (org-element-property :end ref))) 518 | (cl--set-buffer-substring v1 v2 519 | (org-element-interpret-data 520 | `(citation-reference 521 | (:key ,key :prefix ,prefix :suffix ,suffix-processed)))))) 522 | 523 | (defun citar-org-update-prefix-suffix (&optional arg) 524 | "Change the prefix and suffix text of the reference at point. 525 | If given ARG, change the prefix and suffix for every reference in 526 | the citation at point. 527 | 528 | If point is not on a reference or citation, throw an error." 529 | (interactive "P") 530 | (let* ((datum (org-element-context)) 531 | (citation-p (eq 'citation (org-element-type datum))) 532 | (ref-p (eq 'citation-reference (org-element-type datum))) 533 | (current-citation (cond 534 | (citation-p datum) 535 | (ref-p (org-element-property :parent datum)) 536 | (t (error "Not on a citation or reference")))) 537 | (refs (org-cite-get-references current-citation))) 538 | (save-excursion 539 | (if (or arg citation-p) 540 | ;; We use dotimes over dolist because the buffer changes as we iterate through the list, meaning we 541 | ;; cannot simply use the initial value of refs all throughout 542 | (dotimes (ref-index (length refs)) 543 | (citar-org--update-prefix-suffix (nth ref-index refs)) 544 | ;; Update refs since the begins and ends for the following references could have changed when 545 | ;; adding a prefix and/or suffix 546 | (setq refs (org-cite-get-references 547 | (if citation-p 548 | (org-element-context) 549 | (org-element-property :parent (org-element-context)))))) 550 | (citar-org--update-prefix-suffix (org-element-context)))))) 551 | 552 | ;; Load this last. 553 | 554 | ;;;###autoload 555 | (defun citar-org-activate (citation) 556 | "Run all the activation functions in `citar-org-activation-functions'. 557 | Argument CITATION is an org-element holding the references." 558 | (dolist (activate-func citar-org-activation-functions) 559 | (funcall activate-func citation))) 560 | 561 | ;;;###autoload 562 | (with-eval-after-load 'oc 563 | (org-cite-register-processor 'citar 564 | :insert (org-cite-make-insert-processor 565 | #'citar-org-select-key 566 | #'citar-org-select-style) 567 | :follow #'citar-org-follow 568 | :activate #'citar-org-activate)) 569 | 570 | (provide 'citar-org) 571 | ;;; citar-org.el ends here 572 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | [[https://melpa.org/#/citar][file:https://melpa.org/packages/citar-badge.svg]] 2 | 3 | * Citar 4 | :PROPERTIES: 5 | :CUSTOM_ID: citar 6 | :END: 7 | 8 | - [[#features][Features]] 9 | - [[#installation][Installation]] 10 | - [[#configuration][Configuration]] 11 | - [[#usage][Usage]] 12 | - [[#comparisons][Comparisons]] 13 | - [[#acknowledgements][Acknowledgements]] 14 | 15 | ** Features 16 | :PROPERTIES: 17 | :CUSTOM_ID: features 18 | :END: 19 | 20 | Citar provides a highly-configurable =completing-read= front-end to browse and act on BibTeX, BibLaTeX, and CSL JSON bibliographic data, and LaTeX, markdown, and org-cite editing support. 21 | 22 | - quick filtering and selection of bibliographic entries from the minibuffer, and various commands to run against them. 23 | - a small =citar-embark= companion package, that provides contextual actions in the minibuffer, and also at-point in org, markdown, and LaTeX buffers. 24 | - seamless caching of multiple global and local bibliographic sources 25 | - configurable APIs for: 26 | - indicatars, that signal the presence of related resources in the minibuffer 27 | - notes, to integrate with dedicated note packages, with external packages available for =org-roam=, =denote=, and =zk= 28 | - major-mode adapters 29 | - entry-opening, to go to the original entry data 30 | 31 | Here's a screenshot with [[https://github.com/minad/vertico][vertico]] and symbol customization [[https://github.com/emacs-citar/citar?tab=readme-ov-file#rich-ui][noted below]]. 32 | 33 | #+caption: vertico with citar 34 | [[file:images/vertico.png]] 35 | 36 | And here's =citar-capf= in a markdown buffer. 37 | 38 | #+caption: completion-at-point in a markdown buffer 39 | [[file:images/capf-md.png]] 40 | 41 | To see citar in action with org-cite, you can watch [[https://emacsconf.org/2021/talks/research/][this Emacs Conf 2021 presentation]] by [[https://github.com/rka97][Ahmed Khaled]]. 42 | 43 | ** Installation 44 | :PROPERTIES: 45 | :CUSTOM_ID: installation 46 | :END: 47 | 48 | There are a variety of ways to install citar: 49 | 50 | - Doom Emacs :: The easiest way to install and configure citar and related packages is to use the [[https://github.com/hlissner/doom-emacs/tree/master/modules/tools/biblio][Doom Emacs biblio module]] with the ~vertico~ completion module. 51 | - MELPA :: citar is also available via [[https://melpa.org/#/citar][MELPA]]. 52 | - GUIX :: provides the ~emacs-citar~ package. 53 | 54 | In addition, the following packages are strongly recommended for the best experience. 55 | 56 | 1. [[https://github.com/minad/vertico][Vertico]] (completion interface) 57 | 2. [[https://github.com/oantolin/orderless][Orderless]] (completion style) 58 | 3. [[https://github.com/oantolin/embark][Embark]] (contextual actions) 59 | 4. [[https://github.com/minad/marginalia][Marginalia]] (annotations, and also candidate classification for Embark) 60 | 61 | In particular, if you want to narrow your search using authors, titles, etc (i.e., not only citation keys), you need to use a completion style that is order independent; for example, [[https://github.com/oantolin/orderless][Orderless]] with ~completion-styles~ set to ~(orderless basic)~ (see [[https://github.com/oantolin/orderless#overview][example config]]). 62 | 63 | We also recommend Emacs 28 or newer. 64 | 65 | ** Configuration 66 | :PROPERTIES: 67 | :CUSTOM_ID: configuration 68 | :END: 69 | 70 | *** Basic 71 | :PROPERTIES: 72 | :CUSTOM_ID: basic 73 | :END: 74 | 75 | This is the minimal configuration, and will work with any completing-read compliant vertical completion UI, like Vertico, or the built-in icomplete-vertical, with actions available via =M-x= commands. 76 | 77 | #+begin_src emacs-lisp 78 | (use-package citar 79 | :custom 80 | (citar-bibliography '("~/bib/references.bib"))) 81 | #+end_src 82 | 83 | *** =citar-capf= 84 | 85 | This package includes a ~completion-at-point~ function to complete citation keys in the buffer, which you can configure like so: 86 | 87 | #+begin_src emacs-lisp 88 | (use-package citar 89 | :custom 90 | (citar-bibliography '("~/bib/references.bib")) 91 | :hook 92 | (LaTeX-mode . citar-capf-setup) 93 | (org-mode . citar-capf-setup)) 94 | #+end_src 95 | 96 | *** Embark 97 | 98 | The =citar-embark= package adds contextual access actions in the minibuffer and at-point via the ~citar-embark-mode~ minor mode. 99 | 100 | When using Embark, the Citar actions are generic, and work the same across org, markdown, and latex modes. 101 | 102 | #+begin_src emacs-lisp 103 | (use-package citar-embark 104 | :after (citar embark) 105 | :no-require 106 | :config (citar-embark-mode)) 107 | #+end_src 108 | 109 | *** Org-Cite 110 | 111 | This shows the buffer actions made available by =citar-embark=: 112 | 113 | #+CAPTION: org-cite at-point integration with =embark-act= 114 | [[file:images/org-cite-embark-point.png]] 115 | 116 | If you want to use Citar only in Org-Mode, this is the best option. 117 | 118 | #+begin_src emacs-lisp 119 | (use-package citar 120 | :no-require 121 | :custom 122 | (org-cite-global-bibliography '("~/bib/references.bib")) 123 | (org-cite-insert-processor 'citar) 124 | (org-cite-follow-processor 'citar) 125 | (org-cite-activate-processor 'citar) 126 | (citar-bibliography org-cite-global-bibliography) 127 | ;; optional: org-cite-insert is also bound to C-c C-x C-@ 128 | :bind 129 | (:map org-mode-map :package org ("C-c b" . #'org-cite-insert))) 130 | #+end_src 131 | 132 | You can insert citations with the =org-cite-insert= command, which is bound to =C-c C-x C-@= in Org-Mode buffers. The 133 | optional ~:bind~ command above also gives it the shorter =C-c b= binding. 134 | 135 | If you prefer to have the Embark menu open with =org-open-at-point=, you should set this variable. 136 | 137 | #+begin_src emacs-lisp 138 | (setq citar-at-point-function 'embark-act) 139 | #+end_src 140 | 141 | You can invoke both =embark-act= and =embark-dwim=, however, independently of =org-at-point=, and in other modes such as =latex-mode=. 142 | 143 | *** Major-mode adapters 144 | :PROPERTIES: 145 | :CUSTOM_ID: major-mode-adapters 146 | :END: 147 | 148 | Citar includes an adapter framework to enable major-mode specific editing integration. 149 | Such adapters can provide the following capabilities, which one can configure with the ~citar-major-mode-functions~ alist: 150 | 151 | 1. ~insert-keys~: to insert citation keys (this may go away though) 152 | 2. ~insert-citation~: to insert citations 153 | 3. ~insert-edit~: to insert citations or edit at point 154 | 4. ~local-bib-files~: to find bibliographic files associated with a buffer 155 | 5. ~key-at-point~: returns the citation key at point 156 | 6. ~citation-at-point~: returns the list of keys in the citation at point 157 | 158 | Citar currently includes the following such adapters: 159 | 160 | 1. ~citar-org~: by default, only supports ~org-cite~, but can one can configure for other formats 161 | 2. ~citar-latex~: configurable bibtex, natbib and biblatex support (requires AUCTeX) 162 | 3. ~citar-markdown~: by default, only supports the ~pandoc~ citation syntax 163 | 164 | None of these should require any configuration, and should load as needed. 165 | 166 | *** Opening reference entries 167 | 168 | The =citar-open-entry= command will open the source data entry. 169 | You may configure this using ~citar-open-entry-function~. 170 | By default, this uses ~citar-open-entry-in-file~, which will open the relevant bibliographic file and move point to the entry. 171 | The other included option is ~citar-open-entry-in-zotero~, which will select the item in Zotero. 172 | Note that functionality depends on [[https://retorque.re/zotero-better-bibtex/][Better BibTeX]] (which you should be using anyway!). 173 | 174 | ** Rich UI 175 | :PROPERTIES: 176 | :CUSTOM_ID: rich-ui 177 | :END: 178 | 179 | There are three sections of the browsing UI. 180 | 181 | 1. The prefix, exploiting the affixation feature only available starting with Emacs 28, and holding the symbols to indicate the presence of PDFs or notes associated with the entries. 182 | 2. The main display, which by default shows author, title, and date. 183 | 3. The suffix, which by default shows citekey, reference type, and (if present) tags or keywords. 184 | 185 | You can search against all of the above content. 186 | For the prefix, you can filter for associated files or notes using =has:file= or =has:notes= respectively (and at least with ~orderless~, even the =:p= or =:n= shorthand). 187 | 188 | #+CAPTION: UI sections 189 | [[file:images/ui-segments.png]] 190 | 191 | *** Templates 192 | 193 | The =citar-templates= variable configures formatting for these sections, as well as the default note function. 194 | Here's the default value: 195 | 196 | #+begin_src emacs-lisp 197 | (setq citar-templates 198 | '((main . "${author editor:30%sn} ${date year issued:4} ${title:48}") 199 | (suffix . " ${=key= id:15} ${=type=:12} ${tags keywords:*}") 200 | (preview . "${author editor:%etal} (${year issued date}) ${title}, ${journal journaltitle publisher container-title collection-title}.\n") 201 | (note . "Notes on ${author editor:%etal}, ${title}"))) 202 | #+end_src 203 | 204 | Note: 205 | 206 | 1. You may include multiple variables in a field; the formatter will print the first one it finds. 207 | 2. If you plan to use CSL JSON at all, you can and should include CSL JSON variables names where appropriate as such options. 208 | The default main template dates field demonstrates this. 209 | 3. The asterisk signals to the formatter to use available space for the column. 210 | 4. The note template does not take widths, as formatting is inline there rather than columnar. 211 | 5. The ~%~ character preceeds a token defined as a key in ~citar-display-transform-functions~, whose value is a list of functions and optional arguments. 212 | Note that if you include this, if you also include a width specification, it must come after the width. 213 | 214 | *** Indicators 215 | 216 | The UI includes configurable indicators. 217 | By default, it includes plain text indicators for, each of which indicates the presence of different resources related to the reference: 218 | 219 | - notes 220 | - library files 221 | - links 222 | - cited (for references cited in the current buffer) 223 | 224 | For other indicators, see the [[https://github.com/emacs-citar/citar/wiki/Indicators][wiki]]. 225 | 226 | Here's a screenshot using this configuration, which removes the links indicator, and mixes plain text and an icon indicator using ~all-the-icons~. 227 | 228 | #+begin_src emacs-lisp 229 | (setq citar-indicators 230 | (list citar-indicator-files ; plain text 231 | citar-indicator-notes-icons)) ; icon 232 | #+end_src 233 | 234 | #+caption: UI with customized indicators. 235 | #+name: fig-indicators 236 | [[images/indicators.png]] 237 | 238 | You can create your own indicators, of course. 239 | Here's an example indicator definition incorporating icons: 240 | 241 | #+begin_src emacs-lisp 242 | (defvar citar-indicator-notes-icons 243 | (citar-indicator-create 244 | :symbol (all-the-icons-material 245 | "speaker_notes" 246 | :face 'all-the-icons-blue 247 | :v-adjust -0.3) 248 | :function #'citar-has-notes 249 | :padding " " 250 | :tag "has:notes")) 251 | #+end_src 252 | 253 | Keep in mind, however, the included predicate functions must be performance-optimized, since the completion UI runs them on your entire library every time you open it. 254 | 255 | **** With [[https://github.com/rainstormstudio/nerd-icons.el][nerd-icons]] (for doom users too) 256 | 257 | [[file:images/indicators-nerd-icons.png]] 258 | 259 | Nerd-icons.el is a library for easily using Nerd Font icons inside Emacs, an alternative to all-the-icons, and it is the icon package that already included in [[https://github.com/doomemacs/doomemacs][Doom Emacs]]. Here's an example of getting a similar look with it: 260 | 261 | #+begin_src emacs-lisp 262 | (defvar citar-indicator-notes-icons 263 | (citar-indicator-create 264 | :symbol (nerd-icons-mdicon 265 | "nf-md-notebook" 266 | :face 'nerd-icons-blue 267 | :v-adjust -0.3) 268 | :function #'citar-has-notes 269 | :padding " " 270 | :tag "has:notes")) 271 | 272 | (defvar citar-indicator-links-icons 273 | (citar-indicator-create 274 | :symbol (nerd-icons-octicon 275 | "nf-oct-link" 276 | :face 'nerd-icons-orange 277 | :v-adjust -0.1) 278 | :function #'citar-has-links 279 | :padding " " 280 | :tag "has:links")) 281 | 282 | (defvar citar-indicator-files-icons 283 | (citar-indicator-create 284 | :symbol (nerd-icons-faicon 285 | "nf-fa-file" 286 | :face 'nerd-icons-green 287 | :v-adjust -0.1) 288 | :function #'citar-has-files 289 | :padding " " 290 | :tag "has:files")) 291 | 292 | (setq citar-indicators 293 | (list citar-indicator-files-icons 294 | citar-indicator-notes-icons 295 | citar-indicator-links-icons)) 296 | #+end_src 297 | ** Test Script 298 | :PROPERTIES: 299 | :CUSTOM_ID: test-script 300 | :END: 301 | 302 | The repository =test= directory also includes a script you can use to run this and associated packages in the =emacs -Q= sandbox. 303 | To do that, simply run =./run.sh= from the =test= directory. 304 | 305 | ** History and predefined searches 306 | :PROPERTIES: 307 | :CUSTOM_ID: history-and-predefined-searches 308 | :END: 309 | 310 | =citar= has functionality similar to the [[https://github.com/tmalsburg/helm-bibtex#p][predefined search]] functionality in =helm-bibtex= and =ivy-bibtex=, but with a different implementation. 311 | Rather than create a new command with the search terms as argument, you just set the =citar-presets= variable, and add the strings you want to access: 312 | 313 | #+begin_src emacs-lisp 314 | (setq citar-presets '("one search string" "another search string")) 315 | #+end_src 316 | 317 | You then have two ways to access these strings from the completion prompt: 318 | 319 | 1. by using =M-n= from the prompt, which will cycle through the strings 320 | 2. by calling =citar-insert-preset= with a keybinding, and then selecting the string 321 | 322 | =citar= also preserves the history of your selections (see caveat below about multiple candidate selection though), which are also accessible in your completion UI, but by using =M-p=. 323 | You can save this history across sessions by adding =citar-history= to =savehist-additional-variables=. 324 | 325 | ** Refreshing the library display 326 | :PROPERTIES: 327 | :CUSTOM_ID: refreshing-the-library-display 328 | :END: 329 | 330 | Citar uses a cache to speed up library display. 331 | If a bib file changes, the cache will automatically update the next time you run a Citar command. 332 | 333 | Note that cached data preformatted completion candidates are independently tracked by file. 334 | So, for example, if you have one very large bibliography file that changes a lot, you might consider splitting into one large file that is more stable, and one-or-more smaller ones that change more frequently. 335 | 336 | *** Reftex and local bibliographic files in LaTeX 337 | 338 | Citar relies on the builtin library reftex to find the bibliographic files included in a LaTeX document. 339 | Reftex scan the LaTeX document to find these files and caches them. 340 | If you find that Citar isn't picking up bibliographic entries in the included files it is most likely due to the fact that reftex needs to do a rescan. 341 | You can use ~reftex-parse-all~ or ~reftex-parse-one~ to do such a scan interactively. 342 | This should be only rarely needed. 343 | 344 | When Citar is using multiple bibliographies, entries in higher-priority bibliographies (the local ones) completely override entries in global bibliographies with the same key. 345 | As a consequence, if your local and global bibliographies have the same keys, and the ~file~ field exists only in the global bibliography, you will not have access to the ~file~ field ---e.g., the PDFs. 346 | In this case, you might want to remove the local files or advice ~citar-latex-local-bib-files~ to prevent the use of local files ---see details in [[https://github.com/emacs-citar/citar/issues/859][issue 859]]. 347 | 348 | ** Notes 349 | 350 | Citar offers configurable note-taking and access integration. 351 | The ~citar-notes-sources~ variable configures note backends, and ~citar-notes-source~ activates your chosen backend. 352 | 353 | A backend primarily specifies functions to update the Citar display, to create the completion candidates, and to open existing and new notes. 354 | See the ~citar-notes-sources~ docstring for details, and the =citar-register-notes-source= and =citar-remove-notes-source= convenience functions. 355 | 356 | ** Files, file association and file-field parsing 357 | 358 | If you have ~citar-library-paths~ set, the relevant open commands will look in those directories for file names of =CITEKEY.EXTENSION=. 359 | They will also parse contents of a file-field. 360 | The ~citar-file-parser-functions~ variable governs which parsers to use, and there are two included parsers: 361 | 362 | 1. The default =citar-file-parser-default= parser works for simple colon or semi-colon-delimited lists of file paths, as in Zotero. 363 | 2. The =citar-file-parser-triplet= works for Mendeley and Calibre, which represent files using a format like =:/path/file.pdf:PDF=. 364 | 365 | If you have a mix of entries created with Zotero and Calibre, you can set it like so and it will parse both: 366 | 367 | #+begin_src emacs-lisp 368 | (setq citar-file-parser-functions 369 | '(citar-file-parser-default 370 | citar-file-parser-triplet)) 371 | #+end_src 372 | 373 | The ~citar-library-file-extensions~ variable governs which file extensions the open commands will recognize; when `nil`, it will recognize all extensions. 374 | The ~citar-file-additional-files-separator~ variable defines what patterns citar should identify for multiple library files for the same reference key. 375 | Here's an example to only recognize pdf and jpg extensions, but additional file names of the form ~test-1.jpg~: 376 | 377 | #+begin_src emacs-lisp 378 | (setq citar-library-file-extensions (list "pdf" "jpg") 379 | citar-file-additional-files-separator "-") 380 | #+end_src 381 | 382 | To change how citar opens files with given extensions, customize the ~citar-file-open-functions~ variable defined in =citar-file.el=. 383 | 384 | When used with embark and consult, you will have a range of alternate actions available for the candidates. 385 | 386 | #+CAPTION: File candidates with embark options 387 | [[file:images/file-browser-embark.png]] 388 | 389 | *** BibTeX Crossref File Support 390 | 391 | For BibTeX entries that have a 'crossref' field, Citar will associate the entry's key with the resources (files, notes, links) that are associated with the cross-referenced entry. 392 | 393 | For example: consider an entry for "Baym1965" that has a 'crossref' field "Meyers1999". When citar-open is called and "Baym1965" is selected, the minibuffer will list all files, notes, and links associated with both "Baym1965" and "Meyers1999". The proper prefixes, denoting an associated file, note, or link, will also be listed with each candidate in the minibuffer. 394 | 395 | NOTE: For the BibTeX crossref feature to work properly, the entry with the 'crossref' field must come *before* the cross-referenced entry in the bib file. (This is a requirement of BibTeX, not of Citar specifically.) In the example above, then, the entry for "Baym1965" must come before the entry for "Meyers1999". 396 | 397 | ** Usage 398 | :PROPERTIES: 399 | :CUSTOM_ID: usage 400 | :END: 401 | 402 | You have a few different ways to use citar. 403 | 404 | *** Org-cite 405 | 406 | Citar includes an org-cite =citar= processor, with "insert," "activate" and "follow" capabilities. 407 | When speaking about org-cite, *citations* refer to a set of one or more *references (citation-references)*, each of which may have text that precedes it (prefix) and text that proceeds it (suffix). 408 | To learn more about org-cite, visit the [[https://orgmode.org/manual/Citations.html][Citations page in the Org Manual]]. 409 | 410 | The "insert processor" uses =citar-select-refs= to browse your library to insert and edit citations and citation references using the =org-cite-insert= command. 411 | The command is context-aware, so *its behavior depends on the point's location in a citation*. 412 | For example, if point: 413 | 414 | - precedes the colon, you are on the /citation prefix/ and will be prompted to edit the style 415 | - is on an existing citation-reference, you will be prompted to replace it 416 | - follows or precedes a citation-reference, you will be prompted to add a new citation-reference 417 | 418 | The "activate processor" runs the list of functions in ~citar-org-activation-functions~, which by default uses ~citar-org-cite-basic-activate~, a version of the ~basic~ processor from ~oc-basic~ to provide fontification that leverages citar's performant caching, as well as a little function that adds a keymap (~citar-org-citation-map~) for editing citations at point. 419 | The ~citar-org-citation-map~ keymap includes the following bindings that provide additional citation and citation-reference editing options. 420 | 421 | | key | binding | description | 422 | |-------------+---------------------------------+-----------------------------------------------------| 423 | | C-c C-x DEL | citar-org-delete-citation | delete citation or citation-reference at point | 424 | | C-c C-x k | citar-org-kill-citation | kill citation or citation-reference at point | 425 | | S- | citar-org-shift-reference-left | move citation-reference at point left | 426 | | S- | citar-org-shift-reference-right | move citation-reference at point right | 427 | | M-p | citar-org-update-prefix-suffix | update prefix and suffix of reference at point, or, | 428 | | | | when called with prefix arg, update all | 429 | | | | citation-references in citation at point | 430 | | | citar-dwim | call the value of =citar-at-point-function= at point | 431 | | | embark-act | call =embark-act= at point | 432 | 433 | 434 | The "follow processor" provides at-point functionality accessible via the =org-open-at-point= command. 435 | By default, in org-mode with org-cite support, when point is on a citation or citation-reference, and you invoke =org-open-at-point=, it will run the command set in =citar-at-point-function=, which is =citar-open= by default. 436 | Changing the value of =citar-at-point-function= to =embark-act= with embark installed and configured will provide access to the standard citar commands at point. 437 | 438 | Org-cite citations include optional "styles" and "variants" to locally modify the citation rendering. 439 | When inserting a new citation, calling =org-cite-insert= with a prefix arg will prompt to select a style. 440 | To edit an existing citation's style, just make sure *point is on the citation prefix* before running =org-cite-insert=, and you will get a list of available styles. 441 | That list is based on your configuration; if you have the =oc-natbib= and =oc-csl= processors configured, for example, the list will include the styles and variants available in those two processors. 442 | The variants included in the bundled processors include the following, with the shortcuts in parentheses: 443 | 444 | - =bare= (=b=): without surrounding punctuation 445 | - =caps= (=c=): force initial capitalization 446 | - =full= (=f=): ignore et al shortening for author names 447 | 448 | Generally, you shouldn't need these, but they can be useful in certain circumstances. 449 | If an export processor doesn't support a specific variant for a specific style, it should just fallback to the base style. 450 | For example, if you specify =text/f=, and the export processor you use doesn't support the =f= variant there, it should just output as if you specified =text=. 451 | 452 | #+CAPTION: citation styles 453 | [[file:images/oc-styles.png]] 454 | 455 | *** =M-x= 456 | :PROPERTIES: 457 | :CUSTOM_ID: m-x 458 | :END: 459 | 460 | Simply do =M-x= and select the command that you want, enter the terms to find the item you are looking for, and hit return. 461 | This runs the default action: the command you invoked. 462 | 463 | *** Access an alternate action via =embark-act= 464 | :PROPERTIES: 465 | :CUSTOM_ID: access-an-alternate-action-via-embark-act 466 | :END: 467 | 468 | If while browsing you instead would rather edit that record, and you have embark installed and configured, this is where =embark-act= comes in. 469 | Simply input the keybinding for =embark-act= (in my case =C-o=), and select the alternate action. 470 | 471 | *** Use =embark-collect-snapshot= 472 | :PROPERTIES: 473 | :CUSTOM_ID: use-embark-collect-snapshot 474 | :END: 475 | 476 | A final option, that can be useful: run =embark-collect-snapshot= (=S=) from =embark-act=. 477 | This will select the candidate subset, and open it in a separate buffer. 478 | From there, you can run the same options discussed above using =embark-act= (which is also bound to =a= in the collect buffer). 479 | 480 | So, for example, say you are working on a paper. You hold the complete super-set of items you are interested in citing at some point in that buffer. 481 | From there, you can run different actions on the candidates at will, rather than search individually for each item you want to cite. 482 | 483 | *** Use =citar-dwim= 484 | :PROPERTIES: 485 | :CUSTOM_ID: use-citar-dwim 486 | :END: 487 | 488 | =M-x citar-dwim= will run the default action on citation keys found at point directly. 489 | If you have =embark= installed, you use can =embark-dwim= instead for the same behavior, and =embark-act= for additional actions at-point. 490 | 491 | If no citation key is found, the minibuffer will open for selection. 492 | You can disable this behavior by setting =citar-at-point-fallback= to nil. 493 | 494 | ** Related Packages 495 | 496 | The following packages extend or otherwise enhance citar. 497 | 498 | *** Notes Sources 499 | 500 | These small packages provide citar notes sources, and so tighter integration with the respective notes management packages. 501 | 502 | - [[https://github.com/emacs-citar/citar-org-roam][citar-org-roam]] 503 | - [[https://github.com/pprevos/citar-denote][citar-denote]] 504 | - [[https://github.com/localauthor/zk][zk-citar]] 505 | 506 | ** Comparisons 507 | :PROPERTIES: 508 | :CUSTOM_ID: comparisons 509 | :END: 510 | 511 | To understand how citar compares to other packages like =org-ref=, =ivy-bibtex= and =helm-bibtex= (and the related =bibtex-completion=), see the [[https://github.com/emacs-citar/citar/wiki/Comparisons][comparisons]] page on the wiki. 512 | 513 | ** Acknowledgements 514 | :PROPERTIES: 515 | :CUSTOM_ID: acknowledgements 516 | :END: 517 | 518 | The ideas in this project were initially worked out in a [[https://github.com/tmalsburg/helm-bibtex/issues/353][conversation]] with [[https://github.com/mtreca][Maxime Tréca]] and [[https://github.com/minad][Daniel Mendler]]. 519 | Daniel, author of [[https://github.com/minad/consult][consult]] and [[https://github.com/minad/marginalia][marginalia]], helped us understand the possibilities of the new suite of completing-read packages, while Maxime came up with an [[https://github.com/tmalsburg/helm-bibtex/pull/355][initial prototype]]. 520 | 521 | This code takes those ideas and re-implements them to fill out the feature set, and also optimize the code clarity and performance. 522 | 523 | # Local Variables: 524 | # org-edit-src-content-indentation: 0 525 | # End: 526 | -------------------------------------------------------------------------------- /test/test.bib: -------------------------------------------------------------------------------- 1 | %%% ==================================================================== 2 | %%% BibTeX-file{ 3 | %%% author = "David Rhead", 4 | %%% version = "1.00", 5 | %%% date = "17 Feb 1990", 6 | %%% time = "17:00 GMT", 7 | %%% filename = "test.bib", 8 | %%% address = "Cripps Computing Centre, 9 | %%% University of Nottingham, 10 | %%% University Park, 11 | %%% Nottingham, 12 | %%% NG7 2RD, 13 | %%% United Kingdom", 14 | %%% telephone = "+44 602 484848 Ext 2670", 15 | %%% FAX = "+44 602 588138", 16 | %%% checksum = "05151 839 2908 25082", 17 | %%% email = "David_Rhead at uk.ac.nott.vme (JANET)", 18 | %%% codetable = "ISO/ASCII", 19 | %%% keywords = "bibliography, citation, references", 20 | %%% supported = "no", 21 | %%% docstring = "This BibTeX database file contains entries 22 | %%% designed for testing whether a BibTeX style 23 | %%% file lays references out as recommended by 24 | %%% certain authorities. (Note, however, that 25 | %%% the BS 1629 examples are from the 1976 26 | %%% edition. The file needs updating to use 27 | %%% examples from the 1989 edition instead.) 28 | %%% 29 | %%% The checksum field above contains a CRC-16 30 | %%% checksum as the first value, followed by the 31 | %%% equivalent of the standard UNIX wc (word 32 | %%% count) utility output of lines, words, and 33 | %%% characters. This is produced by Robert 34 | %%% Solovay's checksum utility.", 35 | %%% } 36 | %%% ==================================================================== 37 | 38 | 39 | %% @COMMENT{Some standard works describing conventions for citations 40 | %% and bibliographies} 41 | 42 | @Techreport{bs-1629, 43 | author = "BSI", 44 | title = "Bibliographic References", 45 | institution = "British Standards Institution", 46 | year = "1976", 47 | type = "BS", 48 | number = "1629", 49 | } 50 | 51 | @Techreport{bs-5605, 52 | author = "BSI", 53 | title = "Citing Publications by Bibliographic References", 54 | institution = "British Standards Institution", 55 | year = "1978", 56 | type = "BS", 57 | number = "5606", 58 | } 59 | 60 | @Techreport{bs-6371, 61 | author = "BSI", 62 | title = "Citation of unpublished documents", 63 | institution = "British Standards Institution", 64 | year = "1983", 65 | type = "BS", 66 | number = "6371", 67 | } 68 | 69 | @Book{butcher-81, 70 | title = "Copy-editing", 71 | author = "Judith Butcher", 72 | publisher = "Cambridge University Press", 73 | year = "1981", 74 | edition = "2nd", 75 | } 76 | 77 | @Book{chicago-82, 78 | title = "The {C}hicago Manual of Style", 79 | author = "`Chicago'", 80 | year = "1982", 81 | publisher = "University of Chicago Press", 82 | edition = "13th", 83 | } 84 | 85 | %% @COMMENT{Some examples taken from the document describing BS 5605} 86 | 87 | @Article{howells-51, 88 | author = "W. W. Howells", 89 | title = "Factors of Human Physique", 90 | journal = "American Journal of Physical Anthropology", 91 | volume = "9", 92 | pages = "159--192", 93 | year = "1951", 94 | } 95 | 96 | @Article{howells-66-pop, 97 | author = "W. W. Howells", 98 | title = "Population Distances: Biological, Linguistic, 99 | Geographical and Environmental", 100 | journal = "Current Anthropology", 101 | volume = "7", 102 | pages = "531--540", 103 | year = "1966", 104 | } 105 | 106 | @Article{howells-66-var, 107 | author = "W. W. Howells", 108 | title = "Variability in Family Lines vs. Population 109 | Variability", 110 | journal = "Annals of the {New York Academy of Sciences}", 111 | volume = "134", 112 | pages = "624--631", 113 | year = "1966", 114 | } 115 | 116 | @Article{johnson-74, 117 | author = "G. B. Johnson", 118 | title = "Enzyme Polymorphism", 119 | journal = "Science", 120 | volume = "184", 121 | pages = "28--37", 122 | year = "1974", 123 | } 124 | 125 | @Article{johnson-howells, 126 | author = "G. B. Johnson and W. W. Howells", 127 | title = "Title title title title title title title title title 128 | title", 129 | journal = "Journal journal journal", 130 | year = "1974", 131 | } 132 | 133 | @Article{johnson-etc, 134 | author = "G. B. Johnson and W. W. Howells and A. N. Other", 135 | title = "Title title title title title title title title title 136 | title", 137 | journal = "Journal journal journal", 138 | year = "1976", 139 | } 140 | 141 | @Manual{anon-67, 142 | author = "Anon.", 143 | title = "Title title title title title title title title title 144 | title", 145 | organization = "Organization organization organization", 146 | year = "1967", 147 | } 148 | 149 | @Article{aslin-49, 150 | author = "E. J. Aslin", 151 | title = "Photostat recording in library work", 152 | journal = "Aslib Proceedings", 153 | year = "1949", 154 | volume = "1", 155 | pages = "49--52", 156 | } 157 | 158 | @Article{fletcher-hopkins, 159 | author = "W. M. Fletcher and F. G. Hopkins", 160 | title = "Lactic Acid in Amphibian Muscle", 161 | journal = "J. Physiol.", 162 | year = "1907", 163 | volume = "35", 164 | pages = "247--309", 165 | } 166 | 167 | @Incollection{hanson-67, 168 | author = "C. W. Hanson", 169 | title = "Subject inquiries and literature searching", 170 | booktitle = "Handbook of special librarianship and information 171 | work", 172 | editor = "W. Ashworth", 173 | year = "1967", 174 | edition = "3rd", 175 | pages = "414--452", 176 | } 177 | 178 | @Inbook{wright-63, 179 | author = "R. C. Wright", 180 | title = "Report Literature", 181 | booktitle = "Special Materials in the Library", 182 | editor = "J. Burkett and T. S. Morgan", 183 | year = "1963", 184 | pages = "46--59", 185 | } 186 | 187 | @Book{BCM-59, 188 | author = "{Association of British Chemical Manufacturers}", 189 | title = "British chemicals and their manufacturers", 190 | year = "1959", 191 | } 192 | 193 | @Inbook{feigl-58, 194 | author = "F. Feigl", 195 | title = "Spot Tests in Organic Analysis", 196 | year = "1958", 197 | publisher = "Publisher publisher", 198 | edition = "5th", 199 | chapter = "6", 200 | } 201 | 202 | %% @COMMENT{Some examples taken from document describing BS 1629} 203 | 204 | @Book{hoel-71-whole, 205 | author = "Paul Gerhard Hoel", 206 | title = "Elementary Statistics", 207 | publisher = "Wiley", 208 | year = "1971", 209 | series = "Wiley series in probability and mathematical 210 | statistics", 211 | address = "New York, Chichester", 212 | edition = "3rd", 213 | note = "ISBN 0~471~40300", 214 | } 215 | 216 | @Inbook{hoel-71-portion, 217 | author = "Paul Gerhard Hoel", 218 | title = "Elementary Statistics", 219 | publisher = "Wiley", 220 | year = "1971", 221 | series = "Wiley series in probability and mathematical 222 | statistics", 223 | address = "New York, Chichester", 224 | edition = "3rd", 225 | note = "ISBN 0~471~40300", 226 | pages = "19--33", 227 | } 228 | 229 | @Book{singer-whole, 230 | year = "1954--58", 231 | editor = "Charles Joseph Singer and E. J. Holmyard and A. R. 232 | Hall", 233 | title = "A history of technology", 234 | address = "London", 235 | publisher = "Oxford University Press", 236 | note = "5 vol.", 237 | } 238 | 239 | @Incollection{singer-portion-chapter, 240 | title = "The late nineteenth century", 241 | year = "1954--58", 242 | editor = "Charles Joseph Singer and E. J. Holmyard and A. R. 243 | Hall", 244 | booktitle = "A history of technology", 245 | address = "London", 246 | publisher = "Oxford University Press", 247 | type = "Vol.", 248 | chapter = "5", 249 | } 250 | 251 | @Incollection{singer-portion-volume, 252 | title = "The late nineteenth century", 253 | year = "1954--58", 254 | editor = "Charles Joseph Singer and E. J. Holmyard and A. R. 255 | Hall", 256 | booktitle = "A history of technology", 257 | address = "London", 258 | publisher = "Oxford University Press", 259 | volume = "5", 260 | } 261 | 262 | @Manual{bs-2570-manual, 263 | author = "BSI", 264 | title = "Natural Fibre Twines", 265 | organization = "British Standards Institution", 266 | address = "London", 267 | edition = "3rd", 268 | year = "1973", 269 | note = "BS 2570", 270 | } 271 | 272 | @Techreport{bs-2570-techreport, 273 | author = "BSI", 274 | title = "Natural Fibre Twines", 275 | institution = "British Standards Institution", 276 | address = "London", 277 | year = "1973", 278 | type = "BS", 279 | number = "2570", 280 | note = "3rd. edn.", 281 | } 282 | 283 | @Inbook{bs-2570-inbook, 284 | author = "BSI", 285 | title = "{BS} 2570: Natural Fibre Twines", 286 | publisher = "British Standards Institution", 287 | address = "London", 288 | year = "1973", 289 | edition = "3rd", 290 | chapter = "5", 291 | type = "{Table}", 292 | } 293 | 294 | @Techreport{ellis-walton, 295 | author = "B. Ellis and A. K. Walton", 296 | title = "A Bibliography on Optical Modulators", 297 | institution = "Royal Aircraft Establishment", 298 | year = "1971", 299 | number = "RAE-TR-71009", 300 | } 301 | 302 | @Article{godfrey-59, 303 | author = "G. Bernard Godfrey", 304 | title = "Joints in Tubular Structures", 305 | journal = "Struct. Eng.", 306 | year = "1959", 307 | volume = "37", 308 | number = "4", 309 | pages = "126--135", 310 | } 311 | 312 | @Incollection{ramsbottom-31, 313 | author = "John Ramsbottom", 314 | title = "Fungi Pathogenic to Man", 315 | booktitle = "A System of Bacteriology in relation to Medicine", 316 | publisher = "HMSO, for Medical Research Council", 317 | year = "1931", 318 | volume = "8", 319 | pages = "11--70", 320 | address = "London", 321 | } 322 | 323 | @Article{hanlon-72, 324 | author = "Joseph Hanlon", 325 | title = "Designing Buildings by Computer", 326 | journal = "New Scientist", 327 | year = "1972", 328 | month = "31~" # aug, 329 | pages = "429--432", 330 | } 331 | 332 | @Techreport{winget-67, 333 | author = "{Winget Ltd.}", 334 | title = "Detachable Bulldozer Attachment for Dumper Vehicles", 335 | year = "1967", 336 | type = "GB Patent Specification", 337 | number = "1060631", 338 | month = "8~" # mar, 339 | } 340 | 341 | @Article{bry-afflerbach, 342 | author = "I. Bry and L. Afflerbach", 343 | title = "In search of an organizing principle for behavioural 344 | science literature", 345 | journal = "Community Mental Health", 346 | year = "1968", 347 | volume = "4", 348 | number = "1", 349 | pages = "75--84", 350 | } 351 | 352 | @Incollection{ranganathan-51, 353 | author = "S. R. Ranganthan", 354 | title = "Colon classification and its approach to 355 | documentation", 356 | booktitle = "Bibliographic Organization", 357 | editor = "Jesse H. Shera and Margaret E. Egan", 358 | year = "1951", 359 | pages = "94--105", 360 | } 361 | 362 | @Book{mccolvin-nodate, 363 | author = "L. R. McColvin", 364 | title = "Libraries in {Britain}", 365 | publisher = "{Longmans Green, for the British Council}", 366 | address = "London", 367 | } 368 | 369 | %% @COMMENT{Some examples taken from document describing BS 6371} 370 | 371 | @Unpublished{exchequer-34-39, 372 | author = "Exchequer", 373 | year = "1634--1639", 374 | title = "Act books", 375 | note = "Edinburgh, Scottish Record Office, E.4/5", 376 | } 377 | 378 | @Unpublished{traquair-38, 379 | author = "{Earl of} Traquair", 380 | year = "1638", 381 | title = "Letter to {Marquess of Hamilton, 28 Aug.}", 382 | note = "Lennoxlove (E.~Lothian), Muniments of Duke of 383 | Hamilton and Brandon, C.1, no. 963", 384 | } 385 | 386 | @Unpublished{pym-24, 387 | author = "J. Pym", 388 | year = "1624", 389 | title = "Diary", 390 | note = "Northampton, Northamptonshire Record Office, 391 | Finch-Hatton 50", 392 | } 393 | 394 | @Phdthesis{croft-78, 395 | author = "W. B. Croft", 396 | year = "1978", 397 | title = "Organizing and searching large files of document 398 | descriptions", 399 | school = "Cambridge University", 400 | } 401 | 402 | %% @COMMENT{Some examples taken from Chapter 10 of "Copy-editing" 403 | %% by Judith Butcher} 404 | 405 | @Book{darcy-20-amaz, 406 | author = "Firstname D'Arcy", 407 | title = "Title title title title title title title title title 408 | title", 409 | publisher = "Publisher publisher publisher", 410 | year = "1920", 411 | } 412 | 413 | @Book{darcy-20-again, 414 | author = "Firstname D'Arcy", 415 | title = "Title title title title title title title title title 416 | title", 417 | publisher = "Publisher publisher publisher", 418 | year = "1920", 419 | } 420 | 421 | @Article{jones-n-h-r, 422 | author = "Firstname Jones and Firstname Norman and Firstname 423 | Hazel and Firstname Robinson", 424 | title = "Title title title title title title title title title 425 | title", 426 | year = "1962", 427 | journal = "Journal journal journal", 428 | } 429 | 430 | @Article{jones-s-r, 431 | author = "Firstname Jones and Firstname Smith and Firstname 432 | Robinson", 433 | title = "Title title title title title title title title title 434 | title", 435 | year = "1962", 436 | journal = "Journal journal journal", 437 | } 438 | 439 | @Article{jones-65, 440 | author = "Firstname Jones", 441 | title = "Title title title title title title title title title 442 | title", 443 | year = "1965", 444 | journal = "Journal journal journal", 445 | } 446 | 447 | @Article{jones-69, 448 | author = "Firstname Jones", 449 | title = "Title title title title title title title title title 450 | title", 451 | year = "1969", 452 | journal = "Journal journal journal", 453 | } 454 | 455 | @Article{jones-abrams, 456 | author = "Firstname Jones and Firstname Abrams", 457 | title = "Title title title title title title title title title 458 | title", 459 | year = "1968", 460 | journal = "Journal journal journal", 461 | } 462 | 463 | @Article{jones-smith, 464 | author = "Firstname Jones and Firstname Smith", 465 | title = "Title title title title title title title title title 466 | title", 467 | year = "1965", 468 | journal = "Journal journal journal", 469 | } 470 | 471 | @Article{jones-a-s, 472 | author = "Firstname Jones and Firstname Abrams and Firstname 473 | Smith", 474 | title = "Title title title title title title title title title 475 | title", 476 | year = "1966", 477 | journal = "Journal journal journal", 478 | } 479 | 480 | @Article{bloggs-60, 481 | author = "A. J. Bloggs", 482 | title = "Title title title title title title title title title 483 | title", 484 | journal = "Journal journal journal", 485 | year = "1960", 486 | } 487 | 488 | @Article{bloggs-61, 489 | author = "A. J. Bloggs", 490 | title = "Title title title title title title title title title 491 | title", 492 | journal = "Journal journal journal", 493 | year = "1961", 494 | } 495 | 496 | @Article{bloggs-jones, 497 | author = "A. J. Bloggs and X. Y. Jones", 498 | title = "Title title title title title title title title title 499 | title", 500 | journal = "Journal journal journal", 501 | year = "1959", 502 | } 503 | 504 | @Article{bloggs-s-j, 505 | author = "A. J. Bloggs and R. S. Smith and X. Y. Jones", 506 | title = "Title title title title title title title title title 507 | title", 508 | journal = "Journal journal journal", 509 | year = "1955", 510 | } 511 | 512 | @Article{brown-f-s, 513 | author = "H. W. Brown and A. S. Forbes and S. D. Smith", 514 | title = "Title title title title title title title title title 515 | title", 516 | journal = "Journal journal journal", 517 | year = "1900", 518 | } 519 | 520 | @Incollection{eckstein-zuckerman, 521 | author = "P. Eckstein and S. Zuckermann", 522 | title = "Morphology of the Reproductive Tract", 523 | booktitle = "Marshall's Physiology of Reproduction", 524 | publisher = "Longman", 525 | year = "1960", 526 | editor = "A. S. Parkes", 527 | volume = "1", 528 | pages = "43--154", 529 | address = "London", 530 | } 531 | 532 | @Article{heller-lederis, 533 | author = "H. Heller and K. Lederis", 534 | title = "Paper chromatography of small amounts of vasopressin 535 | and oxytocin", 536 | year = "1958", 537 | journal = "Nature", 538 | address = "London", 539 | volume = "182", 540 | pages = "1231--2", 541 | } 542 | 543 | @Book{wood-61, 544 | author = "R. H. Wood", 545 | title = "Plastic and Elastic Design of Slabs and Plates", 546 | publisher = "Thames \& Hudson", 547 | address = "London", 548 | year = "1961", 549 | } 550 | 551 | %% @COMMENT{Some examples taken from Chapter 15 of the Chicago Manual of Style} 552 | 553 | @Book{pratt-75, 554 | author = "Firstname Pratt", 555 | title = "Title title title title title title title title title 556 | title", 557 | publisher = "Publisher publisher publisher", 558 | year = "1975", 559 | } 560 | 561 | @Article{light-72, 562 | author = "Firstname Light", 563 | title = "Title title title title title title title title title 564 | title", 565 | journal = "Journal journal journal", 566 | year = "1972", 567 | } 568 | 569 | @Article{light-wong, 570 | author = "Firstname Light and Firstname Wong", 571 | title = "Title title title title title title title title title 572 | title", 573 | journal = "Journal journal journal", 574 | year = "1975", 575 | } 576 | 577 | @Article{kingston-76, 578 | author = "Firstname Kingston", 579 | title = "Title title title title title title title title title 580 | title", 581 | journal = "Journal journal journal", 582 | year = "1976", 583 | } 584 | 585 | @Article{kelley-96-spring, 586 | author = "Firstname Kelley", 587 | title = "Title title title title title title title title title 588 | title", 589 | journal = "Journal journal journal", 590 | year = "1896", 591 | } 592 | 593 | @Article{kelley-96-autumn, 594 | author = "Firstname Kelley", 595 | title = "Title title title title title title title title title 596 | title", 597 | journal = "Journal journal journal", 598 | year = "1896", 599 | } 600 | 601 | @Article{kelley-07, 602 | author = "Firstname Kelley", 603 | title = "Title title title title title title title title title 604 | title", 605 | journal = "Journal journal journal", 606 | year = "1907", 607 | } 608 | 609 | @Book{strong-01, 610 | author = "Firstname Strong", 611 | title = "Title title title title title title title title title 612 | title", 613 | publisher = "Publisher publisher publisher", 614 | year = "1901", 615 | } 616 | 617 | @Book{strong-02, 618 | author = "Firstname Strong", 619 | title = "Title title title title title title title title title 620 | title", 621 | publisher = "Publisher publisher publisher", 622 | year = "1902", 623 | } 624 | 625 | @Book{shotwell-01, 626 | author = "Firstname Shotwell", 627 | title = "Title title title title title title title title title 628 | title", 629 | publisher = "Publisher publisher publisher", 630 | year = "1901", 631 | } 632 | 633 | %% @COMMENT{Some examples taken from Chapter 16 of the Chicago Manual of Style} 634 | 635 | @Techreport{brunswick-85, 636 | author = "`Brunswick'", 637 | title = "The piper and the rats: A musical experiment", 638 | institution = "Rodent Activities Termination Section (RATS), Pest 639 | Control Division, Brunswick Public Welfare Department", 640 | year = "1985", 641 | number = "1984", 642 | address = "Hamelin", 643 | } 644 | 645 | @Book{kendeigh-52, 646 | author = "S. C. Kendeigh", 647 | year = "1952", 648 | title = "Parental care and its evolution in birds", 649 | series = "Illinois Biological Monographs", 650 | volume = "22", 651 | number = "1--3", 652 | address = "Champaign", 653 | publisher = "Univ. of Illinois Press", 654 | } 655 | 656 | @Book{chapman-75, 657 | author = "Jefferson Chapman", 658 | year = "1975", 659 | title = "The {Icehouse Bottom} Site---{40MR23}", 660 | series = "University of Tennessee Department of Anthropology 661 | Publication", 662 | number = "23", 663 | address = "Knoxville", 664 | publisher = "Univ. of Tennessee Press", 665 | } 666 | 667 | @Book{hershkovitz-62, 668 | author = "P. Hershkovitz", 669 | year = "1962", 670 | title = "Evolution of {Neotropical} cricetine rodents 671 | ({Muridae}) with special reference to the phyllotine 672 | group", 673 | series = "Fieldiana: Zoology", 674 | volume = "46", 675 | address = "Chicago", 676 | publisher = "Field Museum of Natural History", 677 | } 678 | 679 | @Book{wright-78-book, 680 | title = "Evolution and the genetics of populations", 681 | author = "Sewall Wright", 682 | year = "1978", 683 | address = "Chicago", 684 | publisher = "Univ. of Chicago Press", 685 | volume = "4", 686 | } 687 | 688 | @Incollection{wright-78-incollection, 689 | title = "Variability within and among natural populations", 690 | author = "Sewall Wright", 691 | booktitle = "Evolution and the genetics of populations", 692 | year = "1978", 693 | address = "Chicago", 694 | publisher = "Univ. of Chicago Press", 695 | type = "Vol.", 696 | chapter = "4", 697 | } 698 | 699 | @Incollection{ogilvy-65, 700 | author = "David Ogilvy", 701 | title = "The Creative Chef", 702 | booktitle = "The Creative Organization", 703 | year = "1965", 704 | editor = "Gary A. Steiner", 705 | address = "Chicago", 706 | publisher = "University of Chicago Press", 707 | pages = "199--213", 708 | } 709 | 710 | @Incollection{mcneill-63, 711 | author = "William H. McNeill", 712 | year = "1963", 713 | title = "The Era of {Middle Eastern} Dominance to 500 {\sc 714 | b.c.}", 715 | booktitle = "The Rise of the {West}", 716 | publisher = "{University of Chicago Press}", 717 | address = "Chicago", 718 | type = "Part", 719 | chapter = "1", 720 | } 721 | 722 | @Incollection{thomson-71, 723 | author = "Virgil Thomson", 724 | title = "Cage and the Collage of Noises", 725 | booktitle = "American Music since 1910", 726 | address = "New York", 727 | publisher = "Holt, Rinehart and Winston", 728 | year = "1971", 729 | chapter = "8", 730 | } 731 | 732 | @Incollection{gordon-75, 733 | title = "The Tunes of {Chicken Little}", 734 | author = "Robert Gordon", 735 | year = "1975", 736 | booktitle = "Playwrights for Tomorrow: A Collection of Plays", 737 | editor = "Arthur H. Ballet", 738 | volume = "13", 739 | address = "Minneapolis", 740 | publisher = "University of Minnesota Press", 741 | note = "One of four plays included in vol. 13", 742 | } 743 | 744 | @Incollection{milton-24, 745 | author = "John Milton", 746 | title = "Paradise Lost", 747 | booktitle = "The Complete Poetical Works of {John Milton}", 748 | edition = "{Student's Cambridge}", 749 | address = "Boston", 750 | publisher = "Houghton Mifflin", 751 | year = "1924", 752 | editor = "William Vaughn Moody", 753 | } 754 | 755 | @Inproceedings{chomsky-73, 756 | author = "N. Chomsky", 757 | year = "1973", 758 | title = "Conditions on Transformations", 759 | booktitle = "A festschrift for {Morris Halle}", 760 | editor = "S. R. Anderson and P. Kiparsky", 761 | publisher = "Holt, Rinehart \& Winston", 762 | address = "New York", 763 | } 764 | 765 | @Inproceedings{chave-64, 766 | author = "K. E. Chave", 767 | year = "1964", 768 | title = "Skeletal durability and preservation", 769 | booktitle = "Approaches to paleoecology", 770 | editor = "J. Imbrie and N. Newel", 771 | address = "New York", 772 | publisher = "Wiley", 773 | pages = "377--87", 774 | } 775 | 776 | @Book{smart-76, 777 | author = "Ninian Smart", 778 | year = "1976", 779 | title = "The religious experience of mankind", 780 | publisher = "Schribner", 781 | address = "New York", 782 | edition = "2nd", 783 | } 784 | 785 | @Article{jackson-79, 786 | author = "Richard Jackson", 787 | year = "1979", 788 | title = "Running down the up-escalator: Regional inequality in 789 | {Papua New Guinea}", 790 | journal = "Australian Geographer", 791 | volume = "14", 792 | month = may, 793 | pages = "175--84", 794 | } 795 | 796 | @Article{prufer-64, 797 | author = "Olaf Prufer", 798 | year = "1964", 799 | title = "The {Hopewell} Cult", 800 | journal = "Scientific {American}", 801 | month = dec, 802 | pages = "90--102", 803 | } 804 | 805 | @Phdthesis{mann-68, 806 | author = "A. E. Mann", 807 | year = "1968", 808 | title = "The palaeodemography of {Australopithecus}", 809 | type = "Ph.D. diss.", 810 | school = "University of California, Berkeley", 811 | } 812 | 813 | @Phdthesis{maguire-76, 814 | author = "J. Maguire", 815 | year = "1976", 816 | title = "A taxonomic and ecological study of the living and 817 | fossil {Hystricidae} with particular reference to 818 | southern {Africa}", 819 | type = "Ph.D. diss.", 820 | school = "Department of Geology, University of the 821 | Witwatersrand", 822 | address = "Johannesburg", 823 | } 824 | 825 | @Unpublished{downes-74, 826 | title = "Systemic grammar and structural sentence relatedness", 827 | author = "W. J. Downes", 828 | year = "1974", 829 | note = "London School of Economics. Mimeo.", 830 | } 831 | 832 | @Misc{hunt-76, 833 | author = "Horace [pseud.] Hunt", 834 | title = "Interview", 835 | year = "1976", 836 | howpublished = "Tape recording, Pennsylvania Historical and Museum 837 | Commission, Harrisburg", 838 | note = "Interview by {Ronald Schatz, 16 May 1976}", 839 | } 840 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------