The main motivation of this package is to make it
easy to interact with org-mode
and
papis
.
We do not want to reinvent the wheel, so this project should be thought to play well with the very good package org-ref.
If you’re an emacs lisp hacker, feel free to chip in. Otherwise this project should be treated as β software and it might just completely change in the future.
I have implemented some functions that I use in my day-to-day research and worfklow.
There is a `papis-ivy` function to open and so on, and org mode links.
At some point it will get documented…
papis.el
is written as a literate program cite:LiteratePrograKnuth1984.
- We interact with papis through the papis’ json exporter.
- We use
org-links
to get information directly from papis.
The libraries that we will need are therefore:
(require 'ol)
(require 'json)
(defcustom papis--temp-output-file
nil
"This variable holds the papis temporary output file where the json
output is dumped"
:type 'string)
(defcustom papis-binary-path
"papis"
"The binary path for papis.
You might have papis installed for instance in some
virtual environment"
:type 'string)
(defcustom papis-ivy-format-function
#'papis-default-ivy-format-function
"Function taking a papis document (hashmap) and outputing a
string representation of it to be fed into ivy.")
(defcustom papis--query-prompt
"sPapis Query: "
"The prompt to show users in order to accept a query
"
:type 'string)
You can set the
You can set the main library used in papis by setting
(setq papis-library "my-other-library")
(defcustom papis-library
nil
"papis library to be used in commands.
If it is set to nil then the default library of your system will
be used.
"
:type 'string)
(defun papis--doc-get-folder (doc)
(papis--doc-get doc "_papis_local_folder"))
(defun papis--get-file-paths (doc)
(mapcar (lambda (f) (concat (papis--doc-get-folder doc) "/" f))
(papis--doc-get doc "files")))
(defun papis--doc-get (doc key)
(gethash key doc))
(defun papis--get-ref (doc)
(papis--doc-get doc "ref"))
(defun papis--doc-update (doc)
(let ((folder (papis--doc-get-folder doc)))
(papis--cmd (concat "update --doc-folder " folder))))
Most papis commands will need a query, the macro @papis-query
will
take care of having the same query prompt in all commands.
(defmacro @papis-query ()
`(interactive ,papis--query-prompt))
The main interface with papis commands will be papis--cmd
which is a function intended for library writers.
(defun papis--cmd (cmd &optional with-stdout)
"Helping function to run papis commands"
(let ((lib-flags (if papis-library
(concat "-l " papis-library)
""))
(sys (if with-stdout
#'shell-command-to-string
#'shell-command)))
(funcall sys
(format "%s %s %s" papis-binary-path lib-flags cmd))))
A papis document object is represented in papis.el
as a hashtable
, and the command that turns a query
into a list of hashtables is papis-query
.
This is done via the papis’ json
exporter, i.e.,
we query python and get a json document with the documents that
emacs reads in.
(defun papis-query (query)
"Make a general papis query:
it returns a list of hashtables where every hashtable is a papis document"
(let* ((json-object-type 'hash-table)
(json-array-type 'list)
(json-key-type 'string)
(papis--temp-output-file (make-temp-file "papis-emacs-"))
(exit-code (papis-json query papis--temp-output-file)))
(if (not (eq exit-code 0))
(error "Something happened running the papis command"))
(json-read-file papis--temp-output-file)))
The cornerstone of papis is opening documents, in emacs the command is also available:
(defun papis--open-doc (doc)
(split-window-horizontally)
(find-file (completing-read "file: " (papis--get-file-paths doc))))
(defun papis-open (query)
(@papis-query)
(papis--open-doc (papis-ivy query)))
You can edit the info files using papis-edit
,
notice that commiting the
Implement waiting after editing the file like
(defun papis-edit (query)
(@papis-query)
(let* ((doc (papis-ivy query))
(folder (papis--doc-get-folder doc))
(info (concat folder "/" "info.yaml")))
(find-file info)
(papis--doc-update doc)))
(defun papis-exec (python-file &optional arguments)
(let ((fmt "exec %s %s"))
(papis--cmd (format fmt
python-file
(or arguments ""))
t)))
(defun papis-json (query outfile)
(papis--cmd (format "export --all --format json '%s' -o %s"
query
outfile)))
(defun papis-bibtex (query outfile)
(papis--cmd (format "export --all --format bibtex '%s' -o %s"
query
outfile)))
The main dynamic searcher used in papis is ivy.
(defun papis-default-ivy-format-function (doc)
`(
,(format "%s\n\t%s\n\t«%s» +%s %s"
(papis--doc-get doc "title")
(papis--doc-get doc "author")
(papis--doc-get doc "year")
(or (papis--doc-get doc "tags") "")
(let ((n (papis--doc-get doc "_note"))) (if n (concat ":note " n) "")))
.
,doc))
(defun papis-ivy (query)
(@papis-query)
(let* ((results (papis-query query))
(formatted-results (mapcar papis-ivy-format-function results))
(ivy-add-newline-after-prompt t))
(cdr (assoc
(completing-read "Select an entry: " formatted-results)
formatted-results))))
We define the link
(org-link-set-parameters "papis+doi"
:follow #'ol-papis+doi-open
:export #'ol-papis+doi-export
:complete #'org-link-papis-store-doi)
(defun ol-papis+doi-open (doi)
"Open papis document by doi"
(papis-open (format "doi:%s" doi)))
(defun ol-papis+doi-export (doi description format)
(cond
((eq format 'html) (format (concat "<a target='_blank'"
" href='https://doi.org/%s'>"
"%s"
"</a>") doi description))
((eq format 'md) (format "[%s](https://doi.org/%s)" description doi))
((eq format 'org) (format "[[doi:%s][%s]]" doi description))
(t description)))
(org-link-set-parameters "papis"
:follow #'ol-papis-open
:export #'ol-papis-export)
(defun ol-papis-open (link)
(let ((doc (papis-ivy link)))
(cond
(doc (papis--open-doc doc))
(t (error "No doc found")))))
(defun ol-papis-export (link description format)
(let ((doi (papis-get-doi description)))
(cond
((eq format 'html) (format (concat "<a target='_blank'"
" href='https://doi.org/%s'>"
"%s"
"</a>") doi description))
((eq format 'md) (format "[%s](https://doi.org/%s)" description doi))
((eq format 'org) (format "[[doi:%s][%s]]" doi description))
(t description))))
(defun papis-get-doi (query)
(@papis-query)
(let ((papis-command (concat "papis list --format "
"{doc[doi]}"
" --all "
"'" query "'")))
(car (s-lines
(shell-command-to-string
papis-command)))))
(defun org-papis-store-doi-link (query)
(@papis-query)
(let ((doc (papis-ivy query)))
(insert (format "[[papis+doi:%s][%s]]"
(papis--doc-get doc "doi")
(papis--doc-get doc "title")))))
(defun org-papis-store-url-link (query)
(@papis-query)
(let ((doc (papis-ivy query)))
(insert (format "[[%s][%s]]"
(papis--doc-get doc "url")
(papis--doc-get doc "title")))))
(defun org-papis-store-file-link (query)
(@papis-query)
(let ((doc (papis-ivy query)))
(insert (format "[[file:%s][%s]]"
(completing-read "file: " (papis--get-file-paths doc))
(papis--doc-get doc "title")))))
When doing research, often you would like to create some notes on every paper and write some sections with the section titles being links to the papers with some properties so that you can use org-mode’s colum mode.
You can use the following function to create a link with properties
(defun org-papis-doi-heading (query)
(@papis-query)
(let* ((doc (papis-ivy query))
(title (papis--doc-get doc "title"))
(author (papis--doc-get doc "author"))
(year (papis--doc-get doc "year")))
(org-insert-heading)
(insert (format "[[papis+doi:%s][%s]]"
(papis--doc-get doc "doi")
title))
(insert "\n")
(insert (format (concat ":PROPERTIES:\n"
":AUTHOR: %s\n"
":TITLE: %s\n"
":YEAR: %s\n"
":END:")
author title year))))
A recommendation can be to write as the COLUMNS
variable and the PROPERTIES
like so
#+COLUMNS: %7TODO %5YEAR %10AUTHOR %45TITLE %TAGS #+PROPERTIES: TITLE AUTHOR YEAR
and then you can turn on the org-columns
mode.
org-ref
can open the pdf of a publicaction
from the cite:my-reference
link, but in the case of papis
this pdf lives in an isolated folder of its own.
However in org-ref
you can customize how you get the pdf
from the cite
link through the
elisp:org-ref-get-pdf-filename-function.
Therefore, in order to use papis to open the pdf of the referenced
documents you can set
(setq org-ref-get-pdf-filename-function
#'papis-org-ref-get-pdf-filename)
Its implementation is given below:
(defun papis-org-ref-get-pdf-filename (key)
(interactive)
(let* ((docs (papis-query (format "ref:'%s'" key)))
(doc (car docs))
(files (papis--get-file-paths doc)))
(pcase (length files)
(1 (car files))
(_ (completing-read "" files)))))
In general it is recommended to use the citation mechanisms of
org-ref
, however, if for some reason you would like to cite
directly from papis
, you can use the function
(defun papis-org-ref-insert-citation-from-query (query)
(@papis-query)
(let* ((doc (papis-ivy query))
(ref (papis--get-ref doc)))
(insert (format "[cite:@%s]" ref))))
and we will need also a way of listing all the keys of the document
for further functions. I took this from the good citar
package
(defun papis-org-list-keys ()
"List citation keys in the org buffer."
(let ((org-tree (org-element-parse-buffer)))
(delete-dups
(org-element-map org-tree 'citation-reference
(lambda (r) (org-element-property :key r))
org-tree))))
In this section we want to develop a way to generate a bibtex bibliography from references appearing in the document currently being edited.
First we need a script that accepts a list of
import argparse
import papis.api
from papis.bibtex import to_bibtex
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
description='')
parser.add_argument('refs', help='References', action='store', nargs='*')
args = parser.parse_args()
docs = []
for ref in args.refs:
docs.extend(papis.api.get_documents_in_lib(library=None, search=ref))
for d in docs:
print(to_bibtex(d))
(defvar papis--refs-to-bibtex-script
"
<<references-to-bibtex-python-script>>
")
(defun papis--refs-to-bibtex (refs)
(let ((py-script (make-temp-file "papis-bibtex-script" nil ".py")))
(with-temp-buffer
(insert papis--refs-to-bibtex-script)
(write-file py-script))
(papis-exec py-script (s-join " " refs))))
(defun papis-create-papis-bibtex-refs-dblock (bibfile)
(insert (format "#+begin: papis-bibtex-refs :tangle %s" bibfile))
(insert "\n")
(insert "#+end:"))
(defun papis-extract-citations-into-dblock (&optional bibfile)
(interactive)
(if (org-find-dblock "papis-bibtex-refs")
(progn
(org-show-entry)
(org-update-dblock))
(papis-create-papis-bibtex-refs-dblock
(or bibfile (read-file-name "Bib file: " nil "main.bib")))))
(defun org-dblock-write:papis-bibtex-refs (params)
(let ((tangle-file (or (plist-get params :tangle)
(buffer-file-name)))
(exports ":exports none"))
(insert
(format "#+begin_src bibtex %s :tangle %s\n"
exports
tangle-file)))
(let* ((refs (papis-org-list-keys))
(queries (mapcar (lambda (r) (format "ref:\"%s\"" r))
refs)))
(insert (papis--refs-to-bibtex queries)))
(insert "#+end_src\n"))
(provide 'papis)
bibliography:main.bib bibliographystyle:unsrt