diff --git a/worblehat/cli/main.py b/worblehat/cli/main.py index de8e5f4..666373f 100644 --- a/worblehat/cli/main.py +++ b/worblehat/cli/main.py @@ -8,7 +8,6 @@ from sqlalchemy import ( from sqlalchemy.orm import ( Session, ) -from worblehat.cli.subclis.bookcase_item import BookcaseItemCli from worblehat.services.bookcase_item import ( create_bookcase_item_from_isbn, @@ -20,6 +19,9 @@ from .prompt_utils import * from worblehat.config import Config from worblehat.models import * +from .subclis.bookcase_item import BookcaseItemCli +from .subclis.bookcase_shelf_selector import select_bookcase_shelf + # TODO: Category seems to have been forgotten. Maybe relevant interactivity should be added? # However, is there anyone who are going to search by category rather than just look in # the shelves? @@ -66,14 +68,10 @@ class WorblehatCli(NumberedCmd): bookcase_uid = None for shelf in bookcase_shelfs: if shelf.bookcase.uid != bookcase_uid: - print(shelf.bookcase.name) + print(shelf.bookcase.short_str()) bookcase_uid = shelf.bookcase.uid - name = f"r{shelf.row}-c{shelf.column}" - if shelf.description is not None: - name += f" [{shelf.description}]" - - print(f' {name} - {sum(i.amount for i in shelf.items)} items') + print(f' {shelf.short_str()} - {sum(i.amount for i in shelf.items)} items') def do_show_bookcase(self, arg: str): @@ -85,10 +83,7 @@ class WorblehatCli(NumberedCmd): bookcase = bookcase_selector.result for shelf in bookcase.shelfs: - name = f"r{shelf.row}-c{shelf.column}" - if shelf.description is not None: - name += f" [{shelf.description}]" - print(name) + print(shelf.short_str()) for item in shelf.items: print(f' {item.name} - {item.amount} copies') @@ -126,15 +121,6 @@ class WorblehatCli(NumberedCmd): bookcase_selector.cmdloop() bookcase = bookcase_selector.result - while True: - row = input('Row> ') - try: - row = int(row) - except ValueError: - print('Error: row must be a number') - continue - break - while True: column = input('Column> ') try: @@ -144,15 +130,24 @@ class WorblehatCli(NumberedCmd): continue break + while True: + row = input('Row> ') + try: + row = int(row) + except ValueError: + print('Error: row must be a number') + continue + break + if self.sql_session.scalars( select(BookcaseShelf) .where( BookcaseShelf.bookcase == bookcase, - BookcaseShelf.row == row, BookcaseShelf.column == column, + BookcaseShelf.row == row, ) ).one_or_none() is not None: - print(f'Error: a bookshelf in bookcase {bookcase.name} with position {row}-{column} already exists') + print(f'Error: a bookshelf in bookcase {bookcase.name} with position c{column}-r{row} already exists') return description = input('Description> ') @@ -172,7 +167,7 @@ class WorblehatCli(NumberedCmd): def _create_bookcase_item(self, isbn: str): bookcase_item = create_bookcase_item_from_isbn(isbn, self.sql_session) if bookcase_item is None: - print(f'Could not find data about item with isbn {isbn} online.') + print(f'Could not find data about item with ISBN {isbn} online.') print(f'If you think this is not due to a bug, please add the book to openlibrary.org before continuing.') return else: @@ -191,37 +186,7 @@ class WorblehatCli(NumberedCmd): bookcase_selector.cmdloop() bookcase = bookcase_selector.result - def __complete_bookshelf_selection(session: Session, cls: type, arg: str): - args = arg.split('-') - query = select(cls.row, cls.column).where(cls.bookcase == bookcase) - try: - if arg != '' and len(args) > 0: - query = query.where(cls.row == int(args[0])) - if len(args) > 1: - query = query.where(cls.column == int(args[1])) - except ValueError: - return [] - - result = session.execute(query).all() - return [f"{r}-{c}" for r,c in result] - - print('Please select the shelf where the item is placed:') - bookcase_shelf_selector = InteractiveItemSelector( - cls = BookcaseShelf, - sql_session = self.sql_session, - execute_selection = lambda session, cls, arg: session.scalars( - select(cls) - .where( - cls.bookcase == bookcase, - cls.column == int(arg.split('-')[1]), - cls.row == int(arg.split('-')[0]), - ) - ).all(), - complete_selection = __complete_bookshelf_selection, - ) - - bookcase_shelf_selector.cmdloop() - bookcase_item.shelf = bookcase_shelf_selector.result + bookcase_item.shelf = select_bookcase_shelf(bookcase, self.sql_session) print('Please select the items media type:') media_type_selector = InteractiveItemSelector( @@ -254,14 +219,14 @@ class WorblehatCli(NumberedCmd): select(BookcaseItem) .where(BookcaseItem.isbn == isbn) ).one_or_none()) is not None: - print('Found existing BookcaseItem:', existing_item) + print(f'\nFound existing item for isbn "{isbn}"') BookcaseItemCli( sql_session = self.sql_session, bookcase_item = existing_item, ).cmdloop() return - if prompt_yes_no(f"Could not find item with isbn '{isbn}'.\nWould you like to create it?", default=True): + if prompt_yes_no(f"Could not find item with ISBN '{isbn}'.\nWould you like to create it?", default=True): self._create_bookcase_item(isbn) diff --git a/worblehat/cli/subclis/bookcase_item.py b/worblehat/cli/subclis/bookcase_item.py index 2775eb8..485d305 100644 --- a/worblehat/cli/subclis/bookcase_item.py +++ b/worblehat/cli/subclis/bookcase_item.py @@ -9,6 +9,7 @@ from worblehat.cli.prompt_utils import ( prompt_yes_no, ) from worblehat.models import ( + Bookcase, BookcaseItem, Language, MediaType, @@ -18,21 +19,27 @@ from worblehat.services.bookcase_item import ( is_valid_isbn, ) +from .bookcase_shelf_selector import select_bookcase_shelf + +def _selected_bookcase_item_prompt(bookcase_item: BookcaseItem) -> str: + return dedent(f''' + Item: {bookcase_item.name} + ISBN: {bookcase_item.isbn} + Amount: {bookcase_item.amount} + Authors: {', '.join(a.name for a in bookcase_item.authors)} + Bookcase: {bookcase_item.shelf.bookcase.short_str()} + Shelf: {bookcase_item.shelf.short_str()} + ''') + class BookcaseItemCli(NumberedCmd): def __init__(self, sql_session: Session, bookcase_item: BookcaseItem): super().__init__() self.sql_session = sql_session self.bookcase_item = bookcase_item - def do_show(self, _: str): - print(dedent(f""" - Bookcase Item: - Name: {self.bookcase_item.name} - ISBN: {self.bookcase_item.isbn} - Amount: {self.bookcase_item.amount} - Shelf: {self.bookcase_item.shelf.column}-{self.bookcase_item.shelf.row} - Description: {self.bookcase_item.shelf.description} - """)) + @property + def prompt_header(self) -> str: + return _selected_bookcase_item_prompt(self.bookcase_item) def do_update_data(self, _: str): item = create_bookcase_item_from_isbn(self.sql_session, self.bookcase_item.isbn) @@ -41,35 +48,35 @@ class BookcaseItemCli(NumberedCmd): self.bookcase_item.authors = item.authors self.bookcase_item.language = item.language + def do_edit(self, arg: str): EditBookcaseCli(self.sql_session, self.bookcase_item, self).cmdloop() + def do_loan(self, arg: str): print('TODO: implement loan') - def do_exit(self, _: str): + + def do_done(self, _: str): return True + funcs = { 1: { - 'f': do_show, - 'doc': 'Show bookcase item', + 'f': do_loan, + 'doc': 'Loan', }, 2: { - 'f': do_update_data, - 'doc': 'Pull updated data from online databases', - }, - 3: { 'f': do_edit, 'doc': 'Edit', }, - 4: { - 'f': do_loan, - 'doc': 'Loan bookcase item', + 3: { + 'f': do_update_data, + 'doc': 'Pull updated data from online databases', }, - 5: { - 'f': do_exit, - 'doc': 'Exit', + 4: { + 'f': do_done, + 'doc': 'Done', }, } @@ -80,6 +87,9 @@ class EditBookcaseCli(NumberedCmd): self.bookcase_item = bookcase_item self.parent = parent + @property + def prompt_header(self) -> str: + return _selected_bookcase_item_prompt(self.bookcase_item) def do_name(self, _: str): while True: @@ -159,5 +169,51 @@ class EditBookcaseCli(NumberedCmd): self.bookcase_item.amount = new_amount - def do_exit(): + def do_shelf(self, _: str): + bookcase_selector = InteractiveItemSelector( + Bookcase, + self.sql_session, + ) + bookcase_selector.cmdloop() + bookcase = bookcase_selector.result + + shelf = select_bookcase_shelf(bookcase, self.sql_session) + + self.bookcase_item.shelf = shelf + + + def do_done(self, _: str): return True + + + funcs = { + 1: { + 'f': do_name, + 'doc': 'Change name', + }, + 2: { + 'f': do_isbn, + 'doc': 'Change ISBN', + }, + 3: { + 'f': do_language, + 'doc': 'Change language', + }, + 4: { + 'f': do_media_type, + 'doc': 'Change media type', + }, + 5: { + 'f': do_amount, + 'doc': 'Change amount', + }, + 6: { + 'f': do_shelf, + 'doc': 'Change shelf', + }, + 7: { + 'f': do_done, + 'doc': 'Done', + }, + } + diff --git a/worblehat/cli/subclis/bookcase_shelf_selector.py b/worblehat/cli/subclis/bookcase_shelf_selector.py new file mode 100644 index 0000000..7d4421e --- /dev/null +++ b/worblehat/cli/subclis/bookcase_shelf_selector.py @@ -0,0 +1,45 @@ +from sqlalchemy import select +from sqlalchemy.orm import Session + +from worblehat.cli.prompt_utils import InteractiveItemSelector +from worblehat.models import ( + Bookcase, + BookcaseShelf, +) + +def select_bookcase_shelf( + bookcase: Bookcase, + sql_session: Session, + prompt: str = "Please select the shelf where the item is placed (col-row):" +) -> BookcaseShelf: + def __complete_bookshelf_selection(session: Session, cls: type, arg: str): + args = arg.split('-') + query = select(cls.row, cls.column).where(cls.bookcase == bookcase) + try: + if arg != '' and len(args) > 0: + query = query.where(cls.column == int(args[0])) + if len(args) > 1: + query = query.where(cls.row == int(args[1])) + except ValueError: + return [] + + result = session.execute(query).all() + return [f"{c}-{r}" for r,c in result] + + print(prompt) + bookcase_shelf_selector = InteractiveItemSelector( + cls = BookcaseShelf, + sql_session = sql_session, + execute_selection = lambda session, cls, arg: session.scalars( + select(cls) + .where( + cls.bookcase == bookcase, + cls.column == int(arg.split('-')[0]), + cls.row == int(arg.split('-')[1]), + ) + ).all(), + complete_selection = __complete_bookshelf_selection, + ) + + bookcase_shelf_selector.cmdloop() + return bookcase_shelf_selector.result \ No newline at end of file diff --git a/worblehat/models/Bookcase.py b/worblehat/models/Bookcase.py index 6a3e854..51f3828 100644 --- a/worblehat/models/Bookcase.py +++ b/worblehat/models/Bookcase.py @@ -29,3 +29,9 @@ class Bookcase(Base, UidMixin, UniqueNameMixin): self.name = name self.description = description + def short_str(self) -> str: + result = self.name + if self.description is not None: + result += f' [{self.description}]' + return result + diff --git a/worblehat/models/BookcaseShelf.py b/worblehat/models/BookcaseShelf.py index fb21248..09762ec 100644 --- a/worblehat/models/BookcaseShelf.py +++ b/worblehat/models/BookcaseShelf.py @@ -50,4 +50,10 @@ class BookcaseShelf(Base, UidMixin): self.row = row self.column = column self.bookcase = bookcase - self.description = description \ No newline at end of file + self.description = description + + def short_str(self) -> str: + result = f'{self.column}-{self.row}' + if self.description is not None: + result += f' [{self.description}]' + return result \ No newline at end of file