Source code for flake8.options.manager

"""Option handling and Option management logic."""
import collections
import logging
import optparse  # pylint: disable=deprecated-module
from typing import Any, Callable, Dict, List, Optional, Set

from flake8 import utils

LOG = logging.getLogger(__name__)


[docs]class Option(object): """Our wrapper around an optparse.Option object to add features."""
[docs] 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, nargs=None, const=None, choices=None, callback=None, callback_args=None, callback_kwargs=None, help=None, 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. The following are all passed directly through to optparse. :param str short_option_name: The short name of the option (e.g., ``-x``). This will be the first argument passed to :class:`~optparse.Option`. :param str long_option_name: The long name of the option (e.g., ``--xtra-long-option``). This will be the second argument passed to :class:`~optparse.Option`. :param str action: Any action allowed by :mod:`optparse`. :param default: Default value of the option. :param type: Any type allowed by :mod:`optparse`. :param dest: Attribute name to store parsed option value as. :param nargs: Number of arguments to parse for this option. :param const: Constant value to store on a common destination. Usually used in conjuntion with ``action="store_const"``. :param iterable choices: Possible values for the option. :param callable callback: Callback used if the action is ``"callback"``. :param iterable callback_args: Additional positional arguments to the callback callable. :param dictionary callback_kwargs: Keyword arguments to the callback callable. :param str help: Help text displayed in the usage information. :param str metavar: Name to use instead of the long option name for help text. The following parameters are for Flake8's option handling alone. :param bool parse_from_config: Whether or not this option should be parsed out of config files. :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 self.option_args = [ x for x in (short_option_name, long_option_name) if x is not None ] self.option_kwargs = { "action": action, "default": default, "type": type, "dest": self._make_dest(dest), "nargs": nargs, "const": const, "choices": choices, "callback": callback, "callback_args": callback_args, "callback_kwargs": callback_kwargs, "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 # type: Optional[str] if parse_from_config: if not long_option_name: raise ValueError( "When specifying parse_from_config=True, " "a long_option_name must also be specified." ) self.config_name = long_option_name[2:].replace("-", "_") self._opt = None
def __repr__(self): # noqa: D105 return ( "Option({0}, {1}, action={action}, default={default}, " "dest={dest}, type={type}, callback={callback}, help={help}," " callback={callback}, callback_args={callback_args}, " "callback_kwargs={callback_kwargs}, metavar={metavar})" ).format( self.short_option_name, self.long_option_name, **self.option_kwargs ) def _make_dest(self, dest): if dest: return dest if self.long_option_name: return self.long_option_name[2:].replace("-", "_") return self.short_option_name[1]
[docs] def normalize(self, value, *normalize_args): """Normalize the value based on the option configuration.""" if self.normalize_paths: # Decide whether to parse a list of paths or a single path normalize = utils.normalize_path # type: Callable[..., Any] if self.comma_separated_list: normalize = utils.normalize_paths return normalize(value, *normalize_args) elif self.comma_separated_list: return utils.parse_comma_separated_list(value) return value
def normalize_from_setuptools(self, value): """Normalize the value received from setuptools.""" value = self.normalize(value) if self.type == "int" or self.action == "count": return int(value) elif self.type == "float": return float(value) elif self.type == "complex": return complex(value) if self.action in ("store_true", "store_false"): value = str(value).upper() if value in ("1", "T", "TRUE", "ON"): return True if value in ("0", "F", "FALSE", "OFF"): return False return value def to_optparse(self): """Convert a Flake8 Option to an optparse Option.""" if self._opt is None: self._opt = optparse.Option( *self.option_args, **self.option_kwargs ) return self._opt
PluginVersion = collections.namedtuple( "PluginVersion", ["name", "version", "local"] )
[docs]class OptionManager(object): """Manage Options and OptionParser while adding post-processing."""
[docs] def __init__( self, prog=None, version=None, usage="%prog [options] file file ..." ): """Initialize an instance of an OptionManager. :param str prog: Name of the actual program (e.g., flake8). :param str version: Version string for the program. :param str usage: Basic usage string used by the OptionParser. """ self.parser = optparse.OptionParser( prog=prog, version=version, usage=usage ) self.config_options_dict = {} # type: Dict[str, Option] self.options = [] # type: List[Option] self.program_name = prog self.version = version self.registered_plugins = set() # type: Set[PluginVersion] self.extended_default_ignore = set() # type: Set[str] self.extended_default_select = set() # type: Set[str]
[docs] @staticmethod def format_plugin(plugin): """Convert a PluginVersion into a dictionary mapping name to value.""" return {attr: getattr(plugin, attr) for attr in ["name", "version"]}
[docs] 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. """ if len(args) == 1 and args[0].startswith("--"): args = (None, args[0]) option = Option(*args, **kwargs) self.parser.add_option(option.to_optparse()) self.options.append(option) if option.parse_from_config: name = option.config_name assert name is not None # nosec (for mypy) self.config_options_dict[name] = option self.config_options_dict[name.replace("_", "-")] = option LOG.debug('Registered option "%s".', option)
[docs] def remove_from_default_ignore(self, error_codes): """Remove specified error codes from the default ignore list. :param list error_codes: List of strings that are the error/warning codes to attempt to remove from the extended default ignore list. """ LOG.debug("Removing %r from the default ignore list", error_codes) for error_code in error_codes: try: self.extended_default_ignore.remove(error_code) except (ValueError, KeyError): LOG.debug( "Attempted to remove %s from default ignore" " but it was not a member of the list.", error_code, )
[docs] def extend_default_ignore(self, error_codes): """Extend the default ignore list with the error codes provided. :param list error_codes: List of strings that are the error/warning codes with which to extend the default ignore list. """ LOG.debug("Extending default ignore list with %r", error_codes) self.extended_default_ignore.update(error_codes)
[docs] def extend_default_select(self, error_codes): """Extend the default select list with the error codes provided. :param list error_codes: List of strings that are the error/warning codes with which to extend the default select list. """ LOG.debug("Extending default select list with %r", error_codes) self.extended_default_select.update(error_codes)
[docs] def generate_versions( self, format_str="%(name)s: %(version)s", join_on=", " ): """Generate a comma-separated list of versions of plugins.""" return join_on.join( format_str % self.format_plugin(plugin) for plugin in sorted(self.registered_plugins) )
[docs] def update_version_string(self): """Update the flake8 version string.""" self.parser.version = ( self.version + " (" + self.generate_versions() + ") " + utils.get_python_version() )
[docs] def generate_epilog(self): """Create an epilog with the version and name of each of plugin.""" plugin_version_format = "%(name)s: %(version)s" self.parser.epilog = "Installed plugins: " + self.generate_versions( plugin_version_format )
def _normalize(self, options): for option in self.options: old_value = getattr(options, option.dest) setattr(options, option.dest, option.normalize(old_value))
[docs] def parse_args(self, args=None, values=None): """Proxy to calling the OptionParser's parse_args method.""" self.generate_epilog() self.update_version_string() options, xargs = self.parser.parse_args(args, values) self._normalize(options) return options, xargs
[docs] def parse_known_args(self, args=None, values=None): """Parse only the known arguments from the argument values. Replicate a little argparse behaviour while we're still on optparse. """ self.generate_epilog() self.update_version_string() # Taken from optparse.OptionParser.parse_args rargs = self.parser._get_args(args) if values is None: values = self.parser.get_default_values() self.parser.rargs = rargs largs = [] # type: List[str] self.parser.values = values while rargs: # NOTE(sigmavirus24): If we only care about *known* options, then # we should just shift the bad option over to the largs list and # carry on. # Unfortunately, we need to rely on a private method here. try: self.parser._process_args(largs, rargs, values) except ( optparse.BadOptionError, optparse.OptionValueError, ) as err: # TODO: https://gitlab.com/pycqa/flake8/issues/541 largs.append(err.opt_str) # type: ignore args = largs + rargs options, xargs = self.parser.check_values(values, args) self._normalize(options) return options, xargs
[docs] def register_plugin(self, name, version, local=False): """Register a plugin relying on the OptionManager. :param str name: The name of the checker itself. This will be the ``name`` attribute of the class or function loaded from the entry-point. :param str version: The version of the checker that we're using. :param bool local: Whether the plugin is local to the project/repository or not. """ self.registered_plugins.add(PluginVersion(name, version, local))