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
3
cli/exc.py
Normal file
3
cli/exc.py
Normal file
@ -0,0 +1,3 @@
|
||||
class WorblehatException(Exception):
|
||||
def __init__(self, msg):
|
||||
Exception.__init__(self, msg)
|
79
cli/file_io.py
Normal file
79
cli/file_io.py
Normal 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)
|
||||
|
@ -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']}])
|
||||
|
16
cli/util.py
16
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
|
||||
|
@ -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))],
|
||||
|
Reference in New Issue
Block a user