diff --git a/text_interface/addstock.py b/text_interface/addstock.py index 19b9c95..b878b49 100644 --- a/text_interface/addstock.py +++ b/text_interface/addstock.py @@ -101,7 +101,7 @@ much money you're due in credits for the purchase when prompted.\n''' else: self.price = price - description = self.input_str('Log message> ', length_range=(0, 50)) + description = self.input_str('Log message', length_range=(0, 50)) if description == '': description = 'Purchased products for PVVVV, adjusted credit ' + str(self.price) for product in self.products: diff --git a/text_interface/editing.py b/text_interface/editing.py index 89fa7e8..1f2b0e2 100644 --- a/text_interface/editing.py +++ b/text_interface/editing.py @@ -11,10 +11,10 @@ class AddUserMenu(Menu): def _execute(self): self.print_header() - username = self.input_str('Username (should be same as PVV username)> ', User.name_re, (1, 10)) - cardnum = self.input_str('Card number (optional)> ', User.card_re, (0, 10)) + username = self.input_str('Username (should be same as PVV username)', regex=User.name_re, length_range=(1, 10)) + cardnum = self.input_str('Card number (optional)', regex=User.card_re, length_range=(0, 10)) cardnum = cardnum.lower() - rfid = self.input_str('RFID (optional)> ', User.rfid_re, (0, 10)) + rfid = self.input_str('RFID (optional)', regex=User.rfid_re, length_range=(0, 10)) user = User(username, cardnum, rfid) self.session.add(user) try: @@ -37,14 +37,14 @@ user, then rfid (write an empty line to remove the card number or rfid). def _execute(self): self.print_header() - user = self.input_user('User> ') + user = self.input_user('User') self.printc(f'Editing user {user.name}') card_str = f'"{user.card}"' if user.card is None: card_str = 'empty' # TODO: Inconsistent with other defaulted strings. Redo. - user.card = self.input_str('Card number (currently %s)> ' % card_str, - User.card_re, (0, 10), + user.card = self.input_str(f'Card number (currently {card_str})', + regex=User.card_re, length_range=(0, 10), empty_string_is_none=True) if user.card: user.card = user.card.lower() @@ -53,8 +53,8 @@ user, then rfid (write an empty line to remove the card number or rfid). if user.rfid is None: rfid_str = 'empty' # TODO: Inconsistent with other defaulted strings. Redo. - user.rfid = self.input_str(f'RFID (currently {rfid_str})> ', - User.rfid_re, (0, 10), + user.rfid = self.input_str(f'RFID (currently {rfid_str})', + regex=User.rfid_re, length_range=(0, 10), empty_string_is_none=True) try: self.session.commit() @@ -70,9 +70,9 @@ class AddProductMenu(Menu): def _execute(self): self.print_header() - bar_code = self.input_str('Bar code> ', Product.bar_code_re, (8, 13)) - name = self.input_str('Name> ', Product.name_re, (1, Product.name_length)) - price = self.input_int('Price> ', (1, 100000)) + bar_code = self.input_str('Bar code', regex=Product.bar_code_re, length_range=(8, 13)) + name = self.input_str('Name', regex=Product.name_re, length_range=(1, Product.name_length)) + price = self.input_int('Price', allowed_range=(1, 100000)) product = Product(bar_code, name, price) self.session.add(product) try: @@ -89,7 +89,7 @@ class EditProductMenu(Menu): def _execute(self): self.print_header() - product = self.input_product('Product> ') + product = self.input_product('Product') self.printc(f'Editing product {product.name}') while True: selector = Selector(f'Do what with {product.name}?', @@ -100,11 +100,13 @@ class EditProductMenu(Menu): ('store', 'Store')]) what = selector.execute() if what == 'name': - product.name = self.input_str(f'Name[{product.name}]> ', Product.name_re, (1, product.name_length)) + product.name = self.input_str(f'Name[{product.name}]', regex=Product.name_re, + length_range=(1, product.name_length)) elif what == 'price': - product.price = self.input_int(f'Price[{product.price}]> ', (1, 100000)) + product.price = self.input_int(f'Price[{product.price}]', allowed_range=(1, 100000)) elif what == 'barcode': - product.bar_code = self.input_str(f'Bar code[{product.bar_code}]> ', Product.bar_code_re, (8, 13)) + product.bar_code = self.input_str(f'Bar code[{product.bar_code}]', regex=Product.bar_code_re, + length_range=(8, 13)) elif what == 'hidden': product.hidden = self.confirm('Hidden[%s]' % ("Y" if product.hidden else "N"), False) elif what == 'store': @@ -128,19 +130,17 @@ class AdjustStockMenu(Menu): def _execute(self): self.print_header() - product = self.input_product('Product> ') + product = self.input_product('Product') - print(f'The stock of this product is: {product.stock:d} ') + print(f'The stock of this product is: {product.stock:d}') print('Write the number of products you have added to the stock') print('Alternatively, correct the stock for any mistakes') - add_stock = self.input_int('Added stock> ', (-1000, 1000)) + add_stock = self.input_int('Added stock', allowed_range=(-1000, 1000)) # TODO: Print something else when adding negative stock? print(f'You added {add_stock:d} to the stock of {product}') product.stock += add_stock - print(f'The stock is now {product.stock:d}') - try: self.session.commit() print('Stock is now stored') @@ -172,7 +172,7 @@ class CleanupStockMenu(Menu): for product in products: oldstock = product.stock - product.stock = self.input_int(product.name, (0, 10000), default=max(0, oldstock)) + product.stock = self.input_int(product.name, allowed_range=(0, 10000), default=max(0, oldstock)) self.session.add(product) if oldstock != product.stock: changed_products.append((product, oldstock)) diff --git a/text_interface/helpermenus.py b/text_interface/helpermenus.py index f69e5fe..80ad6b4 100644 --- a/text_interface/helpermenus.py +++ b/text_interface/helpermenus.py @@ -10,8 +10,7 @@ from select import select import conf from db import User, Session from helpers import search_user, search_product, guess_data_type, argmax -from text_interface import context_commands, local_help_commands, help_commands, \ - exit_commands +from text_interface import context_commands, local_help_commands, help_commands, exit_commands class ExitMenu(Exception): @@ -19,13 +18,14 @@ class ExitMenu(Exception): class Menu(object): - def __init__(self, name, items=None, prompt='> ', + def __init__(self, name, items=None, prompt=None, end_prompt="> ", return_index=True, exit_msg=None, exit_confirm_msg=None, exit_disallowed_msg=None, help_text=None, uses_db=False): self.name = name self.items = items if items is not None else [] self.prompt = prompt + self.end_prompt = end_prompt self.return_index = return_index self.exit_msg = exit_msg self.exit_confirm_msg = exit_confirm_msg @@ -86,36 +86,18 @@ class Menu(object): return i return self.items[i] - # TODO: Allow default - def input_str(self, prompt=None, regex=None, length_range=(None, None), - empty_string_is_none=False, timeout=None): + def input_str(self, prompt=None, end_prompt=None, regex=None, length_range=(None, None), + empty_string_is_none=False, timeout=None, default=None): if prompt is None: - prompt = self.prompt - if regex is not None: - while True: - result = self.input_str(prompt, length_range=length_range, - empty_string_is_none=empty_string_is_none) - if result is None or re.match(regex + '$', result): - return result - else: - print(f'Value must match regular expression "{regex}"') - if length_range != (None, None): - while True: - result = self.input_str(prompt, empty_string_is_none=empty_string_is_none) - if result is None: - length = 0 - else: - length = len(result) - if ((length_range[0] and length < length_range[0]) or - (length_range[1] and length > length_range[1])): - if length_range[0] and length_range[1]: - print(f'Value must have length in range [{length_range[0]:d}, {length_range[1]:d}]') - elif length_range[0]: - print(f'Value must have length at least {length_range[0]:d}') - else: - print(f'Value must have length at most {length_range[1]:d}') - else: - return result + prompt = self.prompt if self.prompt is not None else "" + if default is not None: + prompt += f" [{default}]" + if end_prompt is not None: + prompt += end_prompt + elif self.end_prompt is not None: + prompt += self.end_prompt + else: + prompt += " " while True: try: if timeout: @@ -127,7 +109,7 @@ class Menu(object): # timeout occurred, simulate empty line result = '' else: - result = input().strip() + result = input(prompt).strip() else: result = input(prompt).strip() except EOFError: @@ -150,6 +132,21 @@ class Menu(object): continue if empty_string_is_none and result == '': return None + if default is not None and result == '': + return default + if regex is not None and not re.match(regex + '$', result): + print(f'Value must match regular expression "{regex}"') + continue + if length_range != (None, None): + length = len(result) + if ((length_range[0] and length < length_range[0]) or (length_range[1] and length > length_range[1])): + if length_range[0] and length_range[1]: + print(f'Value must have length in range [{length_range[0]:d}, {length_range[1]:d}]') + elif length_range[0]: + print(f'Value must have length at least {length_range[0]:d}') + else: + print(f'Value must have length at most {length_range[1]:d}') + continue return result def special_input_options(self, result): @@ -172,13 +169,12 @@ class Menu(object): """ return False - def input_choice(self, number_of_choices, prompt=None): - if prompt is None: - prompt = self.prompt + def input_choice(self, number_of_choices, prompt=None, end_prompt=None): while True: - result = self.input_str(prompt) + result = self.input_str(prompt, end_prompt) if result == '': print('Please enter something') + #TODO: Move this into mainmenu.py special options # 'c' in main menu to change colours elif result == 'c': os.system('echo -e "\033[' + str(random.randint(40, 49)) + ';' + str(random.randint(30, 37)) + ';5m"') @@ -204,21 +200,14 @@ class Menu(object): def invalid_menu_choice(self, in_str): print('Please enter a valid choice.') - def input_int(self, prompt=None, allowed_range=(None, None), null_allowed=False, default=None): - # TODO: Proper default handling - if prompt is None: - prompt = self.prompt + def input_int(self, prompt=None, end_prompt=None, allowed_range=(None, None), null_allowed=False, default=None): while True: - result = self.input_str(prompt) - if result == '': - if default is not None: - return default - elif null_allowed: - return False + result = self.input_str(prompt, end_prompt, default=default) + if result == '' and null_allowed: + return False try: value = int(result) - if ((allowed_range[0] and value < allowed_range[0]) or - (allowed_range[1] and value > allowed_range[1])): + if ((allowed_range[0] and value < allowed_range[0]) or (allowed_range[1] and value > allowed_range[1])): if allowed_range[0] and allowed_range[1]: print(f'Value must be in range [{allowed_range[0]:d}, {allowed_range[1]:d}]') elif allowed_range[0]: @@ -230,40 +219,40 @@ class Menu(object): except ValueError: print("Please enter an integer") - def input_user(self, prompt=None): + def input_user(self, prompt=None, end_prompt=None): user = None while user is None: - user = self.retrieve_user(self.input_str(prompt)) + user = self.retrieve_user(self.input_str(prompt, end_prompt)) return user def retrieve_user(self, search_str): return self.search_ui(search_user, search_str, 'user') - def input_product(self, prompt=None): + def input_product(self, prompt=None, end_prompt=None): product = None while product is None: - product = self.retrieve_product(self.input_str(prompt)) + product = self.retrieve_product(self.input_str(prompt, end_prompt)) return product def retrieve_product(self, search_str): return self.search_ui(search_product, search_str, 'product') - def input_thing(self, prompt=None, permitted_things=('user', 'product'), + def input_thing(self, prompt=None, end_prompt=None, permitted_things=('user', 'product'), add_nonexisting=(), empty_input_permitted=False, find_hidden_products=True): result = None while result is None: - search_str = self.input_str(prompt) + search_str = self.input_str(prompt, end_prompt) if search_str == '' and empty_input_permitted: return None result = self.search_for_thing(search_str, permitted_things, add_nonexisting, find_hidden_products) return result - def input_multiple(self, prompt=None, permitted_things=('user', 'product'), + def input_multiple(self, prompt=None, end_prompt=None, permitted_things=('user', 'product'), add_nonexisting=(), empty_input_permitted=False, find_hidden_products=True): result = None num = 0 while result is None: - search_str = self.input_str(prompt) + search_str = self.input_str(prompt, end_prompt) search_lst = search_str.split(" ") if search_str == '' and empty_input_permitted: return None @@ -330,20 +319,20 @@ class Menu(object): ('set', f'Set card number of an existing user to {string}')]) selection = selector.execute() if selection == 'create': - username = self.input_str('Username for new user (should be same as PVV username)> ', + username = self.input_str('Username for new user (should be same as PVV username)', User.name_re, (1, 10)) user = User(username, string) self.session.add(user) return user if selection == 'set': - user = self.input_user('User to set card number for> ') + user = self.input_user('User to set card number for') old_card = user.card user.card = string print(f'Card number of {user.name} set to {string} (was {old_card})') return user return None if type_guess == 'bar_code': - print('"%s" looks like the bar code for a product, but no such product exists.' % string) + print(f'"{string}" looks like the bar code for a product, but no such product exists.') return None def search_ui(self, search_fun, search_str, thing): @@ -373,8 +362,8 @@ class Menu(object): return selector.execute() @staticmethod - def confirm(prompt, default=None, timeout=None): - return ConfirmMenu(prompt, default, timeout).execute() + def confirm(prompt, end_prompt=None, default=None, timeout=None): + return ConfirmMenu(prompt, end_prompt=None, default=default, timeout=timeout).execute() def header(self): return f"[{self.name}]" @@ -384,7 +373,7 @@ class Menu(object): print(self.header()) def pause(self): - self.input_str('.') + self.input_str('.', end_prompt="") @staticmethod def general_help(): @@ -445,7 +434,7 @@ class Menu(object): return None for i in range(len(self.items)): self.printc(line_format % (i + 1, self.item_name(i))) - item_i = self.input_choice(len(self.items), prompt=self.prompt) - 1 + item_i = self.input_choice(len(self.items)) - 1 if self.item_is_submenu(item_i): self.items[item_i].execute() else: @@ -467,8 +456,8 @@ class MessageMenu(Menu): class ConfirmMenu(Menu): - def __init__(self, prompt='confirm?', default=None, timeout=0): - Menu.__init__(self, 'question', prompt=prompt, + def __init__(self, prompt='confirm? ', end_prompt=": ", default=None, timeout=0): + Menu.__init__(self, 'question', prompt=prompt, end_prompt=end_prompt, exit_disallowed_msg='Please answer yes or no') self.default = default self.timeout = timeout @@ -476,7 +465,7 @@ class ConfirmMenu(Menu): def _execute(self): options = {True: '[y]/n', False: 'y/[n]', None: 'y/n'}[self.default] while True: - result = self.input_str(f'{self.prompt} ({options}): ', timeout=self.timeout) + result = self.input_str(f'{self.prompt} ({options})', end_prompt=": ", timeout=self.timeout) result = result.lower().strip() if result in ['y', 'yes']: return True @@ -489,11 +478,11 @@ class ConfirmMenu(Menu): class Selector(Menu): - def __init__(self, name, items=None, prompt='select> ', return_index=True, exit_msg=None, exit_confirm_msg=None, + def __init__(self, name, items=None, prompt='select', return_index=True, exit_msg=None, exit_confirm_msg=None, help_text=None): if items is None: items = [] - Menu.__init__(self, name, items, prompt, return_index, exit_msg) + Menu.__init__(self, name, items, prompt, return_index=return_index, exit_msg=exit_msg) def header(self): return self.name diff --git a/text_interface/miscmenus.py b/text_interface/miscmenus.py index d8523c6..8513119 100644 --- a/text_interface/miscmenus.py +++ b/text_interface/miscmenus.py @@ -12,13 +12,13 @@ class TransferMenu(Menu): def _execute(self): self.print_header() - amount = self.input_int('Transfer amount> ', (1, 100000)) + amount = self.input_int('Transfer amount', (1, 100000)) self.set_context(f'Transferring {amount:d} kr', display=False) - user1 = self.input_user('From user> ') + user1 = self.input_user('From user') self.add_to_context(f' from {user1.name}') - user2 = self.input_user('To user> ') + user2 = self.input_user('To user') self.add_to_context(f' to {user2.name}') - comment = self.input_str('Comment> ') + comment = self.input_str('Comment') self.add_to_context(f' (comment) {user2.name}') t1 = Transaction(user1, amount, @@ -46,7 +46,7 @@ class ShowUserMenu(Menu): def _execute(self): self.print_header() - user = self.input_user('User name, card number or RFID> ') + user = self.input_user('User name, card number or RFID') print(f'User name: {user.name}') print(f'Card number: {user.card}') print(f'RFID: {user.rfid}') @@ -159,17 +159,17 @@ class AdjustCreditMenu(Menu): def _execute(self): self.print_header() - user = self.input_user('User> ') + user = self.input_user('User') print(f"User {user.name}'s credit is {user.credit:d} kr") self.set_context(f'Adjusting credit for user {user.name}', display=False) print('(Note on sign convention: Enter a positive amount here if you have') print('added money to the PVVVV money box, a negative amount if you have') print('taken money from it)') - amount = self.input_int('Add amount> ', (-100000, 100000)) + amount = self.input_int('Add amount', allowed_range=(-100000, 100000)) print('(The "log message" will show up in the transaction history in the') print('"Show user" menu. It is not necessary to enter a message, but it') print('might be useful to help you remember why you adjusted the credit)') - description = self.input_str('Log message> ', length_range=(0, 50)) + description = self.input_str('Log message', length_range=(0, 50)) if description == '': description = 'manually adjusted credit' transaction = Transaction(user, -amount, description) diff --git a/text_interface/printermenu.py b/text_interface/printermenu.py index f21c22d..6eff8e9 100644 --- a/text_interface/printermenu.py +++ b/text_interface/printermenu.py @@ -18,7 +18,7 @@ Put it up somewhere in the vicinity. def _execute(self): self.print_header() - thing = self.input_thing('Product/User> ') + thing = self.input_thing('Product/User') if isinstance(thing, Product): if re.match(r"^[0-9]{13}$", thing.bar_code):