diff --git a/cli/arguments.txt b/cli/arguments.txt new file mode 100644 index 0000000..f4b6c75 --- /dev/null +++ b/cli/arguments.txt @@ -0,0 +1,67 @@ +# Forslag til kommandolinjeargumenter og deres betydning. + +# søk etter bøker som inneholder «computer programming» og «knuth» (i +# tittel, undertittel eller forfatternavn): +worlbehat search 'computer programming' knuth +# søk i beskrivelse også: +worblehat --search-description search tex + +# list opp alle bøkene: +worblehat list book +# vis boken med en gitt ISBN: +worblehat show 5467237485472 +# vis bok, med kart: +worblehat --with-map show 5467237485472 + +# list opp alle personer: +worblehat list person +# søk etter person: +worblehat search-person donald knuth +# vis en person: +worblehat show dek + +# skriv ut informasjon i samme format som commit forventer: +worblehat --commit-format show 5467237485472 +worblehat --commit-format show dek +# samme, til en ny fil i /tmp: +worblehat --commit-format --tmp-file show 5467237485472 +worblehat --commit-format --tmp-file show dek +# kan vise flere ting samtidig: +worblehat --commit-format --tmp-file show 5467237485472 432175437253 dek rms mlh + +# lagre informasjon fra en fil i databasen (filen kan inneholde både +# nye og endrede personer og bøker): +worblehat commit # leser fra stdin +worblehat commit fjas.txt + +# endre en bok/person +# (kombinerer `wh --commit-format --tmp-file show ...`, +# `$EDITOR /tmp/...` +# og `wh commit /tmp/...`) +worblehat edit 5467237485472 +worblehat edit 5467237485472 432175437253 dek rms mlh + +# foreslå informasjon om nye bøker (leser ISBN-numre fra stdin, søker +# i Google Books eller lignende, skriver info i commit-format til +# stdout (eller tmp-fil)): +worblehat suggest-book-data +worblehat --tmp-file suggest-book-data +# registrer nye bøker +# (kombinerer `wh --tmp-file suggest-book-data`, +# `$EDITOR /tmp/...` +# og `wh commit /tmp/...`) +worblehat register-books + +# list opp alle kategoriene (med id, navn, muligens noe mer): +worblehat list category +# vis en kategori (vis informasjon om kategorien, samt liste over bøkene): +worblehat show matematikk +# vis en kategori, med plassering på kart: +worblehat --with-map show matematikk + +# vis kart over bokhyllene: +worblehat map +# vis kart over hyllen som heter 'A': +worblehat map A +# vis kart over hyllen som heter 'A', og uthev hyllene med matematikk i: +worblehat map A matematikk diff --git a/cli/commit-file-format.txt b/cli/commit-file-format.txt new file mode 100644 index 0000000..98a8434 --- /dev/null +++ b/cli/commit-file-format.txt @@ -0,0 +1,70 @@ +Filformatet som forventes av 'commit'-kommandoen, og som skrives ut av +kommandoen 'suggest-book-data', samt av andre kommandoer når man gir +opsjonen --commit-format + +En linje som starter med '#' er en kommentar. + +Filen består av ett eller flere avsnitt. En blank linje indikerer +nytt avsnitt. Hvert avsnitt beskriver én ting som skal gjøres. De +mulige tingene å gjøre er: legge inn ny bok eller forfatter, endre en +eksisterende bok eller forfatter, slette en bok eller forfatter. + +Hvert avsnitt har en samling felter med tilhørende verdier. Et felt +skrives med feltnavn, kolon, verdi, newline. Hvis verdien skal bestå +av flere linjer, brukes whitespace (minst ett mellomrom eller en tab) +på begynnelsen av hver ekstra linje. Whitespace (inkludert newline) +mellom kolonet og verdien ignoreres. Whitespace på slutten av linjer +ignoreres. Whitespace på begynnelsen av fortsettelseslinjer fjernes i +verdien som lagres, men newline-ene beholdes. + +Eksempler på felter: + +title: Foo Bar +description: + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + In ut est ac ante aliquam dictum. Nulla facilisi. Cras vel + lectus mauris. In nec convallis mauris. + +Hvert avsnitt må inneholde feltet 'action', som beskriver hva som skal +gjøres. De mulige verdiene for action-feltet er: + + new-book + edit-book + delete-book + new-person + edit-person + delete-person + new-category + edit-category + delete-category + +Eksempler: + +action: new-book +isbn: 4325463287546 +title: Foo Bar +subtitle: Baaaz +category: matematikk +persons: + author rjh + author oo + illustrator ko +publisher: Foo Publishing +published_year: 2010 +edition: 1 +num_pages: 420 +series: +description: + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + In ut est ac ante aliquam dictum. Nulla facilisi. Cras vel + lectus mauris. In nec convallis mauris. +picture: +thumbnail: +references: + url http://example.org/ + wikipedia http://en.wikipedia.org/wiki/FooBar + +action: edit-category +id: matematikk +name: Matematikk +placement: T10 T11 T12 diff --git a/cli/fileformat.py b/cli/fileformat.py new file mode 100644 index 0000000..74e3b47 --- /dev/null +++ b/cli/fileformat.py @@ -0,0 +1,180 @@ +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'), ('title', 's'), ('category', 's'), + ('subtitle', 's'), ('persons', 'd'), ('publisher', 's'), + ('published_year', 'i'), ('edition', 'i'), ('num_pages', 'i'), + ('series', 's'), ('description', 's'), # TODO picture, thumbnail + ('references', 'd')], + 'person': + [('id', 's'), ('first_name', 's'), ('last_name', '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', 'first_name', 'last_name'] }, + '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): + pass + +def read_field_value_str(val): + 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(text): + ''' + Parse text as an action, returning a dictionary. + ''' + lines = text.split('\n') + print 'reading action' + print 'lines:' + print lines + d = {} + lastfield = None + for line in lines: + if len(line) == 0: + raise CommitFormatSyntaxError('Empty line in action') + if line[0] in [' ', '\t']: # continuation line + if not lastfield: + raise CommitFormatSyntaxError('First line is continuation line: ' + line) + d[lastfield] = d[lastfield] + '\n' + line.strip() + else: + field, value = line.split(':', 1) + d[field] = value.strip() + lastfield = field + + if 'action' not in d: + raise CommitFormatSyntaxError('Missing \'action\' field') + 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)) + 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]) + return result + +def read_actionlist(text): + ''' + Parse text as a list of actions. + + The result is a list of dictionaries. + ''' + return map(lambda x: read_action(x.strip()), + text.split('\n\n')) + +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']}]) diff --git a/cli/util.py b/cli/util.py new file mode 100644 index 0000000..9c0cda1 --- /dev/null +++ b/cli/util.py @@ -0,0 +1,65 @@ +def make_result_dict(cursor, row): + d = {} + for i in xrange(len(row)): + d[cursor.description[i][0]] = row[i] + return d + +def fetchone_dict(cursor): + row = cursor.fetchone() + if row != None: + return make_result_dict(cursor, row) + return None + +def fetchall_dict(cursor): + return map(lambda r: make_result_dict(cursor, r), + cursor.fetchall()) + +def first(lst): + return lst[0] + +def second(lst): + return lst[1] + +def count(predicate, lst): + c = 0 + for elem in lst: + if predicate(elem): + c = c+1 + return c + +def find(predicate, lst): + for elem in lst: + if predicate(elem): + return elem + return None + +def unique(lst): + newlst = [] + for elem in lst: + if elem not in newlst: + newlst.append(elem) + return newlst + +def mapcond(fun, predicate, lst): + def mapfun(x): + if predicate(x): + return fun(x) + return x + return map(mapfun, lst) + +def maptup(fun, lst): + return tuple(map(fun, lst)) + +def translate(value, translations): + return translations.get(value, value) + +def p(s): + encoded = s + if isinstance(s, unicode): + encoded = s.encode('utf8') + print encoded + +def cut_str(string, length, ellipsis='...'): + if len(string) < length: + return string + return string[0:length-len(ellipsis)]+ellipsis diff --git a/cli/worblehat.py b/cli/worblehat.py new file mode 100644 index 0000000..316bc76 --- /dev/null +++ b/cli/worblehat.py @@ -0,0 +1,12 @@ +import pgdb +from fileformat import read_actionlist, write_actionlist +from util import * + +connection = pgdb.connect(database='oysteini_pbb2', + user='oysteini_pbb', + password='lio5Aide', + host='postgres.pvv.ntnu.no'); + +c = connection.cursor() +c.execute('SELECT * from bok') +print fetchall_dict(c)