Skip to content

Instantly share code, notes, and snippets.

@alexklapheke
Last active February 6, 2017 01:28
Show Gist options
  • Save alexklapheke/390ca44559715f93cdf0598531b724fc to your computer and use it in GitHub Desktop.
Save alexklapheke/390ca44559715f93cdf0598531b724fc to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# Copyright (C) 2016 Alexander Klapheke
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Simple academic paper queue, inspired by paperq <paperq.invergo.net>
# but with tab completion, and pandoc-citeproc as a more robust bibtex
# parser. Expects papers with the same filenames as their bibtex keys.
# Requires pandoc v1.16. Released under the MIT License.
#
# TODO: would be much faster if --list could parse all bibtex keys at once
# TODO: if a key is a prefix of an earlier key, you can't access it---try to match exact first?
# TODO: nicer errors if citation not found (esp. in bibshort() )
# TODO: replace biblong()/bibshort() voodoo with actual parser
# Options:
bibfile=$HOME/texmf/bibtex/bib/references.bib
paperdir=$HOME/Documents/Papers # no trailing slash
queue=$HOME/.config/qlist
default_action=list # remove, view, bib, list, zip, count, edit
view_program=xdg-open # program used to view PDFs
[[ -t 1 ]] && colors=1 # color output if running interactively
[[ -f "$queue" ]] || touch "$queue"
# color messages
message () { if [[ $colors ]]; then echo -e "\e[1;30m${*}\e[0m" >&2; else echo "$*" >&2; fi }
error () { if [[ $colors ]]; then echo -e "\e[1;31m${*}\e[0m" >&2; else echo "$*" >&2; fi }
# color output
gray () { if [[ $colors ]]; then sed $'s/.\+/\e[1;30m\\0\e[0m/' ; else sed -e ''; fi }
white () { if [[ $colors ]]; then sed $'s/.\+/\e[1;37m\\0\e[0m/' ; else sed -e ''; fi }
plain () { if [[ $colors ]]; then sed $'s/.\+/\e[0;37m\\0\e[0m/' ; else sed -e ''; fi }
# check to make sure all external commands can be called
executables=("pandoc" "pandoc-citeproc" "zip" "$view_program")
for ((i=0; i < ${#executables[@]}; i++)); do
if [[ ! $(command -v ${executables[$i]}) ]]; then
error "Executable not found: ${executables[$i]}"; exit
fi
done
# format citations
biblong () {
echo "@$1" | pandoc --bibliography="$bibfile" -t plain --columns="$(tput cols)" | tail -n +3 | head -n -2;
}
bibshort () {
echo "@$1" | pandoc --bibliography="$bibfile" -t plain --wrap=none | # format citation
white | # color author+date
sed '2,$s/.*\(“[^”]\+”\).*/\1/' | # extract title
tr '\n' ' ' | # join lines
sed "s/\(.\{$(($(tput cols)-(number_width-number_space)-6))\}\).*/\1.../"; # truncate
}
get_paper_id () {
local id=${1:-1}
if [[ $id =~ ^[[:digit:]]+$ ]]; then
if [[ $id -le 0 || $id -gt $(queuelength) ]]; then
error "Invalid index."
exit 1
fi
id=$(sed -n "${id}p" "$queue")
else
if ! grep -q "$id" "$queue"; then
error "Invalid index."
exit 1
fi
id=$(sed -n "/${id}/{p;q}" "$queue")
fi
echo "$id"
}
queuelength () {
wc -l "$queue" | cut -f 1 -d ' '
}
iterate () {
if [[ $(queuelength) -eq 0 ]]; then
message "Queue is empty! For help, run: $(basename "$0") --help"
return 1
fi
local num=$(echo "$1" | bc)
while read -r line; do
if [[ "$line" = "" ]]; then continue; fi
if [[ $num -ne 0 && $i -gt $num ]]; then break; fi
echo "$line"
i=$((i+1))
done < "$queue"
}
addpaper () {
local id=$1
filename="$paperdir/$id.pdf"
if [[ ! -r "$filename" ]]; then
error "Could not read file $filename"
return 1
fi
echo "$id" >> "$queue"
message "Added paper:"
biblong "$id"
}
removepaper () {
local id=$(get_paper_id "$1")
if [[ $id ]]; then
sed -i "/${id}/{d;q}" "$queue"
message "Removed paper:"
biblong "$id"
fi
}
viewpaper () {
local id=$(get_paper_id "$1")
if [[ $id ]]; then
"$view_program" "$paperdir/$id.pdf" &> /dev/null
fi
}
listpapers () {
local i=1
local num=$1
# Width of list numbers (= log_10 of number of list items to show)
number_width=$(echo "$(echo "l(${num:-$(queuelength)})/l(10)" | bc -l)/1" | bc)
# Width of space after numbers (bash counts the color codes as taking up width)
number_space=$(if [[ $colors ]]; then echo 12; else echo 1; fi)
for id in $(iterate "${num:=0}"); do
echo -e "$(printf "%-$((number_width+number_space))b" "$(echo -n $i | gray)") $(bibshort "$id")"
i=$((i+1))
done
}
zippapers () {
local i=1
local num=$1
temp=$(mktemp -d)
for id in $(iterate "${num:=0}"); do
ln -T "${paperdir}/${id}.pdf" "${temp}/$(printf '%02d\n' "$i")-${id}.pdf"
i=$((i+1))
done
zip -j ./q.zip "$temp"/*
[[ -d "$temp" ]] && rm -rf "$temp"
}
if [[ $# -eq 0 ]]; then set "$1" "--$default_action"; fi
while [[ $# -gt 0 ]]; do
opt="$1"; shift; if [[ ! "$1" =~ ^- ]]; then arg="$1"; shift; fi
case "$opt" in
'-a'|'--add' ) addpaper "${arg:?Please specify a filename}" ;;
'-r'|'--remove') removepaper "$arg" ;;
'-v'|'--view' ) viewpaper "$arg" ;;
'-b'|'--bib' ) biblong "$(get_paper_id "$arg")" ;;
'-l'|'--list' ) listpapers "$arg" ;;
'-z'|'--zip' ) zippapers "$arg" ;;
'-c'|'--count' ) queuelength | gray ;;
'-e'|'--edit' ) "$EDITOR" "$queue" ;;
'-h'|'--help' )
echo "Reading queue"
echo "Usage: $(basename "$0") [OPTIONS]"
echo
echo " -h, --help Print this message"
echo
echo " -a, --add ID Add (enqueue) file with BibTeX key ID to end of queue"
echo " -r, --remove [NUM] Remove (dequeue) file from queue (default: first)"
echo " -v, --view [NUM] View file from queue (default: first)"
echo " -b, --bib [NUM] View bibliography entry for file (default: first)"
echo " -l, --list [NUM] List first NUM files (default: all)"
echo " -z, --zip [NUM] Create archive of first NUM files (default: all)"
echo " -c, --count Count files in queue"
echo " -e, --edit Manually edit queue"
echo
echo "Options can be used in sequence. With no options, default action is ${default_action}."
echo "A bare argument is interpreted as a file to be viewed."
echo "-a completes a file base name with the extension .pdf."
echo "-v, -b, -r also accept parts of filenames and select the first matching file."
echo
exit 0 ;;
-*) error "Inalid option: $opt" ;;
[^-]*) viewpaper "$opt" ;;
esac
done
# Completion: Place the following completion file in /etc/bash_completion.d/:
_papers () {
COMPREPLY=()
local cur=${COMP_WORDS[COMP_CWORD]}
local prev=${COMP_WORDS[COMP_CWORD-1]}
local paperdir=$HOME/Documents/Papers # no trailing slash
local queue=$HOME/.config/qlist
if [[ "$cur" = -* ]]; then
COMPREPLY=( $( compgen -W "-h --help -a --add -r --remove -v --view -b --bib -l --list -z --zip -c --count -e --edit" -- $cur ) )
else
case "$prev" in
-a|--add) COMPREPLY=( $( compgen -W "$(ls "$paperdir" | xargs basename -s '.pdf')" -- $cur ) ) ;;
-r|--remove|-v|--view|-b|--bib|q) COMPREPLY=( $( compgen -W "$(cat "$queue")" -- $cur ) ) ;;
esac
fi
}
complete -F _papers q
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment