config: add ask_values method that allowing interactive configuration
This commit is contained in:
parent
e058d315d4
commit
2e829c4cc3
1 changed files with 170 additions and 3 deletions
173
mylib/config.py
173
mylib/config.py
|
@ -26,7 +26,7 @@ DEFAULT_ENCODING = 'utf-8'
|
||||||
DEFAULT_CONFIG_DIRPATH = os.path.expanduser('./')
|
DEFAULT_CONFIG_DIRPATH = os.path.expanduser('./')
|
||||||
|
|
||||||
|
|
||||||
class BaseOption:
|
class BaseOption: # pylint: disable=too-many-instance-attributes
|
||||||
""" Base configuration option class """
|
""" Base configuration option class """
|
||||||
|
|
||||||
def __init__(self, config, section, name, default=None, comment=None,
|
def __init__(self, config, section, name, default=None, comment=None,
|
||||||
|
@ -201,6 +201,28 @@ class BaseOption:
|
||||||
lines.append('')
|
lines.append('')
|
||||||
return '\n'.join(lines)
|
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):
|
class StringOption(BaseOption):
|
||||||
""" String configuration option class """
|
""" String configuration option class """
|
||||||
|
@ -238,6 +260,25 @@ class BooleanOption(BaseOption):
|
||||||
).lower().replace('_', '-')
|
).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):
|
class FloatOption(BaseOption):
|
||||||
""" Float configuration option class """
|
""" Float configuration option class """
|
||||||
|
@ -251,6 +292,18 @@ class FloatOption(BaseOption):
|
||||||
def parser_type(self):
|
def parser_type(self):
|
||||||
return float
|
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):
|
class IntegerOption(BaseOption):
|
||||||
""" Integer configuration option class """
|
""" Integer configuration option class """
|
||||||
|
@ -269,6 +322,18 @@ class IntegerOption(BaseOption):
|
||||||
def parser_type(self):
|
def parser_type(self):
|
||||||
return int
|
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):
|
class PasswordOption(StringOption):
|
||||||
""" Password configuration option class """
|
""" Password configuration option class """
|
||||||
|
@ -324,15 +389,57 @@ class PasswordOption(StringOption):
|
||||||
return self.keyring_value
|
return self.keyring_value
|
||||||
return super().to_config(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 """
|
""" 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(
|
keyring.set_password(
|
||||||
self._keyring_service_name, self._keyring_username,
|
self._keyring_service_name, self._keyring_username,
|
||||||
value)
|
value)
|
||||||
value = self.keyring_value
|
value = self.keyring_value
|
||||||
super().set(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:
|
class ConfigSection:
|
||||||
""" Configuration section class """
|
""" Configuration section class """
|
||||||
|
@ -395,6 +502,34 @@ class ConfigSection:
|
||||||
lines.append(self.options[option].export_to_config())
|
lines.append(self.options[option].export_to_config())
|
||||||
return '\n'.join(lines)
|
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):
|
class RawWrappedTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
|
||||||
|
|
||||||
|
@ -684,6 +819,38 @@ class Config:
|
||||||
for section in self._ordered_section_names:
|
for section in self._ordered_section_names:
|
||||||
self.sections[section].add_options_to_parser(parser)
|
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
|
@property
|
||||||
def config_dir(self):
|
def config_dir(self):
|
||||||
""" Retrieve configuration directory path """
|
""" Retrieve configuration directory path """
|
||||||
|
|
Loading…
Reference in a new issue