oysteini
c614e4b06a
* Handles comments (lines starting with '#'). * Remembers line numbers, and uses them in error messages.
219 lines
6.8 KiB
Python
219 lines
6.8 KiB
Python
import re
|
|
import types
|
|
|
|
# The possible fields for each type of object.
|
|
#
|
|
# Each field is a tuple (fieldname, fieldtype). Fieldtype is either
|
|
# 's' (string), 'd' (dictionary, where the values are lists of
|
|
# strings), 'l' (list of strings).
|
|
fields = {
|
|
'book':
|
|
[('isbn', 's'), ('id', 's'), ('title', 's'), ('category', 's'),
|
|
('subtitle', 's'), ('persons', 'd'), ('publisher', 's'),
|
|
('published_year', 'i'), ('edition', 'i'), ('pages', 'i'),
|
|
('series', 's'), ('description', 's'), # TODO picture, thumbnail
|
|
('references', 'd')],
|
|
'person':
|
|
[('id', 's'), ('firstname', 's'), ('lastname', 's')],
|
|
'category':
|
|
[('id', 's'), ('name', 's'), ('placement', 'l')] }
|
|
|
|
# Fields associated with each action.
|
|
#
|
|
# The 'type' is a key in the fields dictionary, indicating the set of
|
|
# fields which can be used with the action. The 'required' list is a
|
|
# list of fields which must be present for the action to be accepted.
|
|
action_fields = {
|
|
'new-book':
|
|
{ 'type': 'book',
|
|
'required': ['isbn', 'title', 'category'] },
|
|
'edit-book':
|
|
{ 'type': 'book',
|
|
'required': ['isbn'] },
|
|
'delete-book':
|
|
{ 'type': 'book',
|
|
'required': ['isbn'] },
|
|
'new-person':
|
|
{ 'type': 'person',
|
|
'required': ['id', 'firstname', 'lastname'] },
|
|
'edit-person':
|
|
{ 'type': 'person',
|
|
'required': ['id'] },
|
|
'delete-person':
|
|
{ 'type': 'person',
|
|
'required': ['id'] },
|
|
'new-category':
|
|
{ 'type': 'category',
|
|
'required': ['id', 'name'] },
|
|
'edit-category':
|
|
{ 'type': 'category',
|
|
'required': ['id'] },
|
|
'delete-category':
|
|
{ 'type': 'category',
|
|
'required': ['id'] } }
|
|
|
|
class CommitFormatSyntaxError(Exception):
|
|
def __init__(self, msg, linenr):
|
|
super(CommitFormatSyntaxError, self).__init__(self, 'Syntax error on line %d: %s' %
|
|
(linenr, msg))
|
|
|
|
def read_field_value_str(val):
|
|
if val.strip() == '':
|
|
return None
|
|
return '\n'.join(map(lambda x: x.strip(), val.split('\n'))).strip()
|
|
|
|
def read_field_value_int(val):
|
|
if val.strip() == '':
|
|
return None
|
|
return int(val.strip())
|
|
|
|
def read_field_value_dict(val):
|
|
d = {}
|
|
if val.strip() == '':
|
|
return d
|
|
for line in val.strip().split('\n'):
|
|
key, value = line.strip().split(' ', 1)
|
|
if key in d:
|
|
d[key].append(value)
|
|
else:
|
|
d[key] = [value]
|
|
return d
|
|
|
|
def read_field_value_list(val):
|
|
return val.strip().split(' ')
|
|
|
|
def read_action(lines):
|
|
'''
|
|
Parse text as an action, returning a dictionary.
|
|
'''
|
|
print 'reading action'
|
|
print 'lines:'
|
|
print lines
|
|
d = {}
|
|
lastfield = None
|
|
for (linenr, line) in lines:
|
|
if len(line) == 0:
|
|
raise CommitFormatSyntaxError('Empty line in action', linenr)
|
|
if line[0] in [' ', '\t']: # continuation line
|
|
if not lastfield:
|
|
raise CommitFormatSyntaxError('First line is continuation line: ' + line, linenr)
|
|
d[lastfield] = d[lastfield] + '\n' + line.strip()
|
|
else:
|
|
field, value = line.split(':', 1)
|
|
d[field] = value.strip()
|
|
lastfield = field
|
|
|
|
firstlinenr = lines[0][0]
|
|
|
|
if 'action' not in d:
|
|
raise CommitFormatSyntaxError('Missing \'action\' field', firstlinenr)
|
|
action = d['action']
|
|
|
|
print 'dict:'
|
|
print d
|
|
|
|
for field in action_fields[action]['required']:
|
|
if field not in d:
|
|
raise CommitFormatSyntaxError('Missing required field \'%s\' in \'%s\' action' %
|
|
(field, action),
|
|
firstlinenr)
|
|
data_type = action_fields[action]['type']
|
|
result = { 'action': action }
|
|
for field, ftype in fields[data_type]:
|
|
if field in d:
|
|
reader = { 's': read_field_value_str,
|
|
'i': read_field_value_int,
|
|
'd': read_field_value_dict,
|
|
'l': read_field_value_list }[ftype]
|
|
result[field] = reader(d[field])
|
|
else:
|
|
result[field] = None
|
|
|
|
print 'final dict:'
|
|
print result
|
|
|
|
return result
|
|
|
|
def read_paragraphs(text):
|
|
lines = text.split('\n')
|
|
current_para = []
|
|
paragraphs = []
|
|
comment_re = r'^#.*$'
|
|
blank_re = r'^[ \t]*$'
|
|
for i in xrange(len(lines)):
|
|
l = lines[i]
|
|
if re.match(comment_re, l):
|
|
print 'comment:', l
|
|
continue
|
|
elif re.match(blank_re, l):
|
|
print 'blank:', l
|
|
if len(current_para) > 0:
|
|
paragraphs.append(current_para)
|
|
current_para = []
|
|
else:
|
|
current_para.append((i+1, l))
|
|
if len(current_para) > 0:
|
|
paragraphs.append(current_para)
|
|
current_para = []
|
|
return paragraphs
|
|
|
|
def read_actionlist(text):
|
|
'''
|
|
Parse text as a list of actions.
|
|
|
|
The result is a list of dictionaries.
|
|
'''
|
|
paragraphs = read_paragraphs(text)
|
|
print 'paragraphs:', paragraphs
|
|
return map(read_action, paragraphs)
|
|
|
|
def write_field_value_str(val):
|
|
lines = ''
|
|
if not val:
|
|
val = ''
|
|
val = unicode(val)
|
|
value_lines = val.split('\n')
|
|
for l in value_lines:
|
|
lines += ' ' + l + '\n'
|
|
return lines
|
|
|
|
def write_field_value_dict(val):
|
|
lines = '\n'
|
|
for (key,values) in val.items():
|
|
for single_value in values:
|
|
lines += ' ' + key + ' ' + unicode(single_value) + '\n'
|
|
return lines
|
|
|
|
def write_field_value_list(val):
|
|
lines = ''
|
|
for single_value in val:
|
|
lines += ' ' + unicode(single_value)
|
|
return lines
|
|
|
|
def make_comment(s):
|
|
return '\n'.join(map(lambda x: '# ' + x,
|
|
s.split('\n'))) + '\n'
|
|
|
|
def write_action(d):
|
|
if type(d) in types.StringTypes:
|
|
return make_comment(d)
|
|
lines = ''
|
|
if 'comment' in d:
|
|
lines += make_comment(d['comment'])
|
|
action = d['action']
|
|
lines += 'action: ' + action + '\n'
|
|
data_type = action_fields[action]['type']
|
|
for field, ftype in fields[data_type]:
|
|
if field in d:
|
|
value_writer = {'s': write_field_value_str,
|
|
'i': write_field_value_str,
|
|
'd': write_field_value_dict,
|
|
'l': write_field_value_list}[ftype]
|
|
lines += field + ':' + value_writer(d[field])
|
|
return lines
|
|
|
|
def write_actionlist(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']}])
|