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:
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:

View File

@ -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))

View File

@ -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
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:
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 += " "
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:
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

View File

@ -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)

View File

@ -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):