Updates to config/options aggregation

- Adds another flag to the Option class
- Adds normalization to the config parsing
- Removes dead code from _parse_config
- Handles user specifying --append-config, --config, and --isolated
  but does not handle their mutual exclusion
This commit is contained in:
Ian Cordasco 2016-01-07 09:37:34 -06:00
parent 07a29e45db
commit 67aff6d2ec
3 changed files with 148 additions and 18 deletions

View file

@ -0,0 +1,51 @@
"""Aggregation function for CLI specified options and config file options.
This holds the logic that uses the collected and merged config files and
applies the user-specified command-line configuration on top of it.
"""
import logging
from flake8.options import config
from flake8 import utils
LOG = logging.getLogger(__name__)
def aggregate_options(manager, arglist=None, values=None):
"""Function that aggregates the CLI and Config options."""
# Get defaults from the option parser
default_values, _ = manager.parse_args([], values=values)
# Get original CLI values so we can find additional config file paths and
# see if --config was specified.
original_values, original_args = manager.parse_args()
extra_config_files = utils.normalize_paths(original_values.append_config)
# Make our new configuration file mergerator
config_parser = config.MergedConfigParser(
option_manager=manager,
extra_config_files=extra_config_files,
args=original_args,
)
# Get the parsed config
parsed_config = config_parser.parse(original_values.config,
original_values.isolated)
# Merge values parsed from config onto the default values returned
for config_name, value in parsed_config.items():
dest_name = config_name
# If the config name is somehow different from the destination name,
# fetch the destination name from our Option
if not hasattr(default_values, config_name):
dest_name = config_parser.config_options[config_name].dest
LOG.debug('Overriding default value of (%s) for "%s" with (%s)',
getattr(default_values, dest_name, None),
dest_name,
value)
# Override the default values with the config values
setattr(default_values, dest_name, value)
# Finally parse the command-line options
final_values, args = manager.parse_args(arglist, default_values)
return final_values, args

View file

@ -11,11 +11,16 @@ from . import utils
LOG = logging.getLogger(__name__)
__all__('ConfigFileFinder', 'MergedConfigParser')
class ConfigFileFinder(object):
PROJECT_FILENAMES = ('setup.cfg', 'tox.ini')
def __init__(self, program_name, args):
def __init__(self, program_name, args, extra_config_files):
# The values of --append-config from the CLI
self.extra_config_files = extra_config_files
# Platform specific settings
self.is_windows = sys.platform == 'win32'
self.xdg_home = os.environ.get('XDG_CONFIG_HOME',
@ -27,8 +32,6 @@ class ConfigFileFinder(object):
# List of filenames to find in the local/project directory
self.project_filenames = ('setup.cfg', 'tox.ini', self.program_config)
# List of filenames to find "globally"
self.global_filenames = (self.program_config,)
self.local_directory = os.curdir
@ -42,6 +45,12 @@ class ConfigFileFinder(object):
found_files = config.read(files)
return (config, found_files)
def cli_config(self, files):
config, found_files = self._read_config(files)
if found_files:
LOG.debug('Found cli configuration files: %s', found_files)
return config
def generate_possible_local_config_files(self):
tail = self.tail
parent = self.parent
@ -57,7 +66,7 @@ class ConfigFileFinder(object):
filename
for filename in self.generate_possible_local_config_files()
if os.path.exists(filename)
]
] + self.extra_config_files
def local_configs(self):
config, found_files = self._read_config(self.local_config_files())
@ -81,20 +90,33 @@ class MergedConfigParser(object):
GETINT_METHODS = set(['int', 'count'])
GETBOOL_METHODS = set(['store_true', 'store_false'])
def __init__(self, option_manager, args=None):
def __init__(self, option_manager, extra_config_files=None, args=None):
# Our instance of flake8.options.manager.OptionManager
self.option_manager = option_manager
# The prog value for the cli parser
self.program_name = option_manager.program_name
# Parsed extra arguments
self.args = args
# Our instance of our ConfigFileFinder
self.config_finder = ConfigFileFinder(self.program_name, self.args)
# Mapping of configuration option names to
# flake8.options.manager.Option instances
self.config_options = option_manager.config_options_dict
self.extra_config_files = extra_config_files or []
@staticmethod
def _normalize_value(option, value):
if option.normalize_paths:
final_value = utils.normalize_paths(value)
elif option.comma_separated_list:
final_value = utils.parse_comma_separated_list(value)
else:
final_value = value
LOG.debug('%r has been normalized to %r for option "%s"',
value, final_value, option.config_name)
return final_value
def _parse_config(self, config_parser):
config = self.config_finder.local_configs()
if not config.has_section(self.program_name):
LOG.debug('Local configuration files have no %s section',
self.program_name)
return {}
config_dict = {}
for option_name in config_parser.options(self.program_name):
if option_name not in self.config_options:
@ -113,10 +135,7 @@ class MergedConfigParser(object):
value = method(self.program_name, option_name)
LOG.debug('Option "%s" returned value: %r', option_name, value)
final_value = value
if option.comma_separated_list:
final_value = utils.parse_comma_separated_list(value)
final_value = self._normalize_value(value)
config_dict[option_name] = final_value
def is_configured_by(self, config):
@ -129,7 +148,7 @@ class MergedConfigParser(object):
if not self.is_configured_by(config):
LOG.debug('Local configuration files have no %s section',
self.program_name)
return
return {}
LOG.debug('Parsing local configuration files.')
return self._parse_config(config)
@ -140,18 +159,51 @@ class MergedConfigParser(object):
if not self.is_configured_by(config):
LOG.debug('User configuration files have no %s section',
self.program_name)
return
return {}
LOG.debug('Parsing user configuration files.')
return self._parse_config(config)
def parse(self):
def parse_cli_config(self, config_path):
"""Parse and return the file specified by --config."""
config = self.config_finder.cli_config(config_path)
if not self.is_configured_by(config):
LOG.debug('CLI configuration files have no %s section',
self.program_name)
return {}
LOG.debug('Parsing CLI configuration files.')
return self._parse_config(config)
def parse(self, cli_config=None, isolated=False):
"""Parse and return the local and user config files.
First this copies over the parsed local configuration and then
iterates over the options in the user configuration and sets them if
they were not set by the local configuration file.
:param str cli_config:
Value of --config when specified at the command-line. Overrides
all other config files.
:param bool isolated:
Determines if we should parse configuration files at all or not.
If running in isolated mode, we ignore all configuration files
:returns:
Dictionary of parsed configuration options
:rtype:
dict
"""
if isolated:
LOG.debug('Refusing to parse configuration files due to user-'
'requested isolation')
return {}
if cli_config:
LOG.debug('Ignoring user and locally found configuration files. '
'Reading only configuration from "%s" specified via '
'--config by the user', cli_config)
return self.parse_cli_config(cli_config)
user_config = self.parse_user_config()
config = self.parse_local_config()

View file

@ -5,6 +5,7 @@ LOG = logging.getLogger(__name__)
class Option(object):
"""Our wrapper around an optparse.Option object to add features."""
def __init__(self, short_option_name=None, long_option_name=None,
# Options below here are taken from the optparse.Option class
action=None, default=None, type=None, dest=None,
@ -13,6 +14,7 @@ class Option(object):
metavar=None,
# Options below here are specific to Flake8
parse_from_config=False, comma_separated_list=False,
normalize_paths=False,
):
"""Initialize an Option instance wrapping optparse.Option.
@ -57,6 +59,9 @@ class Option(object):
:param bool comma_separated_list:
Whether the option is a comma separated list when parsing from a
config file.
:param bool normalize_paths:
Whether the option is expecting a path or list of paths and should
attempt to normalize the paths to absolute paths.
"""
self.short_option_name = short_option_name
self.long_option_name = long_option_name
@ -72,11 +77,16 @@ class Option(object):
'help': help,
'metavar': metavar,
}
# Set attributes for our option arguments
for key, value in self.option_kwargs.items():
setattr(self, key, value)
# Set our custom attributes
self.parse_from_config = parse_from_config
self.comma_separated_list = comma_separated_list
self.normalize_paths = normalize_paths
self.config_name = None
if parse_from_config:
if not long_option_name:
raise ValueError('When specifying parse_from_config=True, '
@ -108,6 +118,16 @@ class OptionManager(object):
self.version = version
def add_option(self, *args, **kwargs):
"""Create and register a new option.
See parameters for :class:`~flake8.options.manager.Option` for
acceptable arguments to this method.
.. note::
``short_option_name`` and ``long_option_name`` may be specified
positionally as they are with optparse normally.
"""
option = Option(*args, **kwargs)
self.parser.add_option(option.to_optparse())
self.options.append(option)
@ -116,4 +136,11 @@ class OptionManager(object):
LOG.debug('Registered option "%s".', option)
def parse_args(self, args=None, values=None):
"""Simple proxy to calling the OptionParser's parse_args method.
.. todo::
Normalize values based on our extra attributes from
:class:`~flake8.options.manager.OptionManager`.
"""
return self.parser.parse_args(args, values)