mirror of
https://github.com/PyCQA/flake8.git
synced 2026-04-08 22:04:17 +00:00
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:
parent
07a29e45db
commit
67aff6d2ec
3 changed files with 148 additions and 18 deletions
51
flake8/options/aggregator.py
Normal file
51
flake8/options/aggregator.py
Normal 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
|
||||||
|
|
@ -11,11 +11,16 @@ from . import utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
__all__('ConfigFileFinder', 'MergedConfigParser')
|
||||||
|
|
||||||
|
|
||||||
class ConfigFileFinder(object):
|
class ConfigFileFinder(object):
|
||||||
PROJECT_FILENAMES = ('setup.cfg', 'tox.ini')
|
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
|
# Platform specific settings
|
||||||
self.is_windows = sys.platform == 'win32'
|
self.is_windows = sys.platform == 'win32'
|
||||||
self.xdg_home = os.environ.get('XDG_CONFIG_HOME',
|
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
|
# List of filenames to find in the local/project directory
|
||||||
self.project_filenames = ('setup.cfg', 'tox.ini', self.program_config)
|
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
|
self.local_directory = os.curdir
|
||||||
|
|
||||||
|
|
@ -42,6 +45,12 @@ class ConfigFileFinder(object):
|
||||||
found_files = config.read(files)
|
found_files = config.read(files)
|
||||||
return (config, found_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):
|
def generate_possible_local_config_files(self):
|
||||||
tail = self.tail
|
tail = self.tail
|
||||||
parent = self.parent
|
parent = self.parent
|
||||||
|
|
@ -57,7 +66,7 @@ class ConfigFileFinder(object):
|
||||||
filename
|
filename
|
||||||
for filename in self.generate_possible_local_config_files()
|
for filename in self.generate_possible_local_config_files()
|
||||||
if os.path.exists(filename)
|
if os.path.exists(filename)
|
||||||
]
|
] + self.extra_config_files
|
||||||
|
|
||||||
def local_configs(self):
|
def local_configs(self):
|
||||||
config, found_files = self._read_config(self.local_config_files())
|
config, found_files = self._read_config(self.local_config_files())
|
||||||
|
|
@ -81,20 +90,33 @@ class MergedConfigParser(object):
|
||||||
GETINT_METHODS = set(['int', 'count'])
|
GETINT_METHODS = set(['int', 'count'])
|
||||||
GETBOOL_METHODS = set(['store_true', 'store_false'])
|
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
|
self.option_manager = option_manager
|
||||||
|
# The prog value for the cli parser
|
||||||
self.program_name = option_manager.program_name
|
self.program_name = option_manager.program_name
|
||||||
|
# Parsed extra arguments
|
||||||
self.args = args
|
self.args = args
|
||||||
|
# Our instance of our ConfigFileFinder
|
||||||
self.config_finder = ConfigFileFinder(self.program_name, self.args)
|
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.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):
|
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 = {}
|
config_dict = {}
|
||||||
for option_name in config_parser.options(self.program_name):
|
for option_name in config_parser.options(self.program_name):
|
||||||
if option_name not in self.config_options:
|
if option_name not in self.config_options:
|
||||||
|
|
@ -113,10 +135,7 @@ class MergedConfigParser(object):
|
||||||
value = method(self.program_name, option_name)
|
value = method(self.program_name, option_name)
|
||||||
LOG.debug('Option "%s" returned value: %r', option_name, value)
|
LOG.debug('Option "%s" returned value: %r', option_name, value)
|
||||||
|
|
||||||
final_value = value
|
final_value = self._normalize_value(value)
|
||||||
if option.comma_separated_list:
|
|
||||||
final_value = utils.parse_comma_separated_list(value)
|
|
||||||
|
|
||||||
config_dict[option_name] = final_value
|
config_dict[option_name] = final_value
|
||||||
|
|
||||||
def is_configured_by(self, config):
|
def is_configured_by(self, config):
|
||||||
|
|
@ -129,7 +148,7 @@ class MergedConfigParser(object):
|
||||||
if not self.is_configured_by(config):
|
if not self.is_configured_by(config):
|
||||||
LOG.debug('Local configuration files have no %s section',
|
LOG.debug('Local configuration files have no %s section',
|
||||||
self.program_name)
|
self.program_name)
|
||||||
return
|
return {}
|
||||||
|
|
||||||
LOG.debug('Parsing local configuration files.')
|
LOG.debug('Parsing local configuration files.')
|
||||||
return self._parse_config(config)
|
return self._parse_config(config)
|
||||||
|
|
@ -140,18 +159,51 @@ class MergedConfigParser(object):
|
||||||
if not self.is_configured_by(config):
|
if not self.is_configured_by(config):
|
||||||
LOG.debug('User configuration files have no %s section',
|
LOG.debug('User configuration files have no %s section',
|
||||||
self.program_name)
|
self.program_name)
|
||||||
return
|
return {}
|
||||||
|
|
||||||
LOG.debug('Parsing user configuration files.')
|
LOG.debug('Parsing user configuration files.')
|
||||||
return self._parse_config(config)
|
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.
|
"""Parse and return the local and user config files.
|
||||||
|
|
||||||
First this copies over the parsed local configuration and then
|
First this copies over the parsed local configuration and then
|
||||||
iterates over the options in the user configuration and sets them if
|
iterates over the options in the user configuration and sets them if
|
||||||
they were not set by the local configuration file.
|
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()
|
user_config = self.parse_user_config()
|
||||||
config = self.parse_local_config()
|
config = self.parse_local_config()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Option(object):
|
class Option(object):
|
||||||
|
"""Our wrapper around an optparse.Option object to add features."""
|
||||||
def __init__(self, short_option_name=None, long_option_name=None,
|
def __init__(self, short_option_name=None, long_option_name=None,
|
||||||
# Options below here are taken from the optparse.Option class
|
# Options below here are taken from the optparse.Option class
|
||||||
action=None, default=None, type=None, dest=None,
|
action=None, default=None, type=None, dest=None,
|
||||||
|
|
@ -13,6 +14,7 @@ class Option(object):
|
||||||
metavar=None,
|
metavar=None,
|
||||||
# Options below here are specific to Flake8
|
# Options below here are specific to Flake8
|
||||||
parse_from_config=False, comma_separated_list=False,
|
parse_from_config=False, comma_separated_list=False,
|
||||||
|
normalize_paths=False,
|
||||||
):
|
):
|
||||||
"""Initialize an Option instance wrapping optparse.Option.
|
"""Initialize an Option instance wrapping optparse.Option.
|
||||||
|
|
||||||
|
|
@ -57,6 +59,9 @@ class Option(object):
|
||||||
:param bool comma_separated_list:
|
:param bool comma_separated_list:
|
||||||
Whether the option is a comma separated list when parsing from a
|
Whether the option is a comma separated list when parsing from a
|
||||||
config file.
|
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.short_option_name = short_option_name
|
||||||
self.long_option_name = long_option_name
|
self.long_option_name = long_option_name
|
||||||
|
|
@ -72,11 +77,16 @@ class Option(object):
|
||||||
'help': help,
|
'help': help,
|
||||||
'metavar': metavar,
|
'metavar': metavar,
|
||||||
}
|
}
|
||||||
|
# Set attributes for our option arguments
|
||||||
for key, value in self.option_kwargs.items():
|
for key, value in self.option_kwargs.items():
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
# Set our custom attributes
|
||||||
self.parse_from_config = parse_from_config
|
self.parse_from_config = parse_from_config
|
||||||
self.comma_separated_list = comma_separated_list
|
self.comma_separated_list = comma_separated_list
|
||||||
|
self.normalize_paths = normalize_paths
|
||||||
|
|
||||||
|
self.config_name = None
|
||||||
if parse_from_config:
|
if parse_from_config:
|
||||||
if not long_option_name:
|
if not long_option_name:
|
||||||
raise ValueError('When specifying parse_from_config=True, '
|
raise ValueError('When specifying parse_from_config=True, '
|
||||||
|
|
@ -108,6 +118,16 @@ class OptionManager(object):
|
||||||
self.version = version
|
self.version = version
|
||||||
|
|
||||||
def add_option(self, *args, **kwargs):
|
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)
|
option = Option(*args, **kwargs)
|
||||||
self.parser.add_option(option.to_optparse())
|
self.parser.add_option(option.to_optparse())
|
||||||
self.options.append(option)
|
self.options.append(option)
|
||||||
|
|
@ -116,4 +136,11 @@ class OptionManager(object):
|
||||||
LOG.debug('Registered option "%s".', option)
|
LOG.debug('Registered option "%s".', option)
|
||||||
|
|
||||||
def parse_args(self, args=None, values=None):
|
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)
|
return self.parser.parse_args(args, values)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue