diff --git a/ceo/library.py b/ceo/library.py index 5905826..5b7b459 100644 --- a/ceo/library.py +++ b/ceo/library.py @@ -11,6 +11,12 @@ import re LIBRARY_DB = "./csc_library.db" +def format_maybe(v): + if v is None: + return "unknown" + else: + return str(v) + class Book: def __init__(self, author, title, year): """Any of these may be None to indicate 'unknown'.""" @@ -30,6 +36,13 @@ class Book: raise Exception("Book was not signed out, no need to sign it in") def __str__(self): + author = self.author + book = "%s [%s]\nBy: %s" % (format_maybe(self.title), format_maybe(self.year), format_maybe(self.author)) + if self.signout: + book += "\n Signed out by %s on %s" % (self.signout.name, time.ctime(self.signout.date)) + return book + + def __repr__(self): return "Book(author=%r, title=%r, year=%r, signout=%r)" % (self.author, self.title, self.year, self.signout) class Signout: @@ -53,12 +66,12 @@ def reset(): def add(author, title, year): - db = shelve.open(LIBRARY_DB,'w') #use w here (not c) to ensure a crash if the DB file got erased (is this a good idea?) + db = shelve.open(LIBRARY_DB,'c') #use w here (not c) to ensure a crash if the DB file got erased (is this a good idea?) i = len(db) db[str(i)] = Book(author, title, year) db.close() -def search(author=None, title=None, year=None): +def search(author=None, title=None, year=None, signedout=None): """search for a title author and title are regular expressions year is a single number or a list of numbers (so use range() to search the DB) @@ -67,17 +80,22 @@ def search(author=None, title=None, year=None): this is extraordinarily inefficient, but whatever (I don't think that without having an indexer run inthe background we can improve this any?) returns: a list of Book objects """ - db = shelve.open(LIBRARY_DB, 'w', writeback=True) #open it for writing so that changes to books get saved + db = shelve.open(LIBRARY_DB, 'c', writeback=True) #open it for writing so that changes to books get saved all = db.values() #this should pull out the ID numbers somehow too.. bah if author is not None: - all = [book for book in all if book.author and re.match(author, book.author)] #should factor this out + all = [book for book in all if book.author and re.search(author, book.author)] #should factor this out if title is not None: - all = [book for book in all if book.title and re.match(title, book.title)] #should factor this out + all = [book for book in all if book.title and re.search(title, book.title)] #should factor this out if year is not None: if type(year) == int: year = [year] #now assume year is a list all = [book for book in all if book.year and book.year in year] + if signedout is not None: + if signedout: + all = [book for book in all if book.signout is not None] + else: + all = [book for book in all if book.signout is None] #aaah copypaste db.close() return all diff --git a/ceo/urwid/library.py b/ceo/urwid/library.py index eeae52d..12b0405 100644 --- a/ceo/urwid/library.py +++ b/ceo/urwid/library.py @@ -4,11 +4,13 @@ from ceo.urwid import search from ceo.urwid.widgets import * from ceo.urwid.window import * +import ceo.library as lib + def library(data): menu = make_menu([ ("Checkout Book", checkout_book, None), ("Return Book", return_book, None), - ("Search Books", search_books, None), + ("List Books", search_books, None), ("Add Book", add_book, None), ("Remove Book", remove_book, None), ("Back", raise_back, None), @@ -16,16 +18,201 @@ def library(data): push_window(menu, "Library") def checkout_book(data): + "should only search signed in books" pass def return_book(data): + "should bring up a searchbox of all the guys first" pass def search_books(data): - pass + push_wizard("Search Books", [ + SearchPage, + ]) + +def view_book(book): + "this should develop into a full fledged useful panel for doing stuff with books. for now it's not." + push_window(BookPage(book), "Book detail") + +def view_books(books): + #XXX should not use a hardcoded 20 in there, should grab the value from the width of the widget + widgets = [] + for b in books: + widgets.append(urwid.AttrWrap(ButtonText(view_book, b, str(b)), None, 'selected')) + widgets.append(urwid.Divider()) + push_window(urwid.ListBox(widgets)) def add_book(data): - pass + push_wizard("Add Book", [AddBookPage]) def remove_book(data): pass + + + +def parse_commaranges(s): + """parse a string into a list of numbers""" + def numbers(section): + if "-" in section: + range_ = section.split("-") + assert len(range_) == 2 + start = int(range_[0]) + end = int(range_[1]) + return range(start, end+1) #+1 to be inclusive of end + else: + return [int(section)] + + l = [] + for y in s.split(","): + l.append(numbers(y)) + return l + + +class AddBookPage(WizardPanel): + def init_widgets(self): + self.author = SingleEdit("Author: ") + self.title = SingleEdit("Title: ") + self.year = SingleIntEdit("Year(s): ") + self.widgets = [ + urwid.Text("Add Book"), + urwid.Divider(), + self.author, + self.title, + self.year, + ] + + def check(self): + author = self.author.get_edit_text() + if author == "": + author = None #null it so that searching ignores + title = self.title.get_edit_text() + if title == "": + title = None + try: + year = self.year.get_edit_text() + if year == "": + year = None + else: + year = int(year) + except: + self.focus_widget(self.year) + set_status("Invalid year") + return True + lib.add(author, title, year) + raise_back() + + + +class SearchPage(WizardPanel): + def init_widgets(self): + self.author = SingleEdit("Author: ") + self.title = SingleEdit("Title: ") + self.year = SingleEdit("Year(s): ") + self.signedout = urwid.CheckBox("Checked Out: ") + self.widgets = [ + urwid.Text("Search Library"), + urwid.Divider(), + self.author, + self.title, + self.year, + urwid.Divider(), + urwid.Text("Author/Title are regexes.\nYear is a comma-separated list or a hyphen-separated range") + ] + def check(self): + author = self.author.get_edit_text() + if author == "": + author = None #null it so that searching ignores + title = self.title.get_edit_text() + if title == "": + title = None + try: + years = self.year.get_edit_text() + if years == "": + years = None + else: + #try to parse the year field + years = parse_commaranges( year ) + except: + self.focus_widget(self.year) + set_status("Invalid year") + return True + signedout = self.signedout.get_state() + view_books(lib.search(author, title, years, signedout)) + + + + +class CheckoutPage(urwid.WidgetWrap): + def __init__(self, book): + self.book = SingleEdit("Book: ") #this needs to be a widget that when you click on it, it takes you to the search_books pane, lets you pick a book, and then drops you back here + self.user = SingleEdit("Checkoutee: ") + self.widgets = [ + urwid.Text("Checkout A Book"), + urwid.Divider(), + self.book, + self.user, + ] + urwid.WidgetWrap.__init__(self, urwid.Pile(self.widgets)) + +class ConfirmDialog(urwid.WidgetWrap): + def __init__(self, msg): + raise NotImplementedError + +def Confirm(msg): + #this should be in widgets.py + push_window(ConfirmDialog(msg)) + +class InputDialog(urwid.WidgetWrap): + def __init__(self, msg=None): + msg = urwid.Text(msg) + self.input = SingleEdit("") + ok = urwid.Button("OK", self.ok) + cancel = urwid.Button("Cancel", self.cancel) + buttons = urwid.Columns([ok, cancel]) + display = urwid.Pile([msg, self.input, buttons]) + urwid.WidgetWrap.__init__(self, display) + def ok(): + self.result = self.input.get_edit_text() + raise Abort() #break out of the inner event loop + def cancel(): + self.result = None + raise Abort() + +def urwid_input(msg): + #this should be in widgets.py + dialog = InputDialog(msg) + push_window(dialog) + event_loop(urwid.main.ui) #HACK + return dialog.result + +def do_checkout(book): + "this is temporary to fil lthe gap until we see what we reall need" + username = urwid_input("Username to check out to?") + if username is None: + set_status("Checkout cancelled") + else: + book.sign_out(username) + +def do_delete(book): + if Confirm("Do you wish to delete %r?" % book): + lib.delete(book) + +class BookPage(urwid.WidgetWrap): + def __init__(self, book): + self._book = book + self.author = SingleEdit("Author: ") + self.title = SingleEdit("Title: ") + self.year = SingleIntEdit("Year: ") + #now need a checkout widget to go down here.. + #and "Delete" + if book.signout is None: + self.checkout = ButtonText(do_checkout, book, "Check Out") + else: + self.checkout = ButtonText(lambda book: book.sign_in(), book, "Check In") + #self.remove = ButtonText(do_delete, book, "Delete") + display = urwid.GridFlow([self.author, self.title, self.year, + #self.checkout, + #self.remove + ], 15, 3, 1, 'left') + urwid.WidgetWrap.__init__(self, self.author) +