Improved handling of output to terminal and files.
New module file_io which takes care of this. All output should go through file_io.write, which encodes it using the appropriate character encoding. For output to a temporary file, use "with file_io.tmpfile('name')". Moved WorblehatException to new module exc, so all modules can access it without importing worblehat.
This commit is contained in:
parent
1f2c52bb47
commit
9a733ef73a
|
@ -0,0 +1,3 @@
|
||||||
|
class WorblehatException(Exception):
|
||||||
|
def __init__(self, msg):
|
||||||
|
Exception.__init__(self, msg)
|
|
@ -0,0 +1,79 @@
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import locale
|
||||||
|
import sys
|
||||||
|
from exc import WorblehatException
|
||||||
|
|
||||||
|
stdout_encoding = locale.getpreferredencoding()
|
||||||
|
file_encoding = 'utf-8'
|
||||||
|
|
||||||
|
output = sys.stdout
|
||||||
|
|
||||||
|
# def write_tmpfile(pfix, content, encoding='utf8'):
|
||||||
|
# file = tempfile.NamedTemporaryFile(prefix=pfix+'-', dir='/tmp', delete=False)
|
||||||
|
# file.write(content.encode(encoding))
|
||||||
|
# name = file.name
|
||||||
|
# file.close()
|
||||||
|
# return name
|
||||||
|
|
||||||
|
def open_tmpfile(prefix):
|
||||||
|
global output
|
||||||
|
if output != sys.stdout:
|
||||||
|
raise WorblehatException('open_tmpfile: Already writing to a file')
|
||||||
|
tmpfile = tempfile.NamedTemporaryFile(prefix='worblehat-%s-' % prefix,
|
||||||
|
dir='/tmp',
|
||||||
|
delete=False)
|
||||||
|
output = tmpfile
|
||||||
|
return tmpfile.name
|
||||||
|
|
||||||
|
def close_tmpfile():
|
||||||
|
global output
|
||||||
|
if output == sys.stdout:
|
||||||
|
raise WorblehatException('close_tmpfile: No file open')
|
||||||
|
output.close()
|
||||||
|
output = sys.stdout
|
||||||
|
|
||||||
|
def output_encoding():
|
||||||
|
if output == sys.stdout:
|
||||||
|
return stdout_encoding
|
||||||
|
else:
|
||||||
|
return file_encoding
|
||||||
|
|
||||||
|
def encoding_comment():
|
||||||
|
return '# -*- coding: %s -*-\n' % file_encoding
|
||||||
|
|
||||||
|
def write(data):
|
||||||
|
if type(data) == unicode:
|
||||||
|
data = data.encode(output_encoding())
|
||||||
|
output.write(data)
|
||||||
|
|
||||||
|
def write_stderr(data):
|
||||||
|
if type(data) == unicode:
|
||||||
|
data = data.encode(output_encoding())
|
||||||
|
sys.stderr.write(data)
|
||||||
|
|
||||||
|
debugging = True
|
||||||
|
def debug(msg):
|
||||||
|
if debugging:
|
||||||
|
write_stderr('DEBUG: %s\n' % msg)
|
||||||
|
|
||||||
|
def tmpfile_name():
|
||||||
|
if output == sys.stdout:
|
||||||
|
raise WorblehatException('tmpfile_name: No file open')
|
||||||
|
return output.name
|
||||||
|
|
||||||
|
class tmpfile:
|
||||||
|
def __init__(self, prefix):
|
||||||
|
self.prefix = prefix
|
||||||
|
def __enter__(self):
|
||||||
|
open_tmpfile(self.prefix)
|
||||||
|
def __exit__(self, exc_type, exc, traceback):
|
||||||
|
close_tmpfile()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import re
|
import re
|
||||||
import types
|
import types
|
||||||
|
from exc import WorblehatException
|
||||||
file_encoding = 'utf8'
|
|
||||||
|
|
||||||
# The possible fields for each type of object.
|
# The possible fields for each type of object.
|
||||||
#
|
#
|
||||||
|
@ -54,10 +53,13 @@ action_fields = {
|
||||||
{ 'type': 'category',
|
{ 'type': 'category',
|
||||||
'required': ['id'] } }
|
'required': ['id'] } }
|
||||||
|
|
||||||
class CommitFormatSyntaxError(Exception):
|
class CommitFormatSyntaxError(WorblehatException):
|
||||||
def __init__(self, msg, linenr):
|
def __init__(self, msg, linenr):
|
||||||
super(CommitFormatSyntaxError, self).__init__(self, 'Syntax error on line %d: %s' %
|
WorblehatException.__init__(self, 'Syntax error on line %d: %s' % (linenr, msg))
|
||||||
(linenr, msg))
|
|
||||||
|
class CommitFormatError(WorblehatException):
|
||||||
|
def __init__(self, msg):
|
||||||
|
WorblehatException.__init__(self, msg)
|
||||||
|
|
||||||
def read_field_value_str(val):
|
def read_field_value_str(val):
|
||||||
if val.strip() == '':
|
if val.strip() == '':
|
||||||
|
@ -67,7 +69,10 @@ def read_field_value_str(val):
|
||||||
def read_field_value_int(val):
|
def read_field_value_int(val):
|
||||||
if val.strip() == '':
|
if val.strip() == '':
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
return int(val.strip())
|
return int(val.strip())
|
||||||
|
except ValueError, TypeError:
|
||||||
|
raise WorblehatException('%s is not an integer' % val)
|
||||||
|
|
||||||
def read_field_value_dict(val):
|
def read_field_value_dict(val):
|
||||||
d = {}
|
d = {}
|
||||||
|
@ -216,10 +221,9 @@ def write_action(d):
|
||||||
'd': write_field_value_dict,
|
'd': write_field_value_dict,
|
||||||
'l': write_field_value_list}[ftype]
|
'l': write_field_value_list}[ftype]
|
||||||
lines += field + ':' + value_writer(d[field])
|
lines += field + ':' + value_writer(d[field])
|
||||||
return lines.encode(file_encoding)
|
return lines
|
||||||
|
|
||||||
def write_actionlist(actions):
|
def write_actionlist(actions):
|
||||||
encoding_comment = '# -*- coding: %s -*-\n' % file_encoding
|
return '\n'.join(map(write_action, actions))
|
||||||
return encoding_comment + '\n'.join(map(write_action, actions))
|
|
||||||
|
|
||||||
# test: print write_actionlist([{'comment':'Foo!\nBar!','action':'new-book','isbn':'434545'},{'action':'edit-book','isbn':'654654745','persons':{'author':['ab','foo'],'illustrator':['moo']}},'This\nis\na\ncomment.',{'action':'edit-category','id':'matematikk','name':'Matematikk','placement':['T10','T11']}])
|
# test: print write_actionlist([{'comment':'Foo!\nBar!','action':'new-book','isbn':'434545'},{'action':'edit-book','isbn':'654654745','persons':{'author':['ab','foo'],'illustrator':['moo']}},'This\nis\na\ncomment.',{'action':'edit-category','id':'matematikk','name':'Matematikk','placement':['T10','T11']}])
|
||||||
|
|
16
cli/util.py
16
cli/util.py
|
@ -1,6 +1,3 @@
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
db_encoding = 'utf8'
|
db_encoding = 'utf8'
|
||||||
|
|
||||||
def value_to_db(value):
|
def value_to_db(value):
|
||||||
|
@ -110,16 +107,3 @@ def combine_dicts(*dicts):
|
||||||
res.update(d)
|
res.update(d)
|
||||||
return res
|
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, encoding='utf8'):
|
|
||||||
file = tempfile.NamedTemporaryFile(prefix=pfix+'-', dir='/tmp', delete=False)
|
|
||||||
file.write(content.encode(encoding))
|
|
||||||
name = file.name
|
|
||||||
file.close()
|
|
||||||
return name
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ import pgdb
|
||||||
from fileformat import read_actionlist, write_actionlist
|
from fileformat import read_actionlist, write_actionlist
|
||||||
from google_interface import google_suggest_book_data
|
from google_interface import google_suggest_book_data
|
||||||
from util import *
|
from util import *
|
||||||
|
from exc import WorblehatException
|
||||||
|
from file_io import tmpfile, tmpfile_name, write, write_stderr, debug, run_editor, encoding_comment
|
||||||
|
|
||||||
# connection = pgdb.connect(database='oysteini_pbb2',
|
# connection = pgdb.connect(database='oysteini_pbb2',
|
||||||
# user='oysteini_pbb',
|
# user='oysteini_pbb',
|
||||||
|
@ -57,6 +59,8 @@ q_edit_person = \
|
||||||
'UPDATE person ' \
|
'UPDATE person ' \
|
||||||
'SET lastname=%(lastname)s, firstname=%(firstname)s ' \
|
'SET lastname=%(lastname)s, firstname=%(firstname)s ' \
|
||||||
'WHERE id=%(id)s'
|
'WHERE id=%(id)s'
|
||||||
|
q_delete_person = \
|
||||||
|
'DELETE FROM person WHERE id=%(id)s'
|
||||||
q_new_book = \
|
q_new_book = \
|
||||||
'INSERT INTO book ' \
|
'INSERT INTO book ' \
|
||||||
' (isbn, id, title, subtitle, category, publisher, ' \
|
' (isbn, id, title, subtitle, category, publisher, ' \
|
||||||
|
@ -102,7 +106,7 @@ def get_by_id(connection, id):
|
||||||
for (typ,q) in [('book', q_book),
|
for (typ,q) in [('book', q_book),
|
||||||
('person', q_person),
|
('person', q_person),
|
||||||
('category', q_cat)]:
|
('category', q_cat)]:
|
||||||
c.execute(q, {'id': id})
|
execute_query(c, q, {'id': id})
|
||||||
if c.rowcount > 0:
|
if c.rowcount > 0:
|
||||||
d = fetchone_dict(c)
|
d = fetchone_dict(c)
|
||||||
d['type'] = typ
|
d['type'] = typ
|
||||||
|
@ -214,15 +218,16 @@ def show(connection, ids, commit_format=False, tmp_file=False):
|
||||||
objects[i] = show_fun(objects[i])
|
objects[i] = show_fun(objects[i])
|
||||||
|
|
||||||
if commit_format:
|
if commit_format:
|
||||||
output = write_actionlist(objects)
|
output = encoding_comment() + write_actionlist(objects)
|
||||||
else:
|
else:
|
||||||
output = '\n'.join(objects)
|
output = '\n'.join(objects)
|
||||||
if tmp_file:
|
if tmp_file:
|
||||||
filename = write_tmpfile('.'.join(ids), output)
|
with tmpfile('.'.join(ids)):
|
||||||
print filename
|
write_stderr('%s\n' % tmpfile_name())
|
||||||
return filename
|
write(output)
|
||||||
|
return tmpfile_name()
|
||||||
else:
|
else:
|
||||||
print output.strip()
|
write(output)
|
||||||
|
|
||||||
def list_books(connection):
|
def list_books(connection):
|
||||||
c = connection.cursor()
|
c = connection.cursor()
|
||||||
|
@ -230,7 +235,7 @@ def list_books(connection):
|
||||||
#print fetchall_dict(c)
|
#print fetchall_dict(c)
|
||||||
for i in xrange(c.rowcount):
|
for i in xrange(c.rowcount):
|
||||||
book = fetchone_dict(c)
|
book = fetchone_dict(c)
|
||||||
print('%-13s %-10s %-60s %s' %
|
write('%-13s %-10s %-60s %s' %
|
||||||
(book['isbn'], str_or_empty(book['id']),
|
(book['isbn'], str_or_empty(book['id']),
|
||||||
cut_str(book['title'], 60), book['persons']))
|
cut_str(book['title'], 60), book['persons']))
|
||||||
|
|
||||||
|
@ -239,16 +244,16 @@ def list_persons(connection):
|
||||||
c.execute(q_list_persons)
|
c.execute(q_list_persons)
|
||||||
for i in xrange(c.rowcount):
|
for i in xrange(c.rowcount):
|
||||||
person = fetchone_dict(c)
|
person = fetchone_dict(c)
|
||||||
print '%-5s %-30s %d books' % (person['id'],
|
write('%-5s %-30s %d books' % (person['id'],
|
||||||
person['firstname']+' '+person['lastname'],
|
person['firstname']+' '+person['lastname'],
|
||||||
person['num_books'])
|
person['num_books']))
|
||||||
|
|
||||||
def list_categories(connection):
|
def list_categories(connection):
|
||||||
c = connection.cursor()
|
c = connection.cursor()
|
||||||
c.execute(q_list_categories)
|
c.execute(q_list_categories)
|
||||||
for i in xrange(c.rowcount):
|
for i in xrange(c.rowcount):
|
||||||
cat = fetchone_dict(c)
|
cat = fetchone_dict(c)
|
||||||
print '%-15s %-30s %d books' % (cat['id'], cat['name'], cat['num_books'])
|
write('%-15s %-30s %d books' % (cat['id'], cat['name'], cat['num_books']))
|
||||||
|
|
||||||
def list_cmd(connection, what):
|
def list_cmd(connection, what):
|
||||||
funs = { 'book': list_books,
|
funs = { 'book': list_books,
|
||||||
|
@ -283,7 +288,7 @@ def search_book(connection, search_strings, search_description=False):
|
||||||
', map(lambda s:'%' + s + '%',result_list))
|
', map(lambda s:'%' + s + '%',result_list))
|
||||||
for i in xrange(c.rowcount):
|
for i in xrange(c.rowcount):
|
||||||
book = fetchone_dict(c)
|
book = fetchone_dict(c)
|
||||||
print('%-13s %-10s %-60s %s' %
|
write('%-13s %-10s %-60s %s' %
|
||||||
(book['isbn'], str_or_empty(book['id']),
|
(book['isbn'], str_or_empty(book['id']),
|
||||||
cut_str(book['title'], 60), book['persons']))
|
cut_str(book['title'], 60), book['persons']))
|
||||||
|
|
||||||
|
@ -298,10 +303,10 @@ def search_person(connection, search_strings):
|
||||||
WHERE person.lastname ILIKE %s or person.firstname ILIKE %s OR person.id ILIKE %s', result_strings)
|
WHERE person.lastname ILIKE %s or person.firstname ILIKE %s OR person.id ILIKE %s', result_strings)
|
||||||
for i in xrange(c.rowcount):
|
for i in xrange(c.rowcount):
|
||||||
person = fetchone_dict(c)
|
person = fetchone_dict(c)
|
||||||
print person['lastname'], ', ', person['firstname'], '\t', person['book']
|
write(person['lastname'], ', ', person['firstname'], '\t', person['book'])
|
||||||
|
|
||||||
def do_action(connection, action):
|
def do_action(connection, action):
|
||||||
print 'ACTION %s ' % action
|
debug('ACTION %s ' % action)
|
||||||
c = connection.cursor()
|
c = connection.cursor()
|
||||||
queries = {'new-person': q_new_person,
|
queries = {'new-person': q_new_person,
|
||||||
'edit-person': q_edit_person,
|
'edit-person': q_edit_person,
|
||||||
|
@ -312,9 +317,9 @@ def do_action(connection, action):
|
||||||
action_type = action['action']
|
action_type = action['action']
|
||||||
execute_query(c, queries[action_type], action)
|
execute_query(c, queries[action_type], action)
|
||||||
if action_type in ['new-book', 'edit-book']:
|
if action_type in ['new-book', 'edit-book']:
|
||||||
print 'FIXING PERSONS: REMOVING'
|
debug('FIXING PERSONS: REMOVING')
|
||||||
c.execute(q_remove_bookpersons, {'isbn': action['isbn']})
|
c.execute(q_remove_bookpersons, {'isbn': action['isbn']})
|
||||||
print 'FIXING PERSONS: ADDING'
|
debug('FIXING PERSONS: ADDING')
|
||||||
if action['persons']:
|
if action['persons']:
|
||||||
for (relation, personlist) in action['persons'].items():
|
for (relation, personlist) in action['persons'].items():
|
||||||
for person in personlist:
|
for person in personlist:
|
||||||
|
@ -335,19 +340,12 @@ def do_action(connection, action):
|
||||||
else:
|
else:
|
||||||
raise WorblehatException('%s is not in the defined references, please use a more general one' % reftype)
|
raise WorblehatException('%s is not in the defined references, please use a more general one' % reftype)
|
||||||
|
|
||||||
class WorblehatException(Exception):
|
|
||||||
def __init__(self, msg):
|
|
||||||
Exception.__init__(self, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def commit_actions(connection, actions):
|
def commit_actions(connection, actions):
|
||||||
for action in actions:
|
for action in actions:
|
||||||
try:
|
try:
|
||||||
do_action(connection, action)
|
do_action(connection, action)
|
||||||
except pgdb.DatabaseError, err:
|
except pgdb.DatabaseError, err:
|
||||||
print>>sys.stderr, 'Error in "%s" action: %s' % (action['action'], err)
|
raise WorblehatException('commit: Error in "%s" action: %s' % (action['action'], err))
|
||||||
print>>sys.stderr, 'Giving up.'
|
|
||||||
return
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
|
||||||
def commit(connection, filename=None):
|
def commit(connection, filename=None):
|
||||||
|
@ -357,9 +355,7 @@ def commit(connection, filename=None):
|
||||||
text = f.read()
|
text = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
print 'commit: Error reading file %s: %s' % (filename, e)
|
raise WorblehatException('commit: Error reading file %s: %s' % (filename, e))
|
||||||
print 'Exiting.'
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
else:
|
||||||
text = sys.stdin.read()
|
text = sys.stdin.read()
|
||||||
|
|
||||||
|
@ -368,9 +364,17 @@ def commit(connection, filename=None):
|
||||||
|
|
||||||
def edit(connection, ids):
|
def edit(connection, ids):
|
||||||
filename = show(connection, ids, commit_format=True, tmp_file=True)
|
filename = show(connection, ids, commit_format=True, tmp_file=True)
|
||||||
print filename
|
done = False
|
||||||
|
while not done:
|
||||||
run_editor(filename)
|
run_editor(filename)
|
||||||
|
try:
|
||||||
commit(connection, filename)
|
commit(connection, filename)
|
||||||
|
done = True
|
||||||
|
except WorblehatException, exc:
|
||||||
|
write_stderr('%s\n' % exc)
|
||||||
|
answer = raw_input('Retry? [Y/n] ')
|
||||||
|
if answer == 'n':
|
||||||
|
done = True
|
||||||
|
|
||||||
def map_cmd(connection, shelfname=None, category=None):
|
def map_cmd(connection, shelfname=None, category=None):
|
||||||
pass
|
pass
|
||||||
|
@ -380,12 +384,12 @@ def suggest_book_data(connection, tmp_file=False):
|
||||||
|
|
||||||
def register_books(connection):
|
def register_books(connection):
|
||||||
filename = suggest_book_data(connection, tmp_file=True)
|
filename = suggest_book_data(connection, tmp_file=True)
|
||||||
print("Tempfile filename: " + filename)
|
#print("Tempfile filename: " + filename)
|
||||||
run_editor(filename)
|
run_editor(filename)
|
||||||
commit(connection, filename)
|
commit(connection, filename)
|
||||||
|
|
||||||
def give_bananas():
|
def give_bananas():
|
||||||
print "Om nom nom... Thanks!"
|
write("Om nom nom... Thanks!")
|
||||||
|
|
||||||
commands = { 'show':
|
commands = { 'show':
|
||||||
{ 'args': [('ids', (1,None))],
|
{ 'args': [('ids', (1,None))],
|
||||||
|
|
Reference in New Issue