Rewrite input_str to allow defaults and act less strange with some parameters

This commit is contained in:
Robert Maikher 2018-08-30 14:59:37 +02:00
parent 1675f26080
commit ca19cc14da
5 changed files with 89 additions and 100 deletions

View File

@ -101,7 +101,7 @@ much money you're due in credits for the purchase when prompted.\n'''
else: else:
self.price = price 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 == '': if description == '':
description = 'Purchased products for PVVVV, adjusted credit ' + str(self.price) description = 'Purchased products for PVVVV, adjusted credit ' + str(self.price)
for product in self.products: for product in self.products:

View File

@ -11,10 +11,10 @@ class AddUserMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() self.print_header()
username = self.input_str('Username (should be same as PVV username)> ', User.name_re, (1, 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)> ', User.card_re, (0, 10)) cardnum = self.input_str('Card number (optional)', regex=User.card_re, length_range=(0, 10))
cardnum = cardnum.lower() 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) user = User(username, cardnum, rfid)
self.session.add(user) self.session.add(user)
try: try:
@ -37,14 +37,14 @@ user, then rfid (write an empty line to remove the card number or rfid).
def _execute(self): def _execute(self):
self.print_header() self.print_header()
user = self.input_user('User> ') user = self.input_user('User')
self.printc(f'Editing user {user.name}') self.printc(f'Editing user {user.name}')
card_str = f'"{user.card}"' card_str = f'"{user.card}"'
if user.card is None: if user.card is None:
card_str = 'empty' card_str = 'empty'
# TODO: Inconsistent with other defaulted strings. Redo. # TODO: Inconsistent with other defaulted strings. Redo.
user.card = self.input_str('Card number (currently %s)> ' % card_str, user.card = self.input_str(f'Card number (currently {card_str})',
User.card_re, (0, 10), regex=User.card_re, length_range=(0, 10),
empty_string_is_none=True) empty_string_is_none=True)
if user.card: if user.card:
user.card = user.card.lower() 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: if user.rfid is None:
rfid_str = 'empty' rfid_str = 'empty'
# TODO: Inconsistent with other defaulted strings. Redo. # TODO: Inconsistent with other defaulted strings. Redo.
user.rfid = self.input_str(f'RFID (currently {rfid_str})> ', user.rfid = self.input_str(f'RFID (currently {rfid_str})',
User.rfid_re, (0, 10), regex=User.rfid_re, length_range=(0, 10),
empty_string_is_none=True) empty_string_is_none=True)
try: try:
self.session.commit() self.session.commit()
@ -70,9 +70,9 @@ class AddProductMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() self.print_header()
bar_code = self.input_str('Bar code> ', Product.bar_code_re, (8, 13)) bar_code = self.input_str('Bar code', regex=Product.bar_code_re, length_range=(8, 13))
name = self.input_str('Name> ', Product.name_re, (1, Product.name_length)) name = self.input_str('Name', regex=Product.name_re, length_range=(1, Product.name_length))
price = self.input_int('Price> ', (1, 100000)) price = self.input_int('Price', allowed_range=(1, 100000))
product = Product(bar_code, name, price) product = Product(bar_code, name, price)
self.session.add(product) self.session.add(product)
try: try:
@ -89,7 +89,7 @@ class EditProductMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() self.print_header()
product = self.input_product('Product> ') product = self.input_product('Product')
self.printc(f'Editing product {product.name}') self.printc(f'Editing product {product.name}')
while True: while True:
selector = Selector(f'Do what with {product.name}?', selector = Selector(f'Do what with {product.name}?',
@ -100,11 +100,13 @@ class EditProductMenu(Menu):
('store', 'Store')]) ('store', 'Store')])
what = selector.execute() what = selector.execute()
if what == 'name': 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': 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': 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': elif what == 'hidden':
product.hidden = self.confirm('Hidden[%s]' % ("Y" if product.hidden else "N"), False) product.hidden = self.confirm('Hidden[%s]' % ("Y" if product.hidden else "N"), False)
elif what == 'store': elif what == 'store':
@ -128,19 +130,17 @@ class AdjustStockMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() 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('Write the number of products you have added to the stock')
print('Alternatively, correct the stock for any mistakes') 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? # TODO: Print something else when adding negative stock?
print(f'You added {add_stock:d} to the stock of {product}') print(f'You added {add_stock:d} to the stock of {product}')
product.stock += add_stock product.stock += add_stock
print(f'The stock is now {product.stock:d}')
try: try:
self.session.commit() self.session.commit()
print('Stock is now stored') print('Stock is now stored')
@ -172,7 +172,7 @@ class CleanupStockMenu(Menu):
for product in products: for product in products:
oldstock = product.stock 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) self.session.add(product)
if oldstock != product.stock: if oldstock != product.stock:
changed_products.append((product, oldstock)) changed_products.append((product, oldstock))

View File

@ -10,8 +10,7 @@ from select import select
import conf import conf
from db import User, Session from db import User, Session
from helpers import search_user, search_product, guess_data_type, argmax from helpers import search_user, search_product, guess_data_type, argmax
from text_interface import context_commands, local_help_commands, help_commands, \ from text_interface import context_commands, local_help_commands, help_commands, exit_commands
exit_commands
class ExitMenu(Exception): class ExitMenu(Exception):
@ -19,13 +18,14 @@ class ExitMenu(Exception):
class Menu(object): class Menu(object):
def __init__(self, name, items=None, prompt='> ', def __init__(self, name, items=None, prompt=None, end_prompt="> ",
return_index=True, return_index=True,
exit_msg=None, exit_confirm_msg=None, exit_disallowed_msg=None, exit_msg=None, exit_confirm_msg=None, exit_disallowed_msg=None,
help_text=None, uses_db=False): help_text=None, uses_db=False):
self.name = name self.name = name
self.items = items if items is not None else [] self.items = items if items is not None else []
self.prompt = prompt self.prompt = prompt
self.end_prompt = end_prompt
self.return_index = return_index self.return_index = return_index
self.exit_msg = exit_msg self.exit_msg = exit_msg
self.exit_confirm_msg = exit_confirm_msg self.exit_confirm_msg = exit_confirm_msg
@ -86,36 +86,18 @@ class Menu(object):
return i return i
return self.items[i] return self.items[i]
# TODO: Allow default def input_str(self, prompt=None, end_prompt=None, regex=None, length_range=(None, None),
def input_str(self, prompt=None, regex=None, length_range=(None, None), empty_string_is_none=False, timeout=None, default=None):
empty_string_is_none=False, timeout=None):
if prompt is None: if prompt is None:
prompt = self.prompt prompt = self.prompt if self.prompt is not None else ""
if regex is not None: if default is not None:
while True: prompt += f" [{default}]"
result = self.input_str(prompt, length_range=length_range, if end_prompt is not None:
empty_string_is_none=empty_string_is_none) prompt += end_prompt
if result is None or re.match(regex + '$', result): elif self.end_prompt is not None:
return result prompt += self.end_prompt
else: else:
print(f'Value must match regular expression "{regex}"') prompt += " "
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
while True: while True:
try: try:
if timeout: if timeout:
@ -127,7 +109,7 @@ class Menu(object):
# timeout occurred, simulate empty line # timeout occurred, simulate empty line
result = '' result = ''
else: else:
result = input().strip() result = input(prompt).strip()
else: else:
result = input(prompt).strip() result = input(prompt).strip()
except EOFError: except EOFError:
@ -150,6 +132,21 @@ class Menu(object):
continue continue
if empty_string_is_none and result == '': if empty_string_is_none and result == '':
return None 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 return result
def special_input_options(self, result): def special_input_options(self, result):
@ -172,13 +169,12 @@ class Menu(object):
""" """
return False return False
def input_choice(self, number_of_choices, prompt=None): def input_choice(self, number_of_choices, prompt=None, end_prompt=None):
if prompt is None:
prompt = self.prompt
while True: while True:
result = self.input_str(prompt) result = self.input_str(prompt, end_prompt)
if result == '': if result == '':
print('Please enter something') print('Please enter something')
#TODO: Move this into mainmenu.py special options
# 'c' in main menu to change colours # 'c' in main menu to change colours
elif result == 'c': elif result == 'c':
os.system('echo -e "\033[' + str(random.randint(40, 49)) + ';' + str(random.randint(30, 37)) + ';5m"') 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): def invalid_menu_choice(self, in_str):
print('Please enter a valid choice.') print('Please enter a valid choice.')
def input_int(self, prompt=None, allowed_range=(None, None), null_allowed=False, default=None): def input_int(self, prompt=None, end_prompt=None, allowed_range=(None, None), null_allowed=False, default=None):
# TODO: Proper default handling
if prompt is None:
prompt = self.prompt
while True: while True:
result = self.input_str(prompt) result = self.input_str(prompt, end_prompt, default=default)
if result == '': if result == '' and null_allowed:
if default is not None: return False
return default
elif null_allowed:
return False
try: try:
value = int(result) value = int(result)
if ((allowed_range[0] and value < allowed_range[0]) or if ((allowed_range[0] and value < allowed_range[0]) or (allowed_range[1] and value > allowed_range[1])):
(allowed_range[1] and value > allowed_range[1])):
if allowed_range[0] and 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}]') print(f'Value must be in range [{allowed_range[0]:d}, {allowed_range[1]:d}]')
elif allowed_range[0]: elif allowed_range[0]:
@ -230,40 +219,40 @@ class Menu(object):
except ValueError: except ValueError:
print("Please enter an integer") print("Please enter an integer")
def input_user(self, prompt=None): def input_user(self, prompt=None, end_prompt=None):
user = None user = None
while user is 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 return user
def retrieve_user(self, search_str): def retrieve_user(self, search_str):
return self.search_ui(search_user, search_str, 'user') 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 product = None
while product is 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 return product
def retrieve_product(self, search_str): def retrieve_product(self, search_str):
return self.search_ui(search_product, search_str, 'product') 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): add_nonexisting=(), empty_input_permitted=False, find_hidden_products=True):
result = None result = None
while result is 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: if search_str == '' and empty_input_permitted:
return None return None
result = self.search_for_thing(search_str, permitted_things, add_nonexisting, find_hidden_products) result = self.search_for_thing(search_str, permitted_things, add_nonexisting, find_hidden_products)
return result 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): add_nonexisting=(), empty_input_permitted=False, find_hidden_products=True):
result = None result = None
num = 0 num = 0
while result is None: while result is None:
search_str = self.input_str(prompt) search_str = self.input_str(prompt, end_prompt)
search_lst = search_str.split(" ") search_lst = search_str.split(" ")
if search_str == '' and empty_input_permitted: if search_str == '' and empty_input_permitted:
return None return None
@ -330,20 +319,20 @@ class Menu(object):
('set', f'Set card number of an existing user to {string}')]) ('set', f'Set card number of an existing user to {string}')])
selection = selector.execute() selection = selector.execute()
if selection == 'create': 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.name_re, (1, 10))
user = User(username, string) user = User(username, string)
self.session.add(user) self.session.add(user)
return user return user
if selection == 'set': 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 old_card = user.card
user.card = string user.card = string
print(f'Card number of {user.name} set to {string} (was {old_card})') print(f'Card number of {user.name} set to {string} (was {old_card})')
return user return user
return None return None
if type_guess == 'bar_code': 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 return None
def search_ui(self, search_fun, search_str, thing): def search_ui(self, search_fun, search_str, thing):
@ -373,8 +362,8 @@ class Menu(object):
return selector.execute() return selector.execute()
@staticmethod @staticmethod
def confirm(prompt, default=None, timeout=None): def confirm(prompt, end_prompt=None, default=None, timeout=None):
return ConfirmMenu(prompt, default, timeout).execute() return ConfirmMenu(prompt, end_prompt=None, default=default, timeout=timeout).execute()
def header(self): def header(self):
return f"[{self.name}]" return f"[{self.name}]"
@ -384,7 +373,7 @@ class Menu(object):
print(self.header()) print(self.header())
def pause(self): def pause(self):
self.input_str('.') self.input_str('.', end_prompt="")
@staticmethod @staticmethod
def general_help(): def general_help():
@ -445,7 +434,7 @@ class Menu(object):
return None return None
for i in range(len(self.items)): for i in range(len(self.items)):
self.printc(line_format % (i + 1, self.item_name(i))) 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): if self.item_is_submenu(item_i):
self.items[item_i].execute() self.items[item_i].execute()
else: else:
@ -467,8 +456,8 @@ class MessageMenu(Menu):
class ConfirmMenu(Menu): class ConfirmMenu(Menu):
def __init__(self, prompt='confirm?', default=None, timeout=0): def __init__(self, prompt='confirm? ', end_prompt=": ", default=None, timeout=0):
Menu.__init__(self, 'question', prompt=prompt, Menu.__init__(self, 'question', prompt=prompt, end_prompt=end_prompt,
exit_disallowed_msg='Please answer yes or no') exit_disallowed_msg='Please answer yes or no')
self.default = default self.default = default
self.timeout = timeout self.timeout = timeout
@ -476,7 +465,7 @@ class ConfirmMenu(Menu):
def _execute(self): def _execute(self):
options = {True: '[y]/n', False: 'y/[n]', None: 'y/n'}[self.default] options = {True: '[y]/n', False: 'y/[n]', None: 'y/n'}[self.default]
while True: 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() result = result.lower().strip()
if result in ['y', 'yes']: if result in ['y', 'yes']:
return True return True
@ -489,11 +478,11 @@ class ConfirmMenu(Menu):
class Selector(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): help_text=None):
if items is None: if items is None:
items = [] 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): def header(self):
return self.name return self.name

View File

@ -12,13 +12,13 @@ class TransferMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() 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) 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}') 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}') 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}') self.add_to_context(f' (comment) {user2.name}')
t1 = Transaction(user1, amount, t1 = Transaction(user1, amount,
@ -46,7 +46,7 @@ class ShowUserMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() 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'User name: {user.name}')
print(f'Card number: {user.card}') print(f'Card number: {user.card}')
print(f'RFID: {user.rfid}') print(f'RFID: {user.rfid}')
@ -159,17 +159,17 @@ class AdjustCreditMenu(Menu):
def _execute(self): def _execute(self):
self.print_header() 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") print(f"User {user.name}'s credit is {user.credit:d} kr")
self.set_context(f'Adjusting credit for user {user.name}', display=False) 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('(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('added money to the PVVVV money box, a negative amount if you have')
print('taken money from it)') 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('(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('"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)') 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 == '': if description == '':
description = 'manually adjusted credit' description = 'manually adjusted credit'
transaction = Transaction(user, -amount, description) transaction = Transaction(user, -amount, description)

View File

@ -18,7 +18,7 @@ Put it up somewhere in the vicinity.
def _execute(self): def _execute(self):
self.print_header() self.print_header()
thing = self.input_thing('Product/User> ') thing = self.input_thing('Product/User')
if isinstance(thing, Product): if isinstance(thing, Product):
if re.match(r"^[0-9]{13}$", thing.bar_code): if re.match(r"^[0-9]{13}$", thing.bar_code):