From 9a733ef73a8df5ad85fdee41b27f8cc25b16b39c Mon Sep 17 00:00:00 2001 From: oysteini Date: Wed, 12 Oct 2011 19:26:21 +0000 Subject: [PATCH] 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. --- cli/exc.py | 3 ++ cli/file_io.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++ cli/fileformat.py | 22 +++++++------ cli/util.py | 16 ---------- cli/worblehat.py | 66 ++++++++++++++++++++------------------- 5 files changed, 130 insertions(+), 56 deletions(-) create mode 100644 cli/exc.py create mode 100644 cli/file_io.py diff --git a/cli/exc.py b/cli/exc.py new file mode 100644 index 0000000..f9e748b --- /dev/null +++ b/cli/exc.py @@ -0,0 +1,3 @@ +class WorblehatException(Exception): + def __init__(self, msg): + Exception.__init__(self, msg) diff --git a/cli/file_io.py b/cli/file_io.py new file mode 100644 index 0000000..5b186d6 --- /dev/null +++ b/cli/file_io.py @@ -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) + diff --git a/cli/fileformat.py b/cli/fileformat.py index 7c09060..50f8538 100644 --- a/cli/fileformat.py +++ b/cli/fileformat.py @@ -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']}]) diff --git a/cli/util.py b/cli/util.py index eff1b63..ebe4089 100644 --- a/cli/util.py +++ b/cli/util.py @@ -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 diff --git a/cli/worblehat.py b/cli/worblehat.py index e8306c6..1285a14 100755 --- a/cli/worblehat.py +++ b/cli/worblehat.py @@ -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))],