362 lines
12 KiB
Python
Executable File
362 lines
12 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import os
|
|
os.environ['DJANGO_SETTINGS_MODULE']='web.settings'
|
|
from web.library.models import *
|
|
from web.library.fileformat import read_actionlist, write_actionlist
|
|
from django.db.models import Q
|
|
from django.db import transaction
|
|
from util import *
|
|
import getopt
|
|
import sys
|
|
import types
|
|
import traceback
|
|
import search
|
|
import placement
|
|
import tempfile
|
|
|
|
file_encoding = 'utf8'
|
|
|
|
def show(ids, commit_format=False, tmp_file=False):
|
|
objects = map(get_object_by_id, ids)
|
|
for i in range(len(ids)):
|
|
if not objects[i]:
|
|
objects[i] = 'No book or person with id %s.\n' % ids[i]
|
|
elif commit_format:
|
|
objects[i] = objects[i].to_dict()
|
|
else:
|
|
objects[i] = objects[i].to_string()
|
|
if commit_format:
|
|
output = write_actionlist(objects)
|
|
else:
|
|
output = '\n'.join(objects)
|
|
if tmp_file:
|
|
filename = write_tmpfile('.'.join(ids), output)
|
|
print filename
|
|
return filename
|
|
else:
|
|
print output.strip()
|
|
|
|
def get_object_by_id(id):
|
|
books = Book.objects.filter(Q(isbn=id)|Q(id__id=id)).all()
|
|
persons = Person.objects.filter(id=id)
|
|
categories = Category.objects.filter(id=id)
|
|
if len(books) + len(persons) + len(categories) > 1:
|
|
print 'Warning: More than one match for id %d.' % id
|
|
print 'This should not happen.'
|
|
if len(books) > 0:
|
|
return books[0]
|
|
if len(persons) > 0:
|
|
return persons[0]
|
|
if len(categories) > 0:
|
|
return categories[0]
|
|
return None
|
|
|
|
def remove_duplicates(list):
|
|
d = {}
|
|
for i in list:
|
|
d[i]=None
|
|
return d.keys()
|
|
|
|
def list_cmd(what):
|
|
if what == 'person':
|
|
print_person_list(Person.objects.all())
|
|
elif what == 'book':
|
|
print_book_list(Book.objects.all())
|
|
elif what == 'category':
|
|
print_category_list(Category.objects.all())
|
|
else:
|
|
print 'I don\' know what \'%s\' is.' % what
|
|
|
|
def print_person_list(lst):
|
|
format = '%-13s %-20s %-70s'
|
|
for person in lst:
|
|
name = cut_str(person.first_name+' '+person.last_name, 20)
|
|
p_id = cut_str(person.id, 10)
|
|
books = cut_str(', '.join(map(lambda x: x.book.title, person.books.all())), 70)
|
|
print format % (p_id, name, books)
|
|
|
|
def print_book_list(lst):
|
|
format = '%-13s %-10s %-40s %-30s'
|
|
for book in lst:
|
|
b_id = book.getid() or ''
|
|
title = cut_str(book.title, 40, '*')
|
|
authors = map(lambda p: p.first_name+' '+p.last_name,
|
|
book.get_authors())
|
|
authors_str = cut_str(', '.join(authors), 30, '*')
|
|
print format % (book.isbn, b_id, title, authors_str)
|
|
|
|
def print_category_list(lst):
|
|
format = '%-20s %s'
|
|
for category in lst:
|
|
print format % (category.id, category.name)
|
|
|
|
def search_person_cmd(search_strings, search_description=False):
|
|
people = search.search_person(search_strings, search_description)
|
|
print_person_list(people)
|
|
|
|
def search_book_cmd(search_strings, search_description=False):
|
|
books = search.search_book(search_strings, search_description)
|
|
print_book_list(books)
|
|
|
|
def commit(filename=None):
|
|
if filename:
|
|
try:
|
|
f = file(filename, 'r')
|
|
text = f.read()
|
|
f.close()
|
|
except IOError, e:
|
|
print 'commit: Error reading file %s: %s' % (filename, e)
|
|
print 'Exiting.'
|
|
sys.exit(1)
|
|
else:
|
|
text = sys.stdin.read()
|
|
|
|
actions = read_actionlist(text)
|
|
commit_actions(actions)
|
|
|
|
@transaction.commit_manually
|
|
def commit_actions(actions):
|
|
# Ensure that creation of new persons is performed first, so that
|
|
# books in the same commit can refer to them:
|
|
def action_cmp(a1, a2):
|
|
if a1['action'] == 'new-person' and a2['action'] != 'new-person':
|
|
return -1
|
|
return cmp(a1, a2)
|
|
actions.sort(action_cmp)
|
|
|
|
try:
|
|
new_persons = {}
|
|
for action in actions:
|
|
obj = perform_action(action, new_persons)
|
|
if isinstance(obj, Person):
|
|
new_persons[obj.id] = obj
|
|
transaction.commit()
|
|
except Exception, e:
|
|
print 'Error when commiting.'
|
|
print e
|
|
traceback.print_exc()
|
|
transaction.rollback()
|
|
|
|
def perform_action(a, new_persons):
|
|
action = a['action']
|
|
if action == 'edit-book' or action == 'new-book':
|
|
print 'Commiting %s action, ISBN: %s' % (action, a['isbn'])
|
|
if action == 'edit-book':
|
|
b = Book.objects.get(isbn=a['isbn'])
|
|
else:
|
|
b = Book(isbn=a['isbn'])
|
|
if 'title' in a: b.title = a['title']
|
|
if 'subtitle' in a: b.subtitle = a['subtitle']
|
|
if 'category' in a: b.category = Category.objects.get(id=a['category'])
|
|
if 'publisher' in a: b.publisher = a['publisher']
|
|
if 'published_year' in a: b.published_year = a['published_year']
|
|
if 'edition' in a: b.edition = a['edition']
|
|
if 'num_pages' in a: b.num_pages = a['num_pages']
|
|
if 'series' in a:
|
|
series_lst = BookSeries.objects.filter(Q(id=a['series']))
|
|
if len(series_lst) > 0:
|
|
b.series = series_lst[0]
|
|
else:
|
|
b.series = None
|
|
if 'description' in a: b.description = a['description']
|
|
if 'persons' in a:
|
|
for bp in b.persons.all():
|
|
if (bp.relation.name not in a['persons']
|
|
or bp.person.id not in a['persons'][bp.relation.name]):
|
|
bp.delete()
|
|
for rel, person_list in a['persons'].items():
|
|
for person_id in person_list:
|
|
if person_id not in map(lambda p: p.id, b.get_persons(rel)):
|
|
if person_id in new_persons.keys():
|
|
person = new_persons[person_id]
|
|
else:
|
|
person = Person.objects.get(id=person_id)
|
|
BookPerson(book=b,
|
|
person=person,
|
|
relation=Relation.objects.get(name=rel)).save()
|
|
if 'references' in a:
|
|
for ref in b.references.all():
|
|
if (ref.reference_type not in a['references']
|
|
or ref.text not in a['references'][ref.reference_type]):
|
|
ref.delete()
|
|
for reftype, text_list in a['references'].items():
|
|
for text in text_list:
|
|
if text not in map(lambda r: r.text, b.references.all()):
|
|
Reference(book=b,
|
|
reference_type=ReferenceType.objects.get(name=reftype),
|
|
text=text).save()
|
|
# TODO pictures
|
|
b.save()
|
|
return b
|
|
if action == 'edit-person' or action == 'new-person':
|
|
print 'Commiting %s action, id: %s' % (action, a['id'])
|
|
if action == 'edit-person':
|
|
p = Person.objects.get(id=a['id'])
|
|
else:
|
|
p = Person(id=a['id'])
|
|
if 'first_name' in a: p.first_name = a['first_name']
|
|
if 'last_name' in a: p.last_name = a['last_name']
|
|
p.save()
|
|
return p
|
|
|
|
def edit_book_or_person(ids):
|
|
filename = show(ids, commit_format=True, tmp_file=True)
|
|
print filename
|
|
run_editor(filename)
|
|
commit(filename)
|
|
|
|
commands = { 'show':
|
|
{ 'args': [('ids', (1,None))],
|
|
'options': ['commit_format', 'tmp_file'],
|
|
'fun': show },
|
|
'list':
|
|
{ 'args': [('what', (1,1))],
|
|
'options': [],
|
|
'fun': list_cmd },
|
|
'search':
|
|
{ 'args': [('search_strings', (1,None))],
|
|
'options': ['search_description'],
|
|
'fun': search_book_cmd },
|
|
'search-person':
|
|
{ 'args': [('search_strings', (1,None))],
|
|
'options': [],
|
|
'fun': search_person_cmd },
|
|
'commit':
|
|
{ 'args': [('filename', (0,1))],
|
|
'options': [],
|
|
'fun': commit },
|
|
'edit':
|
|
{ 'args': [('ids', (1,None))],
|
|
'options': [],
|
|
'fun': edit_book_or_person },
|
|
'map':
|
|
{ 'args': [('shelfname', (0,1)), ('category', (0,1))],
|
|
'options': [],
|
|
'fun': placement.print_shelf_cmd }
|
|
}
|
|
|
|
flags = { 'commit_format':
|
|
{ 'help': 'output data in the format expected by the commit command' },
|
|
'tmp_file':
|
|
{ 'help': 'output data to a new temporary file instead of to stdout' },
|
|
'search_description':
|
|
{ 'help': 'include description field when searching' }
|
|
}
|
|
|
|
general_options = [] # options applicable to all commands
|
|
|
|
class BadCommandLine(Exception):
|
|
def __init__(self, msg):
|
|
Exception.__init__(self, 'Bad command line: ' + msg)
|
|
|
|
def check_command_args(args, command):
|
|
cmd_decl = commands[command]
|
|
min_num_args = sum(map(lambda a: a[1][0], cmd_decl['args']))
|
|
unlimited = any(map(lambda a: a[1][1] == None, cmd_decl['args']))
|
|
if not unlimited:
|
|
max_num_args = sum(map(lambda a: a[1][1], cmd_decl['args']))
|
|
if len(args) < min_num_args:
|
|
raise BadCommandLine('Too few arguments for command %s (expects at least %d, %d given).'
|
|
% (command, min_num_args, len(args)))
|
|
if (not unlimited) and (len(args) > max_num_args):
|
|
raise BadCommandLine('Too many arguments for command %s (expects at most %d, %d given).'
|
|
% (command, max_num_args, len(args)))
|
|
|
|
def check_command_opts(opts, command):
|
|
cmd_decl = commands[command]
|
|
for opt,val in opts:
|
|
if ((opt not in cmd_decl['options']) and
|
|
(opt not in general_options)):
|
|
raise BadCommandLine('Option %s not applicable to command %s.'
|
|
% (opt, command))
|
|
|
|
def assign_command_args(args, command):
|
|
cmd_decl = commands[command]
|
|
d = {}
|
|
i = 0
|
|
for param in cmd_decl['args']:
|
|
pname = param[0]
|
|
pmin = param[1][0]
|
|
pmax = param[1][1]
|
|
if pmax == 1:
|
|
if i < len(args):
|
|
d[pname] = args[i]
|
|
i += 1
|
|
else:
|
|
d[pname] = None
|
|
else:
|
|
d[pname] = []
|
|
j = 0
|
|
while i + j < len(args) and (pmax == None or j < pmax):
|
|
d[pname].append(args[i + j])
|
|
j += 1
|
|
i = i + j
|
|
return d
|
|
|
|
def assign_command_opts(opts, command):
|
|
d = {}
|
|
for option, value in opts:
|
|
if option in flags:
|
|
d[option] = True
|
|
else:
|
|
d[option] = value
|
|
return d
|
|
|
|
def parse_cmdline(args):
|
|
def getopt_option_name(internal_name):
|
|
return internal_name.replace('_', '-')
|
|
def internal_option_name(getopt_ret_name):
|
|
return getopt_ret_name[2:].replace('-', '_')
|
|
|
|
option_names = map(getopt_option_name, flags)
|
|
|
|
options, args = getopt.getopt(args, '', option_names)
|
|
|
|
if len(args) == 0:
|
|
raise BadCommandLine('No command specified.')
|
|
cmd_name = args[0]
|
|
if cmd_name not in commands:
|
|
raise BadCommandLine('Nonexisting command %s.' % cmd_name)
|
|
cmd_decl = commands[cmd_name]
|
|
cmd_args = args[1:]
|
|
cmd_opts = map(lambda (opt,val): (internal_option_name(opt), val),
|
|
options)
|
|
|
|
check_command_args(cmd_args, cmd_name)
|
|
check_command_opts(cmd_opts, cmd_name)
|
|
|
|
return { 'command': cmd_name,
|
|
'args': combine_dicts(assign_command_args(cmd_args, cmd_name),
|
|
assign_command_opts(cmd_opts, cmd_name)) }
|
|
|
|
def invoke_command(command, args):
|
|
cmd_decl = commands[command]
|
|
cmd_decl['fun'](**args)
|
|
|
|
def combine_dicts(*dicts):
|
|
res = {}
|
|
for d in dicts:
|
|
res.update(d)
|
|
return res
|
|
|
|
def run_editor(filename):
|
|
if os.path.exists(filename):
|
|
os.system("%s %s || /usr/bin/env vi %s" %(os.getenv("EDITOR"), filename, filename))
|
|
else:
|
|
exit("Error: %s: File does not exist!" % filename)
|
|
|
|
def write_tmpfile(pfix='', content=''):
|
|
file = tempfile.NamedTemporaryFile(prefix=pfix+'-', dir='/tmp', delete=False)
|
|
file.write(content.encode(file_encoding))
|
|
name = file.name
|
|
file.close()
|
|
return name
|
|
|
|
|
|
cmdline_parsed = parse_cmdline(sys.argv[1:])
|
|
print 'command line parsed to:', cmdline_parsed
|
|
invoke_command(cmdline_parsed['command'],
|
|
cmdline_parsed['args'])
|