This repository has been archived on 2024-07-04. You can view files and clone it, but cannot push or open issues or pull requests.
worblehat-old/python/worblehat.py

309 lines
10 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_book_or_person(ids, commit_format=False, tmp_file=False):
objects = map(get_book_or_person, 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_book_or_person(id):
books = Book.objects.filter(Q(isbn=id)|Q(id__id=id)).all()
persons = Person.objects.filter(id=id)
if len(books) + len(persons) > 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]
def remove_duplicates(list):
d = {}
for i in list:
d[i]=None
return d.keys()
def search_person_cmd(search_strings, search_description=False):
people = search_person(search_strings, search_description)
format = '%-20s %-10s %-70s'
for person in people:
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 % (name,p_id,books)
def search_person(search_strings, search_description=False):
basic_query=Person.objects.select_related('books__book__alt_titles')
for word in search_strings:
basic_query=basic_query.filter(Q(first_name__icontains=word) |
Q(last_name__icontains=word) |
Q(id__icontains=word) |
Q(books__book__isbn__icontains=word) |
Q(books__book__title__icontains=word) |
Q(books__book__alt_titles__alt_title=word) |
Q(books__book__id__id__icontains=word))
return remove_duplicates(basic_query.all())
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:
for action in actions:
perform_action(action)
transaction.commit()
except Exception, e:
print 'Error when commiting.'
print e
traceback.print_exc()
transaction.rollback()
def perform_action(a):
if a['action'] == 'edit-book':
b = Book.objects.get(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.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)):
BookPerson(book=b,
person=Person.objects.get(id=person_id),
relation=Relation.objects.get(name=rel)).save()
if 'references' in a:
for ref in b.references.all():
if 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()
def edit_book_or_person(ids):
filename = show_book_or_person(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_book_or_person },
'search':
{ 'args': [('search_strings', (1,None))],
'options': ['search_description'],
'fun': search.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'])