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:
Øystein Ingmar Skartsæterhagen 2011-10-12 19:26:21 +00:00
parent 1f2c52bb47
commit 9a733ef73a
5 changed files with 130 additions and 56 deletions

3
cli/exc.py Normal file
View File

@ -0,0 +1,3 @@
class WorblehatException(Exception):
def __init__(self, msg):
Exception.__init__(self, msg)

79
cli/file_io.py Normal file
View File

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

View File

@ -1,7 +1,6 @@
import re
import types
file_encoding = 'utf8'
from exc import WorblehatException
# The possible fields for each type of object.
#
@ -54,10 +53,13 @@ action_fields = {
{ 'type': 'category',
'required': ['id'] } }
class CommitFormatSyntaxError(Exception):
class CommitFormatSyntaxError(WorblehatException):
def __init__(self, msg, linenr):
super(CommitFormatSyntaxError, self).__init__(self, 'Syntax error on line %d: %s' %
(linenr, msg))
WorblehatException.__init__(self, 'Syntax error on line %d: %s' % (linenr, msg))
class CommitFormatError(WorblehatException):
def __init__(self, msg):
WorblehatException.__init__(self, msg)
def read_field_value_str(val):
if val.strip() == '':
@ -67,7 +69,10 @@ def read_field_value_str(val):
def read_field_value_int(val):
if val.strip() == '':
return None
return int(val.strip())
try:
return int(val.strip())
except ValueError, TypeError:
raise WorblehatException('%s is not an integer' % val)
def read_field_value_dict(val):
d = {}
@ -216,10 +221,9 @@ def write_action(d):
'd': write_field_value_dict,
'l': write_field_value_list}[ftype]
lines += field + ':' + value_writer(d[field])
return lines.encode(file_encoding)
return lines
def write_actionlist(actions):
encoding_comment = '# -*- coding: %s -*-\n' % file_encoding
return encoding_comment + '\n'.join(map(write_action, actions))
return '\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']}])

View File

@ -1,6 +1,3 @@
import os
import tempfile
db_encoding = 'utf8'
def value_to_db(value):
@ -110,16 +107,3 @@ def combine_dicts(*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, encoding='utf8'):
file = tempfile.NamedTemporaryFile(prefix=pfix+'-', dir='/tmp', delete=False)
file.write(content.encode(encoding))
name = file.name
file.close()
return name

View File

@ -7,6 +7,8 @@ import pgdb
from fileformat import read_actionlist, write_actionlist
from google_interface import google_suggest_book_data
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',
# user='oysteini_pbb',
@ -57,6 +59,8 @@ q_edit_person = \
'UPDATE person ' \
'SET lastname=%(lastname)s, firstname=%(firstname)s ' \
'WHERE id=%(id)s'
q_delete_person = \
'DELETE FROM person WHERE id=%(id)s'
q_new_book = \
'INSERT INTO book ' \
' (isbn, id, title, subtitle, category, publisher, ' \
@ -102,7 +106,7 @@ def get_by_id(connection, id):
for (typ,q) in [('book', q_book),
('person', q_person),
('category', q_cat)]:
c.execute(q, {'id': id})
execute_query(c, q, {'id': id})
if c.rowcount > 0:
d = fetchone_dict(c)
d['type'] = typ
@ -214,15 +218,16 @@ def show(connection, ids, commit_format=False, tmp_file=False):
objects[i] = show_fun(objects[i])
if commit_format:
output = write_actionlist(objects)
output = encoding_comment() + write_actionlist(objects)
else:
output = '\n'.join(objects)
if tmp_file:
filename = write_tmpfile('.'.join(ids), output)
print filename
return filename
with tmpfile('.'.join(ids)):
write_stderr('%s\n' % tmpfile_name())
write(output)
return tmpfile_name()
else:
print output.strip()
write(output)
def list_books(connection):
c = connection.cursor()
@ -230,7 +235,7 @@ def list_books(connection):
#print fetchall_dict(c)
for i in xrange(c.rowcount):
book = fetchone_dict(c)
print('%-13s %-10s %-60s %s' %
write('%-13s %-10s %-60s %s' %
(book['isbn'], str_or_empty(book['id']),
cut_str(book['title'], 60), book['persons']))
@ -239,16 +244,16 @@ def list_persons(connection):
c.execute(q_list_persons)
for i in xrange(c.rowcount):
person = fetchone_dict(c)
print '%-5s %-30s %d books' % (person['id'],
write('%-5s %-30s %d books' % (person['id'],
person['firstname']+' '+person['lastname'],
person['num_books'])
person['num_books']))
def list_categories(connection):
c = connection.cursor()
c.execute(q_list_categories)
for i in xrange(c.rowcount):
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):
funs = { 'book': list_books,
@ -283,7 +288,7 @@ def search_book(connection, search_strings, search_description=False):
', map(lambda s:'%' + s + '%',result_list))
for i in xrange(c.rowcount):
book = fetchone_dict(c)
print('%-13s %-10s %-60s %s' %
write('%-13s %-10s %-60s %s' %
(book['isbn'], str_or_empty(book['id']),
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)
for i in xrange(c.rowcount):
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):
print 'ACTION %s ' % action
debug('ACTION %s ' % action)
c = connection.cursor()
queries = {'new-person': q_new_person,
'edit-person': q_edit_person,
@ -312,9 +317,9 @@ def do_action(connection, action):
action_type = action['action']
execute_query(c, queries[action_type], action)
if action_type in ['new-book', 'edit-book']:
print 'FIXING PERSONS: REMOVING'
debug('FIXING PERSONS: REMOVING')
c.execute(q_remove_bookpersons, {'isbn': action['isbn']})
print 'FIXING PERSONS: ADDING'
debug('FIXING PERSONS: ADDING')
if action['persons']:
for (relation, personlist) in action['persons'].items():
for person in personlist:
@ -335,19 +340,12 @@ def do_action(connection, action):
else:
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):
for action in actions:
try:
do_action(connection, action)
except pgdb.DatabaseError, err:
print>>sys.stderr, 'Error in "%s" action: %s' % (action['action'], err)
print>>sys.stderr, 'Giving up.'
return
raise WorblehatException('commit: Error in "%s" action: %s' % (action['action'], err))
connection.commit()
def commit(connection, filename=None):
@ -357,9 +355,7 @@ def commit(connection, filename=None):
text = f.read()
f.close()
except IOError, e:
print 'commit: Error reading file %s: %s' % (filename, e)
print 'Exiting.'
sys.exit(1)
raise WorblehatException('commit: Error reading file %s: %s' % (filename, e))
else:
text = sys.stdin.read()
@ -368,9 +364,17 @@ def commit(connection, filename=None):
def edit(connection, ids):
filename = show(connection, ids, commit_format=True, tmp_file=True)
print filename
run_editor(filename)
commit(connection, filename)
done = False
while not done:
run_editor(filename)
try:
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):
pass
@ -380,12 +384,12 @@ def suggest_book_data(connection, tmp_file=False):
def register_books(connection):
filename = suggest_book_data(connection, tmp_file=True)
print("Tempfile filename: " + filename)
#print("Tempfile filename: " + filename)
run_editor(filename)
commit(connection, filename)
def give_bananas():
print "Om nom nom... Thanks!"
write("Om nom nom... Thanks!")
commands = { 'show':
{ 'args': [('ids', (1,None))],