358 lines
No EOL
17 KiB
Python
358 lines
No EOL
17 KiB
Python
|
|
from __future__ import print_function
|
|
import math
|
|
import numbers
|
|
import platform
|
|
if (platform.python_version_tuple()[0] == '3'):
|
|
raw_input = input
|
|
__all__ = ['Menu']
|
|
_dsz_mode = False
|
|
try:
|
|
import dsz
|
|
import dsz.ui
|
|
|
|
def utf8(s):
|
|
return (s.encode('utf8') if (type(s) is unicode) else str(s))
|
|
_dsz_mode = True
|
|
except ImportError:
|
|
pass
|
|
_util_ip_mode = False
|
|
try:
|
|
import ip
|
|
_util_ip_mode = True
|
|
except ImportError:
|
|
pass
|
|
EXIT = 'Exit'
|
|
|
|
def _functiontype():
|
|
pass
|
|
|
|
class Menu(object, ):
|
|
|
|
def __init__(self):
|
|
self.__section_order = ['']
|
|
self.__section_content = {'': []}
|
|
self.__heading = None
|
|
self.__current = None
|
|
self.__current_index = None
|
|
self.__current_section = None
|
|
self.__display = None
|
|
self.__output = None
|
|
self.__needs_update = True
|
|
|
|
@property
|
|
def sections(self):
|
|
return self.__section_order
|
|
|
|
@property
|
|
def current(self):
|
|
assert (self.__current is not None), 'This method can only be invoked from within a callback.'
|
|
return self.__current
|
|
|
|
@property
|
|
def current_section(self):
|
|
assert (self.__current_section is not None), 'This method can only be invoked from within a callback.'
|
|
return self.__current_section
|
|
|
|
def set_current_text(self, text):
|
|
assert (self.__current_index is not None), 'This method can only be invoked from within a callback.'
|
|
assert ((type(text) is str) or (type(text) is unicode)), 'text must be str or unicode'
|
|
self.__section_content[self.__current_section][self.__current_index]['text'] = text
|
|
self.__needs_update = True
|
|
|
|
def set_current_state(self, state):
|
|
assert (self.__current_index is not None), 'This method can only be invoked from within a callback.'
|
|
self.__section_content[self.__current_section][self.__current_index]['state'] = state
|
|
self.__needs_update = True
|
|
|
|
def set_current_callback(self, callback):
|
|
assert (self.__current_index is not None), 'This method can only be invoked from within a callback.'
|
|
assert callable(callback), 'callback must be a function'
|
|
self.__section_content[self.__current_section][self.__current_index]['callback'] = callback
|
|
|
|
def set_current_cbparam(self, **optdict):
|
|
assert (self.__current_index is not None), 'This method can only be invoked from within a callback.'
|
|
self.__section_content[self.__current_section][self._current_index]['cbparam'] = optdict
|
|
|
|
def set_current_keywords(self, keywords):
|
|
assert (self.__current_index is not None), 'This method can only be invoked from within a callback.'
|
|
assert (type(keywords) is list), 'keywords must be a list'
|
|
self.__section_content[self.__current_index]['keywords'] = keywords
|
|
self.__needs_update = True
|
|
|
|
def add_section(self, section):
|
|
assert ((type(section) is str) or (type(section) is unicode)), 'str or unicode string required'
|
|
if (section not in self.__section_order):
|
|
self.__section_order.append(section)
|
|
self.__section_content[section] = []
|
|
self.__needs_update = True
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def add_option(self, option, section='', keywords=[], callback=None, state=None, hex=False, **optdict):
|
|
assert ((type(section) is str) or (type(section) is unicode)), 'str or unicode string required'
|
|
assert ((type(option) is str) or (type(option) is unicode)), 'str or unicode string required'
|
|
assert ((callback is None) or (type(callback) is type(_functiontype)) or (type(callback) is type(self.add_option))), 'callback must be a function, instancemethod, or None'
|
|
self.add_section(section)
|
|
self.__section_content[section].append({'text': option, 'keywords': keywords, 'callback': callback, 'cbparam': optdict, 'state': state, 'hex': hex})
|
|
self.__needs_update = True
|
|
|
|
def callback_str(self, allowempty=False, default=None):
|
|
result = None
|
|
while True:
|
|
result = self.__raw_input(('New value for "%s"' % self.current['text']), (self.current['state'] if (default is None) else default))
|
|
if (result or ((result == '') and allowempty)):
|
|
break
|
|
self.set_current_state(result)
|
|
|
|
def add_str_option(self, option, state, section='', keywords=[], allowempty=False, default=None):
|
|
self.add_option(option=option, section=section, state=state, keywords=keywords, callback=self.callback_str, allowempty=allowempty, default=default)
|
|
|
|
def callback_int(self, default=None):
|
|
self.set_current_state(self.__int_input(('New value for "%s"' % self.current['text']), (self.current['state'] if (default is None) else default)))
|
|
|
|
def add_int_option(self, option, state, section='', keywords=[], default=None):
|
|
assert isinstance(state, numbers.Integral), 'State must be an integer.'
|
|
self.add_option(option=option, section=section, state=state, keywords=keywords, callback=self.callback_int, default=default)
|
|
|
|
def callback_hex(self, default=None):
|
|
defresponse = (hex(self.current['state']) if (default is None) else default)
|
|
if (defresponse[(-1)] == 'L'):
|
|
defresponse = defresponse[0:(-1)]
|
|
self.set_current_state(self.__int_input(('New value for "%s":' % self.current['text']), defresponse))
|
|
|
|
def add_hex_option(self, option, state, section='', keywords=[], default=None):
|
|
assert isinstance(state, numbers.Integral), 'State must be an integer.'
|
|
self.add_option(option=option, state=state, section=section, keywords=keywords, callback=self.callback_hex, default=default, hex=True)
|
|
|
|
def callback_toggle(self, enabled='Enabled', disabled='<DISABLED>'):
|
|
if (self.current['state'] == enabled):
|
|
self.set_current_state(disabled)
|
|
else:
|
|
self.set_current_state(enabled)
|
|
|
|
def add_toggle_option(self, option, state=None, section='', keywords=[], enabled='Enabled', disabled='<DISABLED>'):
|
|
assert ((state is None) or (state == enabled) or (state == disabled)), 'Starting state must be either enabled or disabled.'
|
|
self.add_option(option=option, state=(state if (state is not None) else disabled), section=section, keywords=keywords, callback=self.callback_toggle, enabled=enabled, disabled=disabled)
|
|
|
|
def set_heading(self, heading):
|
|
assert ((type(heading) is list) or (type(heading) is str) or (type(heading) is unicode)), 'Heading content must be list, str, or unicode'
|
|
if ((type(heading) is str) or (type(heading) is unicode)):
|
|
self.__heading = [heading]
|
|
else:
|
|
self.__heading = heading
|
|
self.__needs_update = True
|
|
|
|
def execute(self, prompt='Enter selection', default=None, keywords=[], menuloop=True, exit=EXIT, exiton=[]):
|
|
assert ((type(prompt) is str) or (type(prompt) is unicode)), 'Prompt must be str or unicode'
|
|
assert (type(menuloop) is bool), 'Must be a Boolean state.'
|
|
assert ((type(exit) is str) or (type(exit) is unicode)), 'str or unicode required'
|
|
assert ((default is None) or (type(default) is int)), 'Use None for no default or enter a valid integer.'
|
|
assert (type(exiton) is list), 'exiton must be a list'
|
|
self.build_menu(keywords, exit)
|
|
maxvalue = (len(self.__display) - 1)
|
|
if (type(default) is int):
|
|
assert ((default >= 0) and (default <= maxvalue)), 'Default value must be within range of options.'
|
|
if menuloop:
|
|
result = (-1)
|
|
exiton.append(0)
|
|
while (result not in exiton):
|
|
if _dsz_mode:
|
|
dsz.script.CheckStop()
|
|
self.display(keywords, exit)
|
|
result = self.__menu_input(prompt, default, maxvalue)
|
|
if result:
|
|
self.__callback(self.__display[result][0], self.__display[result][2])
|
|
else:
|
|
self.display(keywords, exit)
|
|
result = self.__menu_input(prompt, default, maxvalue)
|
|
if result:
|
|
self.__callback(self.__display[result][0], self.__display[result][2])
|
|
exit_state = {'selection': result, 'option': (self.__display[result][1]['text'] if (result != 0) else exit), 'option_state': (self.__display[result][1]['state'] if (result != 0) else None), 'all_states': self.all_states()}
|
|
return exit_state
|
|
|
|
def all_states(self):
|
|
all = {}
|
|
for s in self.__section_order:
|
|
all[s] = {}
|
|
for i in self.__section_content[s]:
|
|
if i['state']:
|
|
all[s][i['text']] = i['state']
|
|
if (not all[s]):
|
|
del all[s]
|
|
return all
|
|
|
|
def display(self, keywords=[], exit=EXIT):
|
|
print(self.build_menu(keywords, exit))
|
|
|
|
def build_menu(self, keywords=[], exit=EXIT):
|
|
assert (type(keywords) is list)
|
|
assert ((type(exit) is str) or (type(exit) is unicode))
|
|
if (not self.__needs_update):
|
|
return self.__output
|
|
self.__output = '\n'
|
|
if self.__heading:
|
|
for i in self.__heading:
|
|
self.__output += (i + '\n')
|
|
self.__output += '\n'
|
|
lpad = str((int(math.log10((sum([len(i) for i in self.__section_content]) | 1))) + 3))
|
|
self.__output += ((('%' + lpad) + 'd) %s\n') % (0, exit))
|
|
self.__display = [None]
|
|
for s in self.__section_order:
|
|
if (s == ''):
|
|
self.__output += '\n'
|
|
else:
|
|
self.__output += ('\n %s\n' % s)
|
|
longest = 0
|
|
for i in self.__section_content[s]:
|
|
if i['state']:
|
|
if (len(i['text']) > longest):
|
|
longest = len(i['text'])
|
|
index = 0
|
|
for i in self.__section_content[s]:
|
|
if ((not keywords) or (not i['keywords']) or (True in [(k in keywords) for k in i['keywords']])):
|
|
self.__output += ((('%' + lpad) + 'd) %s') % (len(self.__display), i['text']))
|
|
self.__display.append([s, i, index])
|
|
if i['state']:
|
|
self.__output += (' ' * (longest - len(i['text'])))
|
|
if i['hex']:
|
|
hexed = hex(i['state'])
|
|
if (hexed[(-1)] == 'L'):
|
|
hexed = hexed[0:(-1)]
|
|
self.__output += (': %s' % hexed)
|
|
else:
|
|
self.__output += (': %s' % i['state'])
|
|
self.__output += '\n'
|
|
index += 1
|
|
self.__needs_update = False
|
|
return self.__output
|
|
|
|
def __callback(self, section, index):
|
|
content = self.__section_content[section][index]
|
|
if (content['callback'] is not None):
|
|
self.__current = content
|
|
self.__current_index = index
|
|
self.__current_section = section
|
|
if (content['cbparam'] is None):
|
|
content['callback']()
|
|
else:
|
|
content['callback'](**content['cbparam'])
|
|
self.__current = None
|
|
self.__current_index = None
|
|
self.__current_section = None
|
|
|
|
def __menu_input(self, prompt, default, maxvalue):
|
|
result = (-1)
|
|
while ((result < 0) or (result > maxvalue)):
|
|
result = self.__int_input(prompt, default)
|
|
return result
|
|
|
|
def __raw_input(self, prompt, default):
|
|
newprompt = (prompt if (prompt and (prompt[(-1)] in [':', '?'])) else (prompt + ':'))
|
|
if _dsz_mode:
|
|
return dsz.ui.GetString(utf8(newprompt), ('' if (default is None) else utf8(default)))
|
|
else:
|
|
value = raw_input(((newprompt + (' [%s] ' % default)) if (default is not None) else newprompt))
|
|
if ((value == '') and (default is not None)):
|
|
return default
|
|
return value
|
|
|
|
def __int_input(self, prompt, default):
|
|
if _dsz_mode:
|
|
newprompt = (prompt if (prompt and (prompt[(-1)] in [':', '?'])) else (prompt + ':'))
|
|
return dsz.ui.GetInt(utf8(newprompt), ('' if (default is None) else str(default)))
|
|
else:
|
|
while True:
|
|
value = self.__raw_input(prompt, default)
|
|
if (type(value) is int):
|
|
return value
|
|
try:
|
|
if ((len(value) > 2) and (value[0:2].lower() == '0x')):
|
|
return int(value, 16)
|
|
else:
|
|
return int(value)
|
|
except ValueError:
|
|
pass
|
|
if _util_ip_mode:
|
|
|
|
def callback_ip(self, default=None):
|
|
valid = False
|
|
result = None
|
|
while (not valid):
|
|
result = self.__raw_input(('New IP address for "%s"' % self.current['text']), (self.current['state'] if (default is None) else default))
|
|
valid = util.ip.validate(result)
|
|
if (not valid):
|
|
if _dsz_mode:
|
|
dsz.ui.Echo('Invalid IP address.', dsz.ERROR)
|
|
else:
|
|
print('Invalid IPv4 or IPv6 address.')
|
|
self.set_current_state(result)
|
|
|
|
def add_ip_option(self, option, section='', keywords=[], ip=None, default=None):
|
|
assert ((ip is None) or util.ip.validate(ip)), 'ip must be a valid IPv4 or IPv6 address, or None'
|
|
self.add_option(option=option, section=section, state=ip, keywords=keywords, callback=self.callback_ip, default=default)
|
|
|
|
def callback_ipv4(self, default=None):
|
|
valid = False
|
|
result = None
|
|
while (not valid):
|
|
result = self.__raw_input(('New IPv4 address for "%s"' % self.current['text']), (self.current['state'] if (default is None) else default))
|
|
valid = util.ip.validate_ipv4(result)
|
|
if (not valid):
|
|
if _dsz_mode:
|
|
dsz.ui.Echo('Invalid IPv4 address.', dsz.ERROR)
|
|
else:
|
|
print('Invalid IP address.')
|
|
self.set_current_state(result)
|
|
|
|
def add_ipv4_option(self, option, section='', keywords=[], ip=None, default=None):
|
|
assert ((ip is None) or util.ip.validate_ipv4(ip)), 'ip must be a valid IPv4, or None'
|
|
self.add_option(option=option, section=section, state=(ip if ip else '127.0.0.1'), keywords=keywords, callback=self.callback_ipv4, default=default)
|
|
|
|
def callback_ipv6(self, default=None):
|
|
valid = False
|
|
result = None
|
|
while (not valid):
|
|
result = self.__raw_input(('New IPv6 address for "%s"' % self.current['text']), (self.current['state'] if (default is None) else default))
|
|
valid = util.ip.validate_ipv6(result)
|
|
if (not valid):
|
|
if _dsz_mode:
|
|
dsz.ui.Echo('Invalid IPv6 address.', dsz.ERROR)
|
|
else:
|
|
print('Invalid IP address.')
|
|
self.set_current_state(result)
|
|
|
|
def add_ipv6_option(self, option, section='', keywords=[], ip=None, default=None):
|
|
assert ((ip is None) or util.ip.validate_ipv6(ip)), 'ip must be a valid IPv6, or None'
|
|
self.add_option(option=option, section=section, state=(ip if ip else '::1'), keywords=keywords, callback=self.callback_ipv6, default=default)
|
|
|
|
def callback_frz(self, default=None):
|
|
valid = False
|
|
result = None
|
|
while (not valid):
|
|
result = self.__raw_input(('New FRZ address for "%s"' % self.current['text']), (self.current['state'] if (default is None) else default))
|
|
valid = (result and (result[0] == 'z') and util.ip.validate_ipv4(result[1:]))
|
|
if (not valid):
|
|
if _dsz_mode:
|
|
dsz.ui.Echo('Invalid IPv4 address.', dsz.ERROR)
|
|
else:
|
|
print('Invalid FRZ address.')
|
|
self.set_current_state(result)
|
|
|
|
def add_frz_option(self, option, section='', keywords=[], frz=None, default=None):
|
|
assert ((frz is None) or ((frz[0] == 'z') and util.ip.validate_ipv4(frz[1:]))), 'frz must be a valid FRZ address, or None'
|
|
self.add_option(option=option, section=section, state=(frz if frz else 'z0.0.0.1'), keywords=keywords, callback=self.callback_frz, default=default)
|
|
if (__name__ == '__main__'):
|
|
test = Menu()
|
|
test.add_option('Exit and do stuff')
|
|
test.add_str_option('Log', section='Configuration', state='C:\\Windows\\Temp\\log.log')
|
|
test.add_hex_option('ID', section='Configuration', state=268444485)
|
|
test.add_hex_option('ID', section='Configuration', state=17592454488901L)
|
|
test.add_int_option('Loops', section='Advanced', state=((((((((3 * 3) * 3) * 3) * 3) * 3) * 3) * 3) * 3))
|
|
if _util_ip_mode:
|
|
test.add_ip_option('IP', section='Configuration', ip='1.2.3.4')
|
|
test.add_ipv4_option('IPv4', section='Configuration', ip='9.6.3.0')
|
|
test.add_ipv6_option('IPv6', section='Configuration', ip='1234:abcd::5%9')
|
|
test.add_frz_option('FRZ', section='ADVANCED', frz='z7.7.7.7')
|
|
print(test.execute(exiton=[1], default=1)) |