;;; bibslurp.el --- retrieve BibTeX entries from NASA ADS
;; Copyright (C) 2013 Mike McCourt
;;
;; Authors: Mike McCourt
;; URL: https://github.com/mkmcc/bibslurp
;; Package-Version: 20151202.1546
;; Version: 0.0.2
;; Keywords: bibliography, nasa ads
;; Package-Requires: ((s "1.6.0") (dash "1.5.0"))
;; This file is not part of GNU Emacs.
;; bibslurp is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; bibslurp is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with bibslurp. If not, see http://www.gnu.org/licenses.
;;; Commentary:
;; Provides a function `bibslurp-query-ads', which reads a search
;; string from the minibuffer, sends the query to NASA ADS
;; (http://adswww.harvard.edu/), and displays the results in a new
;; buffer called "ADS Search Results".
;; The "ADS Search Results" buffer opens in `bibslurp-mode', which
;; provides a few handy functions. Typing the number preceding an
;; abstract and hitting RET calls `bibslurp-slurp-bibtex', which
;; fetches the bibtex entry corresponding to the abstract and saves it
;; to the kill ring. Typing 'a' instead pulls up the abstract page.
;; At anytime, you can hit 'q' to quit bibslurp-mode and restore the
;; previous window configuration.
;;; Example usage:
;; add an entry to a bibtex buffer:
;; M-x bibslurp-query-ads RET ^Quataert 2008 RET
;; Move to the abstract you want to cite with n and p keys, or search
;; in the buffer with s or r, and then press
;; RET
;; q
;; C-y
;; If you want to select a different abstract, just type the
;; corresponding number before pressing RET:
;; 15 RET
;; q
;; C-y
;; For more examples and information see the project page at
;; http://astro.berkeley.edu/~mkmcc/software/bibslurp.html
;;; Advanced search
;; You can turn to the ADS advanced search interface, akin to
;; http://adsabs.harvard.edu/abstract_service.html, either by pressing
;; C-c C-c after having issued `bibslurp-query-ads', or directly with
;; M-x `bibslurp-query-ads-advanced-search' RET
;; Here you can fill the wanted search fields (authors, publication
;; date, objects, title, abstract) and specify combination logics, and
;; then send the query either with C-c C-c or by pressing the button
;; "Send Query". Use TAB to move through fields, and q outside an
;; input field to quit the search interface.
;;; Other features
;; In the ADS search result buffer you can also visit some useful
;; pages related to each entry:
;; - on-line data at other data centers, with d
;; - on-line version of the selected article, with e
;; - on-line articles in PDF or Postscript, with f
;; - lists of objects for the selected abstract in the NED database,
;; with N
;; - lists of objects for the selected abstract in the SIMBAD
;; database, with S
;; - on-line pre-print version of the article in the arXiv database,
;; with x
;; For each of these commands, BibSlurp will use by default the
;; abstract point is currenly on, but you can specify a different
;; abstract by prefixing the command with a number. For example,
;; 7 x
;; will fire up your browser to the arXiv version of the seventh
;; abstract in the list.
;;; Notes about the implementation:
;; 1. As far as I know, ADS doesn't have an API for searching its
;; database, and emacs doesn't have functionality to parse html.
;; Since I don't want to implement a browser in emacs lisp, that
;; leaves me parsing the html pages with regexps. While that would
;; be a terrible idea under ordinary circumstances, the ADS pages
;; are all automatically generated, so they should conform to a
;; pretty regular format. That gives me some hope...
;; 2. There are many ways you can customize the behaviour of biblurp.
;; I define font-lock faces at the beginning of the file so you can
;; add these to your color theme. I also run a mode hook at the
;; end of `bibslurp-mode', so you can inject your own code at that
;; point.
;;; Installation:
;; Use package.el. You'll need to add MELPA to your archives:
;; (require 'package)
;; (add-to-list 'package-archives
;; '("melpa" . "https://melpa.org/packages/") t)
;; Alternatively, you can just save this file and do the standard
;; (add-to-list 'load-path "/path/to/bibslurp.el")
;; (require 'bibslurp)
;;; TODO:
;; 1. add hooks for show-abstract?
;; 2. finish documentation
;; 3. clean up code
;; 4. rethink regexps
;;; Code:
(require 's)
(require 'dash)
(require 'widget)
(eval-when-compile
(require 'wid-edit))
(defgroup bibslurp nil
"retrieve BibTeX entries from NASA ADS."
:prefix "bibslurp-"
:group 'convenience
:tag "bibslurp"
:link '(url-link :tag "Home Page"
"https://mkmcc.github.io/software/bibslurp.html"))
;;; start by making a rudimentary web browser
;; define font-lock faces
(defface bibslurp-number-face
'((t (:inherit 'font-lock-string-face)))
"Face for entry number.")
(defface bibslurp-name-face
'((t (:inherit 'italic)))
"Face for entry name.")
(defface bibslurp-score-face
'((t (:inherit 'font-lock-comment-face)))
"Face for entry score.")
(defface bibslurp-date-face
'((t (:inherit 'font-lock-variable-name-face)))
"Face for entry date.")
(defface bibslurp-author-face
'((t (:inherit 'font-lock-builtin-face)))
"Face for entry authors")
(defface bibslurp-title-face
'((t (:inherit 'font-lock-string-face)))
"Face for entry title.")
;; key bindings
(defvar bibslurp-mode-map
(let ((map (make-keymap)))
(suppress-keymap map)
(define-key map (kbd "RET") 'bibslurp-slurp-bibtex)
(define-key map (kbd "z") 'bibslurp-slurp-bibtex)
(define-key map "a" 'bibslurp-show-abstract)
;; Navigation
(define-key map (kbd "SPC") 'scroll-up)
(define-key map (kbd "S-SPC") 'scroll-down)
(define-key map ">" 'end-of-buffer)
(define-key map ""
(use-local-map bibslurp-mode-map))
(defun bibslurp/follow-link (number)
"Return the URL corresponding to the abstract NUMBER.
Argument may be either an integer or a string. Return nil if the
link number is invalid. Links are stored in the list
`bibslurp-entry-list', which is populated by `bibslurp-query-ads'
once the search results are returned."
(setq number
(if (integerp number)
(number-to-string number)
number))
(nth 6 (assoc-string number bibslurp-entry-list)))
(defun bibslurp-quit ()
"Close the bibslurp buffer and restore the previous window
configuration."
(interactive)
(kill-buffer)
(when (get-register :bibslurp-window)
(jump-to-register :bibslurp-window)))
(defun bibslurp/build-ads-url (search-string)
"Helper function which turns a search string (e.g. \"^Quataert
2008\") into an ads search url. Used by `bibslurp-query-ads'."
(let ((base-url
"http://adsabs.harvard.edu/cgi-bin/nph-basic_connect?qsearch=")
(url-sep "+")
(url-end "&version=1"))
(concat base-url
(replace-regexp-in-string " " url-sep search-string)
url-end)))
;; functions to parse and display the search results page.
(defvar bibslurp-query-history nil
"History for `bibslurp-query-ads'.")
(defvar bibslurp-entry-list nil
"List of entries for the current search.
For each entry, the elements are:
* 0: number of the entry, starting from 1
* 1: score
* 2: bibcode
* 3: date
* 4: authors
* 5: title
* 6: URL of the abstract
All elements are string.")
(defun bibslurp/search-results (search-url &optional search-string)
"Create the buffer for the results of a search.
Displays results in a new buffer called \"ADS Search Results\"
and enters `bibslurp-mode'. You can retrieve a bibtex entry by
typing the number in front of the abstract link and hitting
enter. Hit 'a' instead to pull up the abstract. You can exit
the mode at any time by hitting 'q'."
(let ((buf (get-buffer-create "ADS Search Results"))
(inhibit-read-only t))
(with-temp-buffer
(url-insert-file-contents search-url)
(setq bibslurp-entry-list
(-map 'bibslurp/clean-entry (bibslurp/read-table))))
(with-current-buffer buf
(erase-buffer)
(insert "ADS Search Results for "
;; `search-string' is nil when we use advanced search.
(if search-string
(concat "\"" (propertize search-string
'face 'font-lock-string-face) "\"")
"advanced search")
"\n\n")
(insert
(propertize
(concat
"Scroll with SPC and SHIFT-SPC, or search using 's' and 'r'."
"\n\n"
"* To slurp a bibtex entry, type the number of the abstract and hit RET."
"\n\n"
"* To view an abstract, type the number of the abstract and hit 'a'."
"\n\n"
"* To quit and restore the previous window configuration, hit 'q'."
"\n\n\n\n") 'face 'font-lock-comment-face))
(save-excursion
(insert
(mapconcat 'identity
(--map (apply 'bibslurp/print-entry it)
bibslurp-entry-list) ""))
;; Shave off the last newlines
(delete-char -4))
(bibslurp-mode))
(switch-to-buffer buf)
(setq buffer-read-only t)
(set-buffer-modified-p nil)
(delete-other-windows)))
;;;###autoload
(defun bibslurp-query-ads (&optional search-string)
"Ask for a search string and sends the query to NASA ADS.
Press \"C-c C-c\" to turn to the advanced search interface."
(interactive)
(let ((map (make-sparse-keymap)))
(set-keymap-parent map minibuffer-local-map)
;; Bind C-c C-c to abort reading from minibuffer. This throws a `quit'
;; signal that we can catch later.
(define-key map "\C-c\C-c"
(lambda ()
(interactive)
(abort-recursive-edit)))
(condition-case nil
(progn
;; Read the search string from minibuffer, if not provided as
;; argument.
(unless search-string
(setq search-string
(read-from-minibuffer "Search string: " nil map nil
'bibslurp-query-history)))
;; Show search results for the given search string.
(window-configuration-to-register :bibslurp-window)
(bibslurp/search-results (bibslurp/build-ads-url search-string)
search-string))
;; We've received a `quit' signal. If it has been thrown by C-c C-c,
;; start the ADS advanced search, otherwise emit the standard error.
;; XXX: actually `last-input-event' holds only the very last event (C-c,
;; in this case), we must hope the user didn't bind other keys ending in
;; C-c to a `quit' signal, but this isn't the case in the standard
;; configuration.
(quit (if (equal last-input-event ?\C-c)
(bibslurp-query-ads-advanced-search)
(error "Quit"))))))
(defun bibslurp/read-table ()
"Parse the HTML from a search results page.
TODO: describe in more detail. also rethink this."
(goto-char (point-min))
;; search results are printed in a

" end t)
(add-to-list 'data (match-string-no-properties 1) t))
;; search results start with a number. if this is a new
;; search result, store it in the temp variable. otherwise,
;; if temp is non-nil, this is the continuation of a search
;; result. append them and add to the rows list.
(cond
((and (car data) (s-numeric? (car data)))
(setq temp data))
(temp
(add-to-list 'rows (append temp data) t)
(setq temp '())))))
rows))
(defun bibslurp/clean-entry (entry)
"Process the data returned by `bibslurp/read-table' into
something human readable.
Note that this function depends on the *order* of