alexandria.py
author Dmitriy Morozov <morozov@cs.duke.edu>
Sat, 17 May 2008 04:35:06 -0400
changeset 3 1626ee683a86
parent 2 b8013798cbfc
child 4 2a543aeac83d
permissions -rwxr-xr-x
Nickname list in alias batched together

#!/usr/bin/env python

import os, sys, os.path
import hashlib
import terminal
from optparse import OptionParser
from models import Author, Paper, Tag, AuthorNickname, initDatabase, session

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)
    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, p.path))

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())
            papers = papers.filter(Paper.tags.any(name = 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
            else:  a = Author.get_by_or_init(name = author)
            papers = papers.filter(Paper.authors.any(name = unicode(a))) 

    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()

    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 _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 _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'])

if __name__ == "__main__":
    usage =  '%s COMMAND OPTIONS\n' % sys.argv[0]
    usage += 'Commands:\n'
    usage += '  add        - add a paper to the database\n'
    usage += '  list       - list papers in the database\n'
    usage += '  alias      - add or list author nicknames\n'
    usage += '  update     - update paper by hash\n'
    usage += '  view       - view paper by hash\n'
    usage += '  remove     - remove paper by hash'
    
    # 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('-s', '--hash', dest='hash', help='hash (only for list)')
    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)
    initDatabase(path, not found)
    
    if len(args) == 0: sys.exit()
    if 'add'.startswith(args[0]):
        add(args[1:], options)
    elif 'list'.startswith(args[0]):
        list(args[1:], options)
    elif 'alias'.startswith(args[0]):
        alias(args[1:], options)
    elif 'update'.startswith(args[0]):
        update(args[1:], options)
    elif 'view'.startswith(args[0]):
        view(args[1:], options)
    elif 'remove'.startswith(args[0]):
        remove(args[1:], options)