parent
03cfc4c80a
commit
876c8b53cc
5 changed files with 564 additions and 8 deletions
@ -0,0 +1,36 @@ |
|||||||
|
class COLORS: |
||||||
|
def __init__(self): |
||||||
|
self.HEADER = '\033[95m' |
||||||
|
self.OKBLUE = '\033[94m' |
||||||
|
self.CBLUE = '\33[44m' |
||||||
|
self.BOLD = '\033[1m' |
||||||
|
self.CITALIC = '\33[3m' |
||||||
|
self.OKGREEN = '\033[92m' |
||||||
|
self.CWHITE = '\33[37m' |
||||||
|
self.ENDC = '\033[0m' + self.CWHITE |
||||||
|
self.UNDERLINE = '\033[4m' |
||||||
|
self.PINK = '\33[38;5;207m' |
||||||
|
self.PRETTY_YELLOW = self.BASE(220) |
||||||
|
|
||||||
|
self.RED = '\033[91m' |
||||||
|
self.PURPLE_BG = '\33[45m' |
||||||
|
self.YELLOW = '\033[93m' |
||||||
|
self.BLUE_GREEN = self.BASE(85) |
||||||
|
|
||||||
|
self.FAIL = self.RED |
||||||
|
# self.INFO = self.PURPLE_BG |
||||||
|
self.INFO = self.BASE(207) |
||||||
|
self.SUCCESS = self.OKGREEN |
||||||
|
self.PROMPT = self.YELLOW |
||||||
|
self.DBLUE = '\033[36m' |
||||||
|
self.CYAN = self.BASE(39) |
||||||
|
self.WARNING = '\033[33m' |
||||||
|
|
||||||
|
def BASE(self, col): # seems to support more colors |
||||||
|
return '\33[38;5;{}m'.format(col) |
||||||
|
|
||||||
|
def BASEBG(self, col): # seems to support more colors |
||||||
|
return '\33[48;5;{}m'.format(col) |
||||||
|
|
||||||
|
|
||||||
|
COLORS = COLORS() |
@ -0,0 +1,193 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import os |
||||||
|
import json |
||||||
|
from atomicwrites import atomic_write |
||||||
|
from common.colors import COLORS |
||||||
|
from common.basedir import BASEDIR |
||||||
|
from selfdrive.hardware import TICI |
||||||
|
try: |
||||||
|
from common.realtime import sec_since_boot |
||||||
|
except ImportError: |
||||||
|
import time |
||||||
|
sec_since_boot = time.time |
||||||
|
|
||||||
|
warning = lambda msg: print('{}opParams WARNING: {}{}'.format(COLORS.WARNING, msg, COLORS.ENDC)) |
||||||
|
error = lambda msg: print('{}opParams ERROR: {}{}'.format(COLORS.FAIL, msg, COLORS.ENDC)) |
||||||
|
|
||||||
|
NUMBER = [float, int] # value types |
||||||
|
NONE_OR_NUMBER = [type(None), float, int] |
||||||
|
|
||||||
|
BASEDIR = os.path.dirname(BASEDIR) |
||||||
|
PARAMS_DIR = os.path.join(BASEDIR, 'community', 'params') |
||||||
|
IMPORTED_PATH = os.path.join(PARAMS_DIR, '.imported') |
||||||
|
OLD_PARAMS_FILE = os.path.join(BASEDIR, 'op_params.json') |
||||||
|
|
||||||
|
|
||||||
|
class Param: |
||||||
|
def __init__(self, default, allowed_types=[], description=None, *, static=False, live=False, hidden=False): # pylint: disable=dangerous-default-value |
||||||
|
self.default_value = default # value first saved and returned if actual value isn't a valid type |
||||||
|
if not isinstance(allowed_types, list): |
||||||
|
allowed_types = [allowed_types] |
||||||
|
self.allowed_types = allowed_types # allowed python value types for opEdit |
||||||
|
self.description = description # description to be shown in opEdit |
||||||
|
self.hidden = hidden # hide this param to user in opEdit |
||||||
|
self.live = live # show under the live menu in opEdit |
||||||
|
self.static = static # use cached value, never reads to update |
||||||
|
self._create_attrs() |
||||||
|
|
||||||
|
def is_valid(self, value): |
||||||
|
if not self.has_allowed_types: # always valid if no allowed types, otherwise checks to make sure |
||||||
|
return True |
||||||
|
return type(value) in self.allowed_types |
||||||
|
|
||||||
|
def _create_attrs(self): # Create attributes and check Param is valid |
||||||
|
self.has_allowed_types = isinstance(self.allowed_types, list) and len(self.allowed_types) > 0 |
||||||
|
self.has_description = self.description is not None |
||||||
|
self.is_list = list in self.allowed_types |
||||||
|
self.read_frequency = None if self.static else (1 if self.live else 10) # how often to read param file (sec) |
||||||
|
self.last_read = -1 |
||||||
|
if self.has_allowed_types: |
||||||
|
assert type(self.default_value) in self.allowed_types, 'Default value type must be in specified allowed_types!' |
||||||
|
if self.is_list: |
||||||
|
self.allowed_types.remove(list) |
||||||
|
|
||||||
|
|
||||||
|
def _read_param(key): # Returns None, False if a json error occurs |
||||||
|
try: |
||||||
|
with open(os.path.join(PARAMS_DIR, key), 'r') as f: |
||||||
|
value = json.loads(f.read()) |
||||||
|
return value, True |
||||||
|
except json.decoder.JSONDecodeError: |
||||||
|
return None, False |
||||||
|
|
||||||
|
|
||||||
|
def _write_param(key, value): |
||||||
|
param_path = os.path.join(PARAMS_DIR, key) |
||||||
|
with atomic_write(param_path, overwrite=True) as f: |
||||||
|
f.write(json.dumps(value)) |
||||||
|
|
||||||
|
|
||||||
|
def _import_params(): |
||||||
|
if os.path.exists(OLD_PARAMS_FILE) and not os.path.exists(IMPORTED_PATH): # if opParams needs to import from old params file |
||||||
|
try: |
||||||
|
with open(OLD_PARAMS_FILE, 'r') as f: |
||||||
|
old_params = json.loads(f.read()) |
||||||
|
for key in old_params: |
||||||
|
_write_param(key, old_params[key]) |
||||||
|
open(IMPORTED_PATH, 'w').close() |
||||||
|
except: # pylint: disable=bare-except |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
class opParams: |
||||||
|
def __init__(self): |
||||||
|
""" |
||||||
|
To add your own parameter to opParams in your fork, simply add a new entry in self.fork_params, instancing a new Param class with at minimum a default value. |
||||||
|
The allowed_types and description args are not required but highly recommended to help users edit their parameters with opEdit safely. |
||||||
|
- The description value will be shown to users when they use opEdit to change the value of the parameter. |
||||||
|
- The allowed_types arg is used to restrict what kinds of values can be entered with opEdit so that users can't crash openpilot with unintended behavior. |
||||||
|
(setting a param intended to be a number with a boolean, or viceversa for example) |
||||||
|
Limiting the range of floats or integers is still recommended when `.get`ting the parameter. |
||||||
|
When a None value is allowed, use `type(None)` instead of None, as opEdit checks the type against the values in the arg with `isinstance()`. |
||||||
|
- If you want your param to update within a second, specify live=True. If your param is designed to be read once, specify static=True. |
||||||
|
Specifying neither will have the param update every 10 seconds if constantly .get() |
||||||
|
If the param is not static, call the .get() function on it in the update function of the file you're reading from to use live updating |
||||||
|
|
||||||
|
Here's an example of a good fork_param entry: |
||||||
|
self.fork_params = {'camera_offset': Param(0.06, allowed_types=NUMBER), live=True} # NUMBER allows both floats and ints |
||||||
|
""" |
||||||
|
|
||||||
|
self.fork_params = { |
||||||
|
'SETME_X1': Param(1, NUMBER, 'Always 1', live=True), |
||||||
|
'SETME_X3': Param(1, NUMBER, 'Sometimes 3, mostly 1?', live=True), |
||||||
|
'PERCENTAGE': Param(100, NUMBER, '100 when not touching wheel, 0 when touching wheel', live=True), |
||||||
|
'SETME_X64': Param(100, NUMBER, 'Unsure', live=True), |
||||||
|
'ANGLE': Param(0, NUMBER, 'Rate limit? Lower is better?', live=True), |
||||||
|
'LTA_REQUEST_TYPE': Param(1, NUMBER, '1: LTA, 3: LTA for lane keeping', live=True), |
||||||
|
'BIT': Param(0, NUMBER, '1: LTA, 2: LTA for lane keeping', live=True), |
||||||
|
} |
||||||
|
|
||||||
|
self._to_delete = [] # a list of unused params you want to delete from users' params file |
||||||
|
self._to_reset = [] # a list of params you want reset to their default values |
||||||
|
self._run_init() # restores, reads, and updates params |
||||||
|
|
||||||
|
def _run_init(self): # does first time initializing of default params |
||||||
|
# Two required parameters for opEdit |
||||||
|
self.fork_params['username'] = Param(None, [type(None), str, bool], 'Your identifier provided with any crash logs sent to Sentry.\nHelps the developer reach out to you if anything goes wrong') |
||||||
|
self.fork_params['op_edit_live_mode'] = Param(False, bool, 'This parameter controls which mode opEdit starts in', hidden=True) |
||||||
|
|
||||||
|
self.params = self._load_params(can_import=True) |
||||||
|
self._add_default_params() # adds missing params and resets values with invalid types to self.params |
||||||
|
self._delete_and_reset() # removes old params |
||||||
|
|
||||||
|
def get(self, key=None, *, force_update=False): # key=None returns dict of all params |
||||||
|
if key is None: |
||||||
|
return self._get_all_params(to_update=force_update) |
||||||
|
self._check_key_exists(key, 'get') |
||||||
|
param_info = self.fork_params[key] |
||||||
|
rate = param_info.read_frequency # will be None if param is static, so check below |
||||||
|
|
||||||
|
if (not param_info.static and sec_since_boot() - self.fork_params[key].last_read >= rate) or force_update: |
||||||
|
value, success = _read_param(key) |
||||||
|
self.fork_params[key].last_read = sec_since_boot() |
||||||
|
if not success: # in case of read error, use default and overwrite param |
||||||
|
value = param_info.default_value |
||||||
|
_write_param(key, value) |
||||||
|
self.params[key] = value |
||||||
|
|
||||||
|
if param_info.is_valid(value := self.params[key]): |
||||||
|
return value # all good, returning user's value |
||||||
|
print(warning('User\'s value type is not valid! Returning default')) # somehow... it should always be valid |
||||||
|
return param_info.default_value # return default value because user's value of key is not in allowed_types to avoid crashing openpilot |
||||||
|
|
||||||
|
def put(self, key, value): |
||||||
|
self._check_key_exists(key, 'put') |
||||||
|
if not self.fork_params[key].is_valid(value): |
||||||
|
raise Exception('opParams: Tried to put a value of invalid type!') |
||||||
|
self.params.update({key: value}) |
||||||
|
_write_param(key, value) |
||||||
|
|
||||||
|
def _load_params(self, can_import=False): |
||||||
|
if not os.path.exists(PARAMS_DIR): |
||||||
|
os.makedirs(PARAMS_DIR) |
||||||
|
if can_import: |
||||||
|
_import_params() # just imports old params. below we read them in |
||||||
|
|
||||||
|
params = {} |
||||||
|
for key in os.listdir(PARAMS_DIR): # PARAMS_DIR is guaranteed to exist |
||||||
|
if key.startswith('.') or key not in self.fork_params: |
||||||
|
continue |
||||||
|
value, success = _read_param(key) |
||||||
|
if not success: |
||||||
|
value = self.fork_params[key].default_value |
||||||
|
_write_param(key, value) |
||||||
|
params[key] = value |
||||||
|
return params |
||||||
|
|
||||||
|
def _get_all_params(self, to_update=False): |
||||||
|
if to_update: |
||||||
|
self.params = self._load_params() |
||||||
|
return {k: self.params[k] for k, p in self.fork_params.items() if k in self.params and not p.hidden} |
||||||
|
|
||||||
|
def _check_key_exists(self, key, met): |
||||||
|
if key not in self.fork_params: |
||||||
|
raise Exception('opParams: Tried to {} an unknown parameter! Key not in fork_params: {}'.format(met, key)) |
||||||
|
|
||||||
|
def _add_default_params(self): |
||||||
|
for key, param in self.fork_params.items(): |
||||||
|
if key not in self.params: |
||||||
|
self.params[key] = param.default_value |
||||||
|
_write_param(key, self.params[key]) |
||||||
|
elif not param.is_valid(self.params[key]): |
||||||
|
print(warning('Value type of user\'s {} param not in allowed types, replacing with default!'.format(key))) |
||||||
|
self.params[key] = param.default_value |
||||||
|
_write_param(key, self.params[key]) |
||||||
|
|
||||||
|
def _delete_and_reset(self): |
||||||
|
for key in list(self.params): |
||||||
|
if key in self._to_delete: |
||||||
|
del self.params[key] |
||||||
|
os.remove(os.path.join(PARAMS_DIR, key)) |
||||||
|
elif key in self._to_reset and key in self.fork_params: |
||||||
|
self.params[key] = self.fork_params[key].default_value |
||||||
|
_write_param(key, self.params[key]) |
@ -0,0 +1,325 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import time |
||||||
|
from common.op_params import opParams |
||||||
|
import ast |
||||||
|
import difflib |
||||||
|
from common.colors import COLORS |
||||||
|
|
||||||
|
|
||||||
|
class opEdit: # use by running `python /data/openpilot/op_edit.py` |
||||||
|
def __init__(self): |
||||||
|
self.op_params = opParams() |
||||||
|
self.params = None |
||||||
|
self.sleep_time = 0.5 |
||||||
|
self.live_tuning = self.op_params.get('op_edit_live_mode') |
||||||
|
self.username = self.op_params.get('username') |
||||||
|
self.type_colors = {int: COLORS.BASE(179), float: COLORS.BASE(179), |
||||||
|
bool: {False: COLORS.RED, True: COLORS.OKGREEN}, |
||||||
|
type(None): COLORS.BASE(177), |
||||||
|
str: COLORS.BASE(77)} |
||||||
|
|
||||||
|
self.last_choice = None |
||||||
|
|
||||||
|
self.run_init() |
||||||
|
|
||||||
|
def run_init(self): |
||||||
|
if self.username is None: |
||||||
|
self.success('\nWelcome to the {}opParams{} command line editor!'.format(COLORS.CYAN, COLORS.SUCCESS), sleep_time=0) |
||||||
|
self.prompt('Would you like to add your Discord username for easier crash debugging for the fork owner?') |
||||||
|
self.prompt('Your username is only used for reaching out if a crash occurs.') |
||||||
|
|
||||||
|
username_choice = self.input_with_options(['Y', 'N', 'don\'t ask again'], default='n')[0] |
||||||
|
if username_choice == 0: |
||||||
|
self.prompt('Enter a unique identifer/Discord username:') |
||||||
|
username = '' |
||||||
|
while username == '': |
||||||
|
username = input('>> ').strip() |
||||||
|
self.op_params.put('username', username) |
||||||
|
self.username = username |
||||||
|
self.success('Thanks! Saved your username\n' |
||||||
|
'Edit the \'username\' parameter at any time to update', sleep_time=1.5) |
||||||
|
elif username_choice == 2: |
||||||
|
self.op_params.put('username', False) |
||||||
|
self.info('Got it, bringing you into opEdit\n' |
||||||
|
'Edit the \'username\' parameter at any time to update', sleep_time=1.0) |
||||||
|
else: |
||||||
|
self.success('\nWelcome to the {}opParams{} command line editor, {}!'.format(COLORS.CYAN, COLORS.SUCCESS, self.username), sleep_time=0) |
||||||
|
|
||||||
|
self.run_loop() |
||||||
|
|
||||||
|
def run_loop(self): |
||||||
|
while True: |
||||||
|
if not self.live_tuning: |
||||||
|
self.info('Here are all your parameters:', sleep_time=0) |
||||||
|
self.info('(non-static params update while driving)', end='\n', sleep_time=0) |
||||||
|
else: |
||||||
|
self.info('Here are your live parameters:', sleep_time=0) |
||||||
|
self.info('(changes take effect within a second)', end='\n', sleep_time=0) |
||||||
|
self.params = self.op_params.get(force_update=True) |
||||||
|
if self.live_tuning: # only display live tunable params |
||||||
|
self.params = {k: v for k, v in self.params.items() if self.op_params.fork_params[k].live} |
||||||
|
|
||||||
|
values_list = [] |
||||||
|
for k, v in self.params.items(): |
||||||
|
if len(str(v)) < 20: |
||||||
|
v = self.color_from_type(v) |
||||||
|
else: |
||||||
|
v = '{} ... {}'.format(str(v)[:30], str(v)[-15:]) |
||||||
|
values_list.append(v) |
||||||
|
|
||||||
|
static = [COLORS.INFO + '(static)' + COLORS.ENDC if self.op_params.fork_params[k].static else '' for k in self.params] |
||||||
|
|
||||||
|
to_print = [] |
||||||
|
blue_gradient = [33, 39, 45, 51, 87] |
||||||
|
for idx, param in enumerate(self.params): |
||||||
|
line = '{}. {}: {} {}'.format(idx + 1, param, values_list[idx], static[idx]) |
||||||
|
if idx == self.last_choice and self.last_choice is not None: |
||||||
|
line = COLORS.OKGREEN + line |
||||||
|
else: |
||||||
|
_color = blue_gradient[min(round(idx / len(self.params) * len(blue_gradient)), len(blue_gradient) - 1)] |
||||||
|
line = COLORS.BASE(_color) + line |
||||||
|
to_print.append(line) |
||||||
|
|
||||||
|
extras = {'l': ('Toggle live params', COLORS.WARNING), |
||||||
|
'e': ('Exit opEdit', COLORS.PINK)} |
||||||
|
|
||||||
|
to_print += ['---'] + ['{}. {}'.format(ext_col + e, ext_txt + COLORS.ENDC) for e, (ext_txt, ext_col) in extras.items()] |
||||||
|
print('\n'.join(to_print)) |
||||||
|
self.prompt('\nChoose a parameter to edit (by index or name):') |
||||||
|
|
||||||
|
choice = input('>> ').strip().lower() |
||||||
|
parsed, choice = self.parse_choice(choice, len(to_print) - len(extras)) |
||||||
|
if parsed == 'continue': |
||||||
|
continue |
||||||
|
elif parsed == 'change': |
||||||
|
self.last_choice = choice |
||||||
|
self.change_parameter(choice) |
||||||
|
elif parsed == 'live': |
||||||
|
self.last_choice = None |
||||||
|
self.live_tuning = not self.live_tuning |
||||||
|
self.op_params.put('op_edit_live_mode', self.live_tuning) # for next opEdit startup |
||||||
|
elif parsed == 'exit': |
||||||
|
return |
||||||
|
|
||||||
|
def parse_choice(self, choice, opt_len): |
||||||
|
if choice.isdigit(): |
||||||
|
choice = int(choice) |
||||||
|
choice -= 1 |
||||||
|
if choice not in range(opt_len): # number of options to choose from |
||||||
|
self.error('Not in range!') |
||||||
|
return 'continue', choice |
||||||
|
return 'change', choice |
||||||
|
|
||||||
|
if choice in ['l', 'live']: # live tuning mode |
||||||
|
return 'live', choice |
||||||
|
elif choice in ['exit', 'e', '']: |
||||||
|
self.error('Exiting opEdit!', sleep_time=0) |
||||||
|
return 'exit', choice |
||||||
|
else: # find most similar param to user's input |
||||||
|
param_sims = [(idx, self.str_sim(choice, param.lower())) for idx, param in enumerate(self.params)] |
||||||
|
param_sims = [param for param in param_sims if param[1] > 0.33] |
||||||
|
if len(param_sims) > 0: |
||||||
|
chosen_param = sorted(param_sims, key=lambda param: param[1], reverse=True)[0] |
||||||
|
return 'change', chosen_param[0] # return idx |
||||||
|
|
||||||
|
self.error('Invalid choice!') |
||||||
|
return 'continue', choice |
||||||
|
|
||||||
|
def str_sim(self, a, b): |
||||||
|
return difflib.SequenceMatcher(a=a, b=b).ratio() |
||||||
|
|
||||||
|
def change_parameter(self, choice): |
||||||
|
while True: |
||||||
|
chosen_key = list(self.params)[choice] |
||||||
|
param_info = self.op_params.fork_params[chosen_key] |
||||||
|
|
||||||
|
old_value = self.params[chosen_key] |
||||||
|
if not param_info.static: |
||||||
|
self.info2('Chosen parameter: {}{} (live!)'.format(chosen_key, COLORS.BASE(207)), sleep_time=0) |
||||||
|
else: |
||||||
|
self.info2('Chosen parameter: {}{} (static)'.format(chosen_key, COLORS.BASE(207)), sleep_time=0) |
||||||
|
|
||||||
|
to_print = [] |
||||||
|
if param_info.has_description: |
||||||
|
to_print.append(COLORS.OKGREEN + '>> Description: {}'.format(param_info.description.replace('\n', '\n > ')) + COLORS.ENDC) |
||||||
|
if param_info.static: |
||||||
|
to_print.append(COLORS.WARNING + '>> A reboot is required for changes to this parameter!' + COLORS.ENDC) |
||||||
|
if not param_info.static and not param_info.live: |
||||||
|
to_print.append(COLORS.WARNING + '>> Changes take effect within 10 seconds for this parameter!' + COLORS.ENDC) |
||||||
|
if param_info.has_allowed_types: |
||||||
|
to_print.append(COLORS.RED + '>> Allowed types: {}'.format(', '.join([at.__name__ for at in param_info.allowed_types])) + COLORS.ENDC) |
||||||
|
to_print.append(COLORS.WARNING + '>> Default value: {}'.format(self.color_from_type(param_info.default_value)) + COLORS.ENDC) |
||||||
|
|
||||||
|
if to_print: |
||||||
|
print('\n{}\n'.format('\n'.join(to_print))) |
||||||
|
|
||||||
|
if param_info.is_list: |
||||||
|
self.change_param_list(old_value, param_info, chosen_key) # TODO: need to merge the code in this function with the below to reduce redundant code |
||||||
|
return |
||||||
|
|
||||||
|
self.info('Current value: {}{} (type: {})'.format(self.color_from_type(old_value), COLORS.INFO, type(old_value).__name__), sleep_time=0) |
||||||
|
|
||||||
|
while True: |
||||||
|
self.prompt('\nEnter your new value (enter to exit):') |
||||||
|
new_value = input('>> ').strip() |
||||||
|
if new_value == '': |
||||||
|
self.info('Exiting this parameter...\n') |
||||||
|
return |
||||||
|
|
||||||
|
new_value = self.str_eval(new_value) |
||||||
|
if not param_info.is_valid(new_value): |
||||||
|
self.error('The type of data you entered ({}) is not allowed with this parameter!'.format(type(new_value).__name__)) |
||||||
|
continue |
||||||
|
|
||||||
|
if not param_info.static: # stay in live tuning interface |
||||||
|
self.op_params.put(chosen_key, new_value) |
||||||
|
self.success('Saved {} with value: {}{}! (type: {})'.format(chosen_key, self.color_from_type(new_value), COLORS.SUCCESS, type(new_value).__name__)) |
||||||
|
else: # else ask to save and break |
||||||
|
self.warning('\nOld value: {}{} (type: {})'.format(self.color_from_type(old_value), COLORS.WARNING, type(old_value).__name__)) |
||||||
|
self.success('New value: {}{} (type: {})'.format(self.color_from_type(new_value), COLORS.OKGREEN, type(new_value).__name__), sleep_time=0) |
||||||
|
self.prompt('\nDo you want to save this?') |
||||||
|
if self.input_with_options(['Y', 'N'], 'N')[0] == 0: |
||||||
|
self.op_params.put(chosen_key, new_value) |
||||||
|
self.success('Saved!') |
||||||
|
else: |
||||||
|
self.info('Not saved!') |
||||||
|
return |
||||||
|
|
||||||
|
def change_param_list(self, old_value, param_info, chosen_key): |
||||||
|
while True: |
||||||
|
self.info('Current value: {} (type: {})'.format(old_value, type(old_value).__name__), sleep_time=0) |
||||||
|
self.prompt('\nEnter index to edit (0 to {}):'.format(len(old_value) - 1)) |
||||||
|
choice_idx = self.str_eval(input('>> ')) |
||||||
|
if choice_idx == '': |
||||||
|
self.info('Exiting this parameter...') |
||||||
|
return |
||||||
|
|
||||||
|
if not isinstance(choice_idx, int) or choice_idx not in range(len(old_value)): |
||||||
|
self.error('Must be an integar within list range!') |
||||||
|
continue |
||||||
|
|
||||||
|
while True: |
||||||
|
self.info('Chosen index: {}'.format(choice_idx), sleep_time=0) |
||||||
|
self.info('Value: {} (type: {})'.format(old_value[choice_idx], type(old_value[choice_idx]).__name__), sleep_time=0) |
||||||
|
self.prompt('\nEnter your new value:') |
||||||
|
new_value = input('>> ').strip() |
||||||
|
if new_value == '': |
||||||
|
self.info('Exiting this list item...') |
||||||
|
break |
||||||
|
|
||||||
|
new_value = self.str_eval(new_value) |
||||||
|
if not param_info.is_valid(new_value): |
||||||
|
self.error('The type of data you entered ({}) is not allowed with this parameter!'.format(type(new_value).__name__)) |
||||||
|
continue |
||||||
|
|
||||||
|
old_value[choice_idx] = new_value |
||||||
|
|
||||||
|
self.op_params.put(chosen_key, old_value) |
||||||
|
self.success('Saved {} with value: {}{}! (type: {})'.format(chosen_key, self.color_from_type(new_value), COLORS.SUCCESS, type(new_value).__name__), end='\n') |
||||||
|
break |
||||||
|
|
||||||
|
def color_from_type(self, v): |
||||||
|
v_color = '' |
||||||
|
if type(v) in self.type_colors: |
||||||
|
v_color = self.type_colors[type(v)] |
||||||
|
if isinstance(v, bool): |
||||||
|
v_color = v_color[v] |
||||||
|
v = '{}{}{}'.format(v_color, v, COLORS.ENDC) |
||||||
|
return v |
||||||
|
|
||||||
|
def cyan(self, msg, end=''): |
||||||
|
msg = self.str_color(msg, style='cyan') |
||||||
|
# print(msg, flush=True, end='\n' + end) |
||||||
|
return msg |
||||||
|
|
||||||
|
def prompt(self, msg, end=''): |
||||||
|
msg = self.str_color(msg, style='prompt') |
||||||
|
print(msg, flush=True, end='\n' + end) |
||||||
|
|
||||||
|
def warning(self, msg, end=''): |
||||||
|
msg = self.str_color(msg, style='warning') |
||||||
|
print(msg, flush=True, end='\n' + end) |
||||||
|
|
||||||
|
def info(self, msg, sleep_time=None, end=''): |
||||||
|
if sleep_time is None: |
||||||
|
sleep_time = self.sleep_time |
||||||
|
msg = self.str_color(msg, style='info') |
||||||
|
|
||||||
|
print(msg, flush=True, end='\n' + end) |
||||||
|
time.sleep(sleep_time) |
||||||
|
|
||||||
|
def info2(self, msg, sleep_time=None, end=''): |
||||||
|
if sleep_time is None: |
||||||
|
sleep_time = self.sleep_time |
||||||
|
msg = self.str_color(msg, style=86) |
||||||
|
|
||||||
|
print(msg, flush=True, end='\n' + end) |
||||||
|
time.sleep(sleep_time) |
||||||
|
|
||||||
|
def error(self, msg, sleep_time=None, end='', surround=True): |
||||||
|
if sleep_time is None: |
||||||
|
sleep_time = self.sleep_time |
||||||
|
msg = self.str_color(msg, style='fail', surround=surround) |
||||||
|
|
||||||
|
print(msg, flush=True, end='\n' + end) |
||||||
|
time.sleep(sleep_time) |
||||||
|
|
||||||
|
def success(self, msg, sleep_time=None, end=''): |
||||||
|
if sleep_time is None: |
||||||
|
sleep_time = self.sleep_time |
||||||
|
msg = self.str_color(msg, style='success') |
||||||
|
|
||||||
|
print(msg, flush=True, end='\n' + end) |
||||||
|
time.sleep(sleep_time) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def str_color(msg, style, surround=False): |
||||||
|
if style == 'success': |
||||||
|
style = COLORS.SUCCESS |
||||||
|
elif style == 'fail': |
||||||
|
style = COLORS.FAIL |
||||||
|
elif style == 'prompt': |
||||||
|
style = COLORS.PROMPT |
||||||
|
elif style == 'info': |
||||||
|
style = COLORS.INFO |
||||||
|
elif style == 'cyan': |
||||||
|
style = COLORS.CYAN |
||||||
|
elif style == 'warning': |
||||||
|
style = COLORS.WARNING |
||||||
|
elif isinstance(style, int): |
||||||
|
style = COLORS.BASE(style) |
||||||
|
|
||||||
|
if surround: |
||||||
|
msg = '{}--------\n{}\n{}--------{}'.format(style, msg, COLORS.ENDC + style, COLORS.ENDC) |
||||||
|
else: |
||||||
|
msg = '{}{}{}'.format(style, msg, COLORS.ENDC) |
||||||
|
|
||||||
|
return msg |
||||||
|
|
||||||
|
def input_with_options(self, options, default=None): |
||||||
|
""" |
||||||
|
Takes in a list of options and asks user to make a choice. |
||||||
|
The most similar option list index is returned along with the similarity percentage from 0 to 1 |
||||||
|
""" |
||||||
|
user_input = input('[{}]: '.format('/'.join(options))).lower().strip() |
||||||
|
if not user_input: |
||||||
|
return default, 0.0 |
||||||
|
sims = [self.str_sim(i.lower().strip(), user_input) for i in options] |
||||||
|
argmax = sims.index(max(sims)) |
||||||
|
return argmax, sims[argmax] |
||||||
|
|
||||||
|
def str_eval(self, dat): |
||||||
|
dat = dat.strip() |
||||||
|
try: |
||||||
|
dat = ast.literal_eval(dat) |
||||||
|
except: |
||||||
|
if dat.lower() == 'none': |
||||||
|
dat = None |
||||||
|
elif dat.lower() == 'false': |
||||||
|
dat = False |
||||||
|
elif dat.lower() == 'true': # else, assume string |
||||||
|
dat = True |
||||||
|
return dat |
||||||
|
|
||||||
|
|
||||||
|
opEdit() |
Loading…
Reference in new issue