config: add ask_values method that allowing interactive configuration

This commit is contained in:
Benjamin Renard 2021-11-23 12:34:50 +01:00
parent e058d315d4
commit 2e829c4cc3

View file

@ -26,7 +26,7 @@ DEFAULT_ENCODING = 'utf-8'
DEFAULT_CONFIG_DIRPATH = os.path.expanduser('./')
class BaseOption:
class BaseOption: # pylint: disable=too-many-instance-attributes
""" Base configuration option class """
def __init__(self, config, section, name, default=None, comment=None,
@ -201,6 +201,28 @@ class BaseOption:
lines.append('')
return '\n'.join(lines)
def _ask_value(self, prompt=None):
""" Ask to user to enter value of this option and return it """
if self.comment:
print('# ' + self.comment)
default_value = self.get()
if not prompt:
prompt = "%s: " % self.name
if default_value is not None:
prompt += "[%s] " % self.to_config(default_value)
value = input(prompt)
return default_value if value == '' else value
def ask_value(self, set_it=True):
"""
Ask to user to enter value of this option and set or
return it regarding set parameter
"""
value = self._ask_value()
if set_it:
return self.set(value)
return value
class StringOption(BaseOption):
""" String configuration option class """
@ -238,6 +260,25 @@ class BooleanOption(BaseOption):
).lower().replace('_', '-')
)
def _ask_value(self, prompt=None):
""" Ask to user to enter value of this option and return it """
default_value = self.get()
prompt = "%s: " % self.name
if default_value:
prompt += '[Y/n] '
else:
prompt += '[y/N] '
while True:
value = super()._ask_value(prompt).lower()
if value in ['', None]:
return default_value
if value == 'y':
return True
if value == 'n':
return False
print('Invalid answer. Possible values: Y or N (case insensitive)')
class FloatOption(BaseOption):
""" Float configuration option class """
@ -251,6 +292,18 @@ class FloatOption(BaseOption):
def parser_type(self):
return float
def _ask_value(self, prompt=None):
""" Ask to user to enter value of this option and return it """
default_value = self.get()
while True:
value = super()._ask_value()
if value in ['', None]:
return default_value
try:
return float(value)
except ValueError:
print('Invalid answer. Must a numeric value, for instance "12" or "12.5"')
class IntegerOption(BaseOption):
""" Integer configuration option class """
@ -269,6 +322,18 @@ class IntegerOption(BaseOption):
def parser_type(self):
return int
def _ask_value(self, prompt=None):
""" Ask to user to enter value of this option and return it """
default_value = self.get()
while True:
value = super()._ask_value()
if value in ['', None]:
return default_value
try:
return int(value)
except ValueError:
print('Invalid answer. Must a integer value')
class PasswordOption(StringOption):
""" Password configuration option class """
@ -324,15 +389,57 @@ class PasswordOption(StringOption):
return self.keyring_value
return super().to_config(value)
def set(self, value):
def set(self, value, use_keyring=None): # pylint: disable=arguments-differ
""" Set option value to config file """
if super().get() == self.keyring_value:
if (use_keyring is None and super().get() == self.keyring_value) or use_keyring:
keyring.set_password(
self._keyring_service_name, self._keyring_username,
value)
value = self.keyring_value
super().set(value)
def _ask_value(self, prompt=None):
""" Ask to user to enter value of this option and return it """
if self.comment:
print('# ' + self.comment)
default_value = self.get()
if not prompt:
prompt = '%s: ' % self.name
if default_value is not None:
# Hide value only if it differed from default value
if default_value == self.default:
prompt += '[%s] ' % default_value
else:
prompt += '[secret defined, leave to empty to keep it as unchange] '
value = getpass(prompt)
return default_value if value == '' else value
def ask_value(self, set_it=True):
"""
Ask to user to enter value of this option and set or
return it regarding set parameter
"""
value = self._ask_value()
if set_it:
use_keyring = None
default_use_keyring = (super().get() == self.keyring_value)
while use_keyring is None:
prompt = (
'Do you want to use XDG keyring ? [%s] ' %
('Y/n' if default_use_keyring else 'y/N')
)
result = input(prompt).lower()
if result == '':
use_keyring = default_use_keyring
elif result == 'y':
use_keyring = True
elif result == 'n':
use_keyring = False
else:
print('Invalid answer. Possible values: Y or N (case insensitive)')
return self.set(value, use_keyring=use_keyring)
return value
class ConfigSection:
""" Configuration section class """
@ -395,6 +502,34 @@ class ConfigSection:
lines.append(self.options[option].export_to_config())
return '\n'.join(lines)
def ask_values(self, set_it=True):
"""
Ask user to enter value for each configuration option of the section
:param set_it: If True (default), option value will be updated with user input
:return: If set_it is True, return True if valid value for each configuration
option have been retrieved and set. If False, return a dict of configuration
options and their value.
:rtype: bool of dict
"""
if self.comment:
print('# %s' % self.comment)
print('[%s]\n' % self.name)
result = dict()
error = False
for name, option in self.options.items():
option_result = option.ask_value(set_it=set_it)
if set_it:
result[name] = option_result
elif not option_result:
error = True
print()
print()
if set_it:
return not error
return result
class RawWrappedTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
@ -684,6 +819,38 @@ class Config:
for section in self._ordered_section_names:
self.sections[section].add_options_to_parser(parser)
def ask_values(self, set_it=True, execute_callback=False):
"""
Ask user to enter value for each configuration option
:param set_it: If True (default), option value will be updated with user input
:param execute_callback: Sections's loaded callbacks will be finally executed
(only if set_it is True, default: False)
:return: If set_it is True, return True if valid value for each configuration
option have been retrieved and set. If False, return a dict of configuration
section and their options value.
:rtype: bool of dict
"""
# On set it mode, ensure configuration file parser is initialized
if set_it and not self.config_parser:
self.config_parser = ConfigParser()
result = dict()
error = False
for name, section in self.sections.items():
section_result = section.ask_values(set_it=set_it)
if not set_it:
result[name] = section_result
elif not section_result:
error = True
if set_it:
if error:
return False
if execute_callback:
self._loaded()
return True
return result
@property
def config_dir(self):
""" Retrieve configuration directory path """