#!/usr/bin/env python
import os, sys, os.path
import hashlib
import terminal
from optparse import OptionParser
from ConfigParser import SafeConfigParser
from models import Author, Paper, Tag, AuthorNickname, initDatabase, session, asc
db_filename = 'alexandria.db'
term = terminal.TerminalController()
color = {'title': term.GREEN + term.BOLD,
'author': term.YELLOW + term.BOLD,
'label': term.CYAN + term.BOLD,
'path': term.NORMAL,
'hash': term.RED + term.BOLD,
'error': term.RED + term.BOLD,
'normal': term.NORMAL}
default_viewer = '/usr/bin/evince'
def find_database(starting_path = None):
if not starting_path: starting_path = '.'
# Walk up until "alexandria.db" is found
# return (True, path) if found, (False, os.path.join(starting_path, 'alexandria.db')) otherwise
directory = starting_path
while os.path.abspath(directory) != '/':
if os.path.exists(os.path.join(directory, db_filename)):
break
directory = os.path.abspath(os.path.join(directory, '..'))
else:
return (False, os.path.join(starting_path, db_filename))
return (True, os.path.join(directory, db_filename))
def add(args, options):
path = args[0]
if not os.path.exists(path):
print _colorize_string('error', "Path %s does not exist. Cannot add paper." % path)
return
m = hashlib.md5()
fd = open(path, 'r')
m.update(fd.read())
fd.close()
path = os.path.abspath(path)
commonpath = os.path.commonprefix([options.commonpath, path])
path = path[len(commonpath):]
path = path.strip('/')
p = Paper.get_by(path = path) or Paper.get_by(md5 = m.hexdigest())
if p is not None:
print _colorize_string('error', "Paper already exists, use update")
print '--------------------------------'
_show_paper(p)
return
p = Paper(path = path, md5 = m.hexdigest())
_set_options(p, options, required = ['title'])
session.flush()
_show_paper(p)
def update(args, options):
p = Paper.query.filter(Paper.md5.startswith(args[0])).one()
_set_options(p, options)
session.flush()
_show_paper(p)
def view(args, options):
if len(args) < 1: return
p = Paper.query.filter(Paper.md5.startswith(args[0])).one()
if not p: return
if len(args) > 1: viewer = args[1]
else: viewer = default_viewer
os.system('%s %s' % (viewer, os.path.join(options.commonpath, p.path.strip('/'))))
def remove(args, options):
if len(args) < 1: return
p = Paper.query.filter(Paper.md5.startswith(args[0])).one()
if not p: return
print "Removing"
_show_paper(p)
p.delete()
session.flush()
def list(args, options):
papers = Paper.query
# Refactor with _set_options()
for label_with_commas in (options.labels or []):
labels = label_with_commas.split(',')
for label in labels:
label = unicode(label.strip())
label = label.replace('*', '%') # allow for glob style-pattern
papers = papers.filter(Paper.tags.any(Tag.name.like(label)))
for author_with_commas in (options.authors or []):
authors = author_with_commas.split(',')
for author in authors:
author = unicode(author.strip())
an = AuthorNickname.get_by(name = author)
if an: a = an.author.name
else: a = author.replace('*', '%')
papers = papers.filter(Paper.authors.any(Author.name.like(a)))
print
for p in papers.all():
_show_paper(p)
print
def alias(args, options):
if len(args) > 1:
a = Author.get_by_or_init(name = unicode(args[1]))
an = AuthorNickname.get_by_or_init(name = unicode(args[0]))
an.author = a
session.flush()
_show_nicknames()
def labels(args, options):
if len(args) >= 2:
t = Tag.get_by(name = unicode(args[0]))
if t:
t.name = unicode(args[1])
session.flush()
print "Labels:"
for t in Tag.query.order_by(asc(Tag.name)).all():
if len(t.papers) == 0: # clean the database
t.delete()
continue
print ' (%d) %s' % (len(t.papers), t.name)
session.flush()
def ralias(args, options):
if len(args) == 0:
print _colorize_string('error', 'Need alias to remove as an argument.')
an = AuthorNickname.get_by(name = unicode(args[0]))
if an:
an.delete()
session.flush()
_show_nicknames()
def _set_options(p, options, required = []):
title = options.title or ('title' in required) and raw_input("Enter title: ")
if title:
p.title = unicode(title)
for label_with_commas in (options.labels or []):
labels = label_with_commas.split(',')
for label in labels:
label = unicode(label.strip())
if label[0] == '-': # remove label
t = Tag.get_by(name = label[1:])
t.papers.remove(p)
else: # add label
t = Tag.get_by_or_init(name = label)
t.papers.append(p)
for author_with_commas in (options.authors or []):
authors = author_with_commas.split(',')
for author in authors:
author = unicode(author.strip())
an = AuthorNickname.get_by(name = author)
if an: a = an.author
else: a = Author.get_by_or_init(name = author)
a.papers.append(p)
def _show_nicknames():
print "Nicknames:"
for a in Author.query.all():
if len(a.nicknames) > 0:
print ' ' + a.name + ':',
for an in a.nicknames[:-1]:
print an.name + ',',
#print '%s: %s' % (a.nicknames[-1], a.name)
print a.nicknames[-1]
def _sort_authors(authors):
authors.sort() # FIXME: deal with firstname lastname issues
def _show_paper(paper):
print _colorize_string('title', paper.title)
authors = [str(a) for a in paper.authors]
_sort_authors(authors)
for author in authors[:-1]:
print '%s,' % _colorize_string('author', author),
print '%s' % _colorize_string('author', authors[-1])
print 'Labels:',
for tag in paper.tags:
print '+%s' % _colorize_string('label', tag),
print color['normal']
print "Path: %s" % _colorize_string('path', paper.path)
print "Hash: %s" % _colorize_string('hash', paper.md5)
def _colorize_string(clr, str):
return '%s%s%s' % (color[clr], str, color['normal'])
commands = [
(add, 'add a paper to the database'),
(list, 'list papers in the database'),
(alias, 'add or list author nicknames'),
(update, 'update paper by hash'),
(view, 'view paper by hash'),
(remove, 'remove paper by hash'),
(labels, 'rename and/or list labels'),
(ralias, 'remove alias'),
]
if __name__ == "__main__":
usage = '%s COMMAND OPTIONS\n' % sys.argv[0]
usage += 'Commands:\n'
for cmd in commands:
func, description = cmd
usage += ' %-10s - %s\n' % (func.__name__, description)
# Parse config
config = SafeConfigParser()
config.read([os.path.expanduser('~/.alexandria')])
dbpath = os.path.expanduser(config.get('paths', 'dbpath'))
commonpath = os.path.expanduser(config.get('paths', 'common'))
# Parse options
parser = OptionParser(usage = usage)
parser.add_option('-a', '--author', action='append', dest='authors', help='author')
parser.add_option('-t', '--title', dest='title', help='title')
parser.add_option('-l', '--label', action='append', dest='labels', help='label')
parser.add_option('-D', '--database', dest='database', help='directory with the database')
(options, args) = parser.parse_args()
# Find database
found, path = find_database(options.database or dbpath)
initDatabase(path, not found)
# Augment options
options.dbpath = path
options.commonpath = commonpath
# Find and execute the command
if len(args) == 0: sys.exit()
candidates = []
for cmd in commands:
func, description = cmd
if func.__name__.startswith(args[0]):
candidates += [func]
if len(candidates) > 1:
print "Ambiguous choices:",
for c in candidates: print c.__name__,
print
else:
candidates[0](args[1:], options)