summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2015-09-01 21:41:10 +0300
committerClaudiu Popa <pcmanticore@gmail.com>2015-09-01 21:41:10 +0300
commit7d9d8365065376461c588162afa6b0c495d8353c (patch)
tree9a03a50b519c8c246b9fa1ba6008493a9f00d678
parent19161ddc41707337b3be5778ea2e01504f23ece0 (diff)
downloadpylint-7d9d8365065376461c588162afa6b0c495d8353c.tar.gz
Bring parts of logilab.common.configuration and logilab.common.optik_ext into pylint.config
The reason behind this is that we can better control the behaviour of the underlying configuration modules, such as the case for undefined options or for quickly fixing other bugs. Another side effect of this change is that it gets us closer to the moment where we will not be dependent on logilab.common anymore, which will definitely make our pytest users happy. Some parts were copied almost verbatim from logilab.common.configuration and logilab.common.optik_ext and pylint.config will definitely need a refactoring and reengineering for abstracting the configuration, so that we won't use optparse anymore, but that's subject for another patch.
-rw-r--r--pylint/checkers/__init__.py3
-rw-r--r--pylint/config.py767
-rw-r--r--pylint/lint.py17
-rw-r--r--pylint/pyreverse/main.py5
-rw-r--r--pylint/test/test_functional.py3
-rw-r--r--pylint/utils.py94
6 files changed, 826 insertions, 63 deletions
diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py
index 51adb4d..daa942a 100644
--- a/pylint/checkers/__init__.py
+++ b/pylint/checkers/__init__.py
@@ -44,8 +44,7 @@ import sys
import tokenize
import warnings
-from logilab.common.configuration import OptionsProviderMixIn
-
+from pylint.config import OptionsProviderMixIn
from pylint.reporters import diff_string
from pylint.utils import register_plugins
from pylint.interfaces import UNDEFINED
diff --git a/pylint/config.py b/pylint/config.py
index a821726..a18e609 100644
--- a/pylint/config.py
+++ b/pylint/config.py
@@ -16,40 +16,48 @@
* pylintrc
* pylint.d (PYLINTHOME)
"""
-from __future__ import with_statement
from __future__ import print_function
-import pickle
+# TODO(cpopa): this module contains the logic for the
+# configuration parser and for the command line parser,
+# but it's really coupled to optparse's internals.
+# The code was copied almost verbatim from logilab.common,
+# in order to not depend on it anymore and it will definitely
+# need a cleanup. It could be completely reengineered as well.
+
+import contextlib
+import copy
+import optparse
import os
+import pickle
+import re
import sys
-from os.path import exists, isfile, join, expanduser, abspath, dirname
+import time
+
+from six.moves import configparser
+from six.moves import range
-# pylint home is used to save old runs results ################################
+from pylint import utils
-USER_HOME = expanduser('~')
+
+USER_HOME = os.path.expanduser('~')
if 'PYLINTHOME' in os.environ:
PYLINT_HOME = os.environ['PYLINTHOME']
if USER_HOME == '~':
- USER_HOME = dirname(PYLINT_HOME)
+ USER_HOME = os.path.dirname(PYLINT_HOME)
elif USER_HOME == '~':
PYLINT_HOME = ".pylint.d"
else:
- PYLINT_HOME = join(USER_HOME, '.pylint.d')
+ PYLINT_HOME = os.path.join(USER_HOME, '.pylint.d')
-def get_pdata_path(base_name, recurs):
- """return the path of the file which should contain old search data for the
- given base_name with the given options values
- """
+
+def _get_pdata_path(base_name, recurs):
base_name = base_name.replace(os.sep, '_')
- return join(PYLINT_HOME, "%s%s%s"%(base_name, recurs, '.stats'))
+ return os.path.join(PYLINT_HOME, "%s%s%s"%(base_name, recurs, '.stats'))
-def load_results(base):
- """try to unpickle and return data from file if it exists and is not
- corrupted
- return an empty dictionary if it doesn't exists
- """
- data_file = get_pdata_path(base, 1)
+def load_results(base):
+ data_file = _get_pdata_path(base, 1)
try:
with open(data_file, _PICK_LOAD) as stream:
return pickle.load(stream)
@@ -62,48 +70,45 @@ else:
_PICK_DUMP, _PICK_LOAD = 'wb', 'rb'
def save_results(results, base):
- """pickle results"""
- if not exists(PYLINT_HOME):
+ if not os.path.exists(PYLINT_HOME):
try:
os.mkdir(PYLINT_HOME)
except OSError:
print('Unable to create directory %s' % PYLINT_HOME, file=sys.stderr)
- data_file = get_pdata_path(base, 1)
+ data_file = _get_pdata_path(base, 1)
try:
with open(data_file, _PICK_DUMP) as stream:
pickle.dump(results, stream)
except (IOError, OSError) as ex:
print('Unable to create file %s: %s' % (data_file, ex), file=sys.stderr)
-# location of the configuration file ##########################################
-
def find_pylintrc():
"""search the pylint rc file and return its path if it find it, else None
"""
# is there a pylint rc file in the current directory ?
- if exists('pylintrc'):
- return abspath('pylintrc')
- if exists('.pylintrc'):
- return abspath('.pylintrc')
- if isfile('__init__.py'):
- curdir = abspath(os.getcwd())
- while isfile(join(curdir, '__init__.py')):
- curdir = abspath(join(curdir, '..'))
- if isfile(join(curdir, 'pylintrc')):
- return join(curdir, 'pylintrc')
- if 'PYLINTRC' in os.environ and exists(os.environ['PYLINTRC']):
+ if os.path.exists('pylintrc'):
+ return os.path.abspath('pylintrc')
+ if os.path.exists('.pylintrc'):
+ return os.path.abspath('.pylintrc')
+ if os.path.isfile('__init__.py'):
+ curdir = os.path.abspath(os.getcwd())
+ while os.path.isfile(os.path.join(curdir, '__init__.py')):
+ curdir = os.path.abspath(os.path.join(curdir, '..'))
+ if os.path.isfile(os.path.join(curdir, 'pylintrc')):
+ return os.path.join(curdir, 'pylintrc')
+ if 'PYLINTRC' in os.environ and os.path.exists(os.environ['PYLINTRC']):
pylintrc = os.environ['PYLINTRC']
else:
- user_home = expanduser('~')
+ user_home = os.path.expanduser('~')
if user_home == '~' or user_home == '/root':
pylintrc = ".pylintrc"
else:
- pylintrc = join(user_home, '.pylintrc')
- if not isfile(pylintrc):
- pylintrc = join(user_home, '.config', 'pylintrc')
- if not isfile(pylintrc):
- if isfile('/etc/pylintrc'):
+ pylintrc = os.path.join(user_home, '.pylintrc')
+ if not os.path.isfile(pylintrc):
+ pylintrc = os.path.join(user_home, '.config', 'pylintrc')
+ if not os.path.isfile(pylintrc):
+ if os.path.isfile('/etc/pylintrc'):
pylintrc = '/etc/pylintrc'
else:
pylintrc = None
@@ -112,12 +117,682 @@ def find_pylintrc():
PYLINTRC = find_pylintrc()
ENV_HELP = '''
-The following environment variables are used:
- * PYLINTHOME
- Path to the directory where the persistent for the run will be stored. If
-not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working
-directory).
- * PYLINTRC
+The following environment variables are used:
+ * PYLINTHOME
+ Path to the directory where the persistent for the run will be stored. If
+not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working
+directory).
+ * PYLINTRC
Path to the configuration file. See the documentation for the method used
to search for configuration file.
''' % globals()
+
+
+class UnsupportedAction(Exception):
+ """raised by set_option when it doesn't know what to do for an action"""
+
+
+def _choice_validator(optdict, name, value):
+ if value not in optdict['choices']:
+ msg = "option %s: invalid value: %r, should be in %s"
+ raise optparse.OptionValueError(msg % (name, value, optdict['choices']))
+ return value
+
+
+def _multiple_choice_validator(optdict, name, value):
+ choices = optdict['choices']
+ values = utils._check_csv(value)
+ for value in values:
+ if value not in choices:
+ msg = "option %s: invalid value: %r, should be in %s"
+ raise optparse.OptionValueError(msg % (name, value, choices))
+ return values
+
+
+# pylint: disable=unused-argument
+def _csv_validator(_, name, value):
+ return utils._check_csv(value)
+
+
+# pylint: disable=unused-argument
+def _regexp_validator(_, name, value):
+ if hasattr(value, 'pattern'):
+ return value
+ return re.compile(value)
+
+
+def _yn_validator(opt, _, value):
+ if isinstance(value, int):
+ return bool(value)
+ if value in ('y', 'yes'):
+ return True
+ if value in ('n', 'no'):
+ return False
+ msg = "option %s: invalid yn value %r, should be in (y, yes, n, no)"
+ raise optparse.OptionValueError(msg % (opt, value))
+
+
+VALIDATORS = {
+ 'string': utils._unquote,
+ 'int': int,
+ 'regexp': re.compile,
+ 'csv': _csv_validator,
+ 'yn': _yn_validator,
+ 'choice': _choice_validator,
+ 'multiple_choice': _multiple_choice_validator,
+}
+
+def _call_validator(opttype, optdict, option, value):
+ if opttype not in VALIDATORS:
+ raise Exception('Unsupported type "%s"' % opttype)
+ try:
+ return VALIDATORS[opttype](optdict, option, value)
+ except TypeError:
+ try:
+ return VALIDATORS[opttype](value)
+ except Exception:
+ raise optparse.OptionValueError('%s value (%r) should be of type %s' %
+ (option, value, opttype))
+
+
+def _validate(value, optdict, name=''):
+ """return a validated value for an option according to its type
+
+ optional argument name is only used for error message formatting
+ """
+ try:
+ _type = optdict['type']
+ except KeyError:
+ # FIXME
+ return value
+ return _call_validator(_type, optdict, name, value)
+
+
+def _level_options(group, outputlevel):
+ return [option for option in group.option_list
+ if (getattr(option, 'level', 0) or 0) <= outputlevel
+ and option.help is not optparse.SUPPRESS_HELP]
+
+
+def _expand_default(self, option):
+ """Patch OptionParser.expand_default with custom behaviour
+
+ This will handle defaults to avoid overriding values in the
+ configuration file.
+ """
+ if self.parser is None or not self.default_tag:
+ return option.help
+ optname = option._long_opts[0][2:]
+ try:
+ provider = self.parser.options_manager._all_options[optname]
+ except KeyError:
+ value = None
+ else:
+ optdict = provider.get_option_def(optname)
+ optname = provider.option_attrname(optname, optdict)
+ value = getattr(provider.config, optname, optdict)
+ value = utils._format_option_value(optdict, value)
+ if value is optparse.NO_DEFAULT or not value:
+ value = self.NO_DEFAULT_VALUE
+ return option.help.replace(self.default_tag, str(value))
+
+
+@contextlib.contextmanager
+def _patch_optparse():
+ orig_default = optparse.HelpFormatter
+ try:
+ optparse.HelpFormatter.expand_default = _expand_default
+ yield
+ finally:
+ optparse.HelpFormatter.expand_default = orig_default
+
+
+class Option(optparse.Option):
+ TYPES = optparse.Option.TYPES + ('regexp', 'csv', 'yn', 'multiple_choice')
+ ATTRS = optparse.Option.ATTRS + ['hide', 'level']
+ TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER)
+ TYPE_CHECKER['regexp'] = _regexp_validator
+ TYPE_CHECKER['csv'] = _csv_validator
+ TYPE_CHECKER['yn'] = _yn_validator
+ TYPE_CHECKER['multiple_choice'] = _multiple_choice_validator
+
+ def __init__(self, *opts, **attrs):
+ optparse.Option.__init__(self, *opts, **attrs)
+ if hasattr(self, "hide") and self.hide:
+ self.help = optparse.SUPPRESS_HELP
+
+ def _check_choice(self):
+ if self.type in ("choice", "multiple_choice"):
+ if self.choices is None:
+ raise optparse.OptionError(
+ "must supply a list of choices for type 'choice'", self)
+ elif not isinstance(self.choices, (tuple, list)):
+ raise optparse.OptionError(
+ "choices must be a list of strings ('%s' supplied)"
+ % str(type(self.choices)).split("'")[1], self)
+ elif self.choices is not None:
+ raise optparse.OptionError(
+ "must not supply choices for type %r" % self.type, self)
+ optparse.Option.CHECK_METHODS[2] = _check_choice
+
+ def process(self, opt, value, values, parser):
+ # First, convert the value(s) to the right type. Howl if any
+ # value(s) are bogus.
+ value = self.convert_value(opt, value)
+ if self.type == 'named':
+ existant = getattr(values, self.dest)
+ if existant:
+ existant.update(value)
+ value = existant
+ # And then take whatever action is expected of us.
+ # This is a separate method to make life easier for
+ # subclasses to add new actions.
+ return self.take_action(
+ self.action, self.dest, opt, value, values, parser)
+
+
+class OptionParser(optparse.OptionParser):
+
+ def __init__(self, option_class=Option, *args, **kwargs):
+ optparse.OptionParser.__init__(self, option_class=Option, *args, **kwargs)
+
+ def format_option_help(self, formatter=None):
+ if formatter is None:
+ formatter = self.formatter
+ outputlevel = getattr(formatter, 'output_level', 0)
+ formatter.store_option_strings(self)
+ result = []
+ result.append(formatter.format_heading("Options"))
+ formatter.indent()
+ if self.option_list:
+ result.append(optparse.OptionContainer.format_option_help(self, formatter))
+ result.append("\n")
+ for group in self.option_groups:
+ if group.level <= outputlevel and (
+ group.description or _level_options(group, outputlevel)):
+ result.append(group.format_help(formatter))
+ result.append("\n")
+ formatter.dedent()
+ # Drop the last "\n", or the header if no options or option groups:
+ return "".join(result[:-1])
+
+
+# pylint: disable=abstract-method; by design?
+class _ManHelpFormatter(optparse.HelpFormatter):
+
+ def __init__(self, indent_increment=0, max_help_position=24,
+ width=79, short_first=0):
+ optparse.HelpFormatter.__init__(
+ self, indent_increment, max_help_position, width, short_first)
+
+ def format_heading(self, heading):
+ return '.SH %s\n' % heading.upper()
+
+ def format_description(self, description):
+ return description
+
+ def format_option(self, option):
+ try:
+ optstring = option.option_strings
+ except AttributeError:
+ optstring = self.format_option_strings(option)
+ if option.help:
+ help_text = self.expand_default(option)
+ help = ' '.join([l.strip() for l in help_text.splitlines()])
+ else:
+ help = ''
+ return '''.IP "%s"
+%s
+''' % (optstring, help)
+
+ def format_head(self, optparser, pkginfo, section=1):
+ long_desc = ""
+ try:
+ pgm = optparser._get_prog_name()
+ except AttributeError:
+ # py >= 2.4.X (dunno which X exactly, at least 2)
+ pgm = optparser.get_prog_name()
+ short_desc = self.format_short_description(pgm, pkginfo.description)
+ if hasattr(pkginfo, "long_desc"):
+ long_desc = self.format_long_description(pgm, pkginfo.long_desc)
+ return '%s\n%s\n%s\n%s' % (self.format_title(pgm, section),
+ short_desc, self.format_synopsis(pgm),
+ long_desc)
+
+ @staticmethod
+ def format_title(pgm, section):
+ date = '-'.join(str(num) for num in time.localtime()[:3])
+ return '.TH %s %s "%s" %s' % (pgm, section, date, pgm)
+
+ @staticmethod
+ def format_short_description(pgm, short_desc):
+ return '''.SH NAME
+.B %s
+\- %s
+''' % (pgm, short_desc.strip())
+
+ @staticmethod
+ def format_synopsis(pgm):
+ return '''.SH SYNOPSIS
+.B %s
+[
+.I OPTIONS
+] [
+.I <arguments>
+]
+''' % pgm
+
+ @staticmethod
+ def format_long_description(pgm, long_desc):
+ long_desc = '\n'.join(line.lstrip()
+ for line in long_desc.splitlines())
+ long_desc = long_desc.replace('\n.\n', '\n\n')
+ if long_desc.lower().startswith(pgm):
+ long_desc = long_desc[len(pgm):]
+ return '''.SH DESCRIPTION
+.B %s
+%s
+''' % (pgm, long_desc.strip())
+
+ @staticmethod
+ def format_tail(pkginfo):
+ tail = '''.SH SEE ALSO
+/usr/share/doc/pythonX.Y-%s/
+
+.SH BUGS
+Please report bugs on the project\'s mailing list:
+%s
+
+.SH AUTHOR
+%s <%s>
+''' % (getattr(pkginfo, 'debian_name', pkginfo.modname),
+ pkginfo.mailinglist, pkginfo.author, pkginfo.author_email)
+
+ if hasattr(pkginfo, "copyright"):
+ tail += '''
+.SH COPYRIGHT
+%s
+''' % pkginfo.copyright
+
+ return tail
+
+
+class OptionsManagerMixIn(object):
+ """Handle configuration from both a configuration file and command line options"""
+
+ def __init__(self, usage, config_file=None, version=None, quiet=0):
+ self.config_file = config_file
+ self.reset_parsers(usage, version=version)
+ # list of registered options providers
+ self.options_providers = []
+ # dictionary associating option name to checker
+ self._all_options = {}
+ self._short_options = {}
+ self._nocallback_options = {}
+ self._mygroups = {}
+ # verbosity
+ self.quiet = quiet
+ self._maxlevel = 0
+
+ def reset_parsers(self, usage='', version=None):
+ # configuration file parser
+ self.cfgfile_parser = configparser.ConfigParser()
+ # command line parser
+ self.cmdline_parser = OptionParser(usage=usage, version=version)
+ self.cmdline_parser.options_manager = self
+ self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS)
+
+ def register_options_provider(self, provider, own_group=True):
+ """register an options provider"""
+ assert provider.priority <= 0, "provider's priority can't be >= 0"
+ for i in range(len(self.options_providers)):
+ if provider.priority > self.options_providers[i].priority:
+ self.options_providers.insert(i, provider)
+ break
+ else:
+ self.options_providers.append(provider)
+ non_group_spec_options = [option for option in provider.options
+ if 'group' not in option[1]]
+ groups = getattr(provider, 'option_groups', ())
+ if own_group and non_group_spec_options:
+ self.add_option_group(provider.name.upper(), provider.__doc__,
+ non_group_spec_options, provider)
+ else:
+ for opt, optdict in non_group_spec_options:
+ self.add_optik_option(provider, self.cmdline_parser, opt, optdict)
+ for gname, gdoc in groups:
+ gname = gname.upper()
+ goptions = [option for option in provider.options
+ if option[1].get('group', '').upper() == gname]
+ self.add_option_group(gname, gdoc, goptions, provider)
+
+ def add_option_group(self, group_name, _, options, provider):
+ # add option group to the command line parser
+ if group_name in self._mygroups:
+ group = self._mygroups[group_name]
+ else:
+ group = optparse.OptionGroup(self.cmdline_parser,
+ title=group_name.capitalize())
+ self.cmdline_parser.add_option_group(group)
+ group.level = provider.level
+ self._mygroups[group_name] = group
+ # add section to the config file
+ if group_name != "DEFAULT":
+ self.cfgfile_parser.add_section(group_name)
+ # add provider's specific options
+ for opt, optdict in options:
+ self.add_optik_option(provider, group, opt, optdict)
+
+ def add_optik_option(self, provider, optikcontainer, opt, optdict):
+ args, optdict = self.optik_option(provider, opt, optdict)
+ option = optikcontainer.add_option(*args, **optdict)
+ self._all_options[opt] = provider
+ self._maxlevel = max(self._maxlevel, option.level or 0)
+
+ def optik_option(self, provider, opt, optdict):
+ """get our personal option definition and return a suitable form for
+ use with optik/optparse
+ """
+ optdict = copy.copy(optdict)
+ if 'action' in optdict:
+ self._nocallback_options[provider] = opt
+ else:
+ optdict['action'] = 'callback'
+ optdict['callback'] = self.cb_set_provider_option
+ # default is handled here and *must not* be given to optik if you
+ # want the whole machinery to work
+ if 'default' in optdict:
+ if ('help' in optdict
+ and optdict.get('default') is not None
+ and optdict['action'] not in ('store_true', 'store_false')):
+ optdict['help'] += ' [current: %default]'
+ del optdict['default']
+ args = ['--' + str(opt)]
+ if 'short' in optdict:
+ self._short_options[optdict['short']] = opt
+ args.append('-' + optdict['short'])
+ del optdict['short']
+ # cleanup option definition dict before giving it to optik
+ for key in list(optdict.keys()):
+ if key not in self._optik_option_attrs:
+ optdict.pop(key)
+ return args, optdict
+
+ def cb_set_provider_option(self, option, opt, value, parser):
+ """optik callback for option setting"""
+ if opt.startswith('--'):
+ # remove -- on long option
+ opt = opt[2:]
+ else:
+ # short option, get its long equivalent
+ opt = self._short_options[opt[1:]]
+ # trick since we can't set action='store_true' on options
+ if value is None:
+ value = 1
+ self.global_set_option(opt, value)
+
+ def global_set_option(self, opt, value):
+ """set option on the correct option provider"""
+ self._all_options[opt].set_option(opt, value)
+
+ def generate_config(self, stream=None, skipsections=(), encoding=None):
+ """write a configuration file according to the current configuration
+ into the given stream or stdout
+ """
+ options_by_section = {}
+ sections = []
+ for provider in self.options_providers:
+ for section, options in provider.options_by_section():
+ if section is None:
+ section = provider.name
+ if section in skipsections:
+ continue
+ options = [(n, d, v) for (n, d, v) in options
+ if d.get('type') is not None]
+ if not options:
+ continue
+ if section not in sections:
+ sections.append(section)
+ alloptions = options_by_section.setdefault(section, [])
+ alloptions += options
+ stream = stream or sys.stdout
+ encoding = utils._get_encoding(encoding, stream)
+ printed = False
+ for section in sections:
+ if printed:
+ print('\n', file=stream)
+ utils.format_section(stream, section.upper(),
+ options_by_section[section],
+ encoding)
+ printed = True
+
+ def generate_manpage(self, pkginfo, section=1, stream=None):
+ with _patch_optparse():
+ _generate_manpage(self.cmdline_parser, pkginfo,
+ section, stream=stream or sys.stdout,
+ level=self._maxlevel)
+
+ def load_provider_defaults(self):
+ """initialize configuration using default values"""
+ for provider in self.options_providers:
+ provider.load_defaults()
+
+ def read_config_file(self, config_file=None):
+ """read the configuration file but do not load it (i.e. dispatching
+ values to each options provider)
+ """
+ helplevel = 1
+ while helplevel <= self._maxlevel:
+ opt = '-'.join(['long'] * helplevel) + '-help'
+ if opt in self._all_options:
+ break # already processed
+ # pylint: disable=unused-argument
+ def helpfunc(option, opt, val, p, level=helplevel):
+ print(self.help(level))
+ sys.exit(0)
+ helpmsg = '%s verbose help.' % ' '.join(['more'] * helplevel)
+ optdict = {'action': 'callback', 'callback': helpfunc,
+ 'help': helpmsg}
+ provider = self.options_providers[0]
+ self.add_optik_option(provider, self.cmdline_parser, opt, optdict)
+ provider.options += ((opt, optdict),)
+ helplevel += 1
+ if config_file is None:
+ config_file = self.config_file
+ if config_file is not None:
+ config_file = os.path.expanduser(config_file)
+ if config_file and os.path.exists(config_file):
+ parser = self.cfgfile_parser
+ parser.read([config_file])
+ # normalize sections'title
+ for sect, values in list(parser._sections.items()):
+ if not sect.isupper() and values:
+ parser._sections[sect.upper()] = values
+ elif not self.quiet:
+ msg = 'No config file found, using default configuration'
+ print(msg, file=sys.stderr)
+ return
+
+ def load_config_file(self):
+ """dispatch values previously read from a configuration file to each
+ options provider)
+ """
+ parser = self.cfgfile_parser
+ for section in parser.sections():
+ for option, value in parser.items(section):
+ try:
+ self.global_set_option(option, value)
+ except (KeyError, optparse.OptionError):
+ # TODO handle here undeclared options appearing in the config file
+ continue
+
+ def load_configuration(self, **kwargs):
+ """override configuration according to given parameters"""
+ for opt, opt_value in kwargs.items():
+ opt = opt.replace('_', '-')
+ provider = self._all_options[opt]
+ provider.set_option(opt, opt_value)
+
+ def load_command_line_configuration(self, args=None):
+ """Override configuration according to command line parameters
+
+ return additional arguments
+ """
+ with _patch_optparse():
+ if args is None:
+ args = sys.argv[1:]
+ else:
+ args = list(args)
+ (options, args) = self.cmdline_parser.parse_args(args=args)
+ for provider in self._nocallback_options.keys():
+ config = provider.config
+ for attr in config.__dict__.keys():
+ value = getattr(options, attr, None)
+ if value is None:
+ continue
+ setattr(config, attr, value)
+ return args
+
+ def add_help_section(self, title, description, level=0):
+ """add a dummy option section for help purpose """
+ group = optparse.OptionGroup(self.cmdline_parser,
+ title=title.capitalize(),
+ description=description)
+ group.level = level
+ self._maxlevel = max(self._maxlevel, level)
+ self.cmdline_parser.add_option_group(group)
+
+ def help(self, level=0):
+ """return the usage string for available options """
+ self.cmdline_parser.formatter.output_level = level
+ with _patch_optparse():
+ return self.cmdline_parser.format_help()
+
+
+class OptionsProviderMixIn(object):
+ """Mixin to provide options to an OptionsManager"""
+
+ # those attributes should be overridden
+ priority = -1
+ name = 'default'
+ options = ()
+ level = 0
+
+ def __init__(self):
+ self.config = optparse.Values()
+ self.load_defaults()
+
+ def load_defaults(self):
+ """initialize the provider using default values"""
+ for opt, optdict in self.options:
+ action = optdict.get('action')
+ if action != 'callback':
+ # callback action have no default
+ if optdict is None:
+ optdict = self.get_option_def(opt)
+ default = optdict.get('default')
+ self.set_option(opt, default, action, optdict)
+
+ def option_attrname(self, opt, optdict=None):
+ """get the config attribute corresponding to opt"""
+ if optdict is None:
+ optdict = self.get_option_def(opt)
+ return optdict.get('dest', opt.replace('-', '_'))
+
+ def option_value(self, opt):
+ """get the current value for the given option"""
+ return getattr(self.config, self.option_attrname(opt), None)
+
+ def set_option(self, opt, value, action=None, optdict=None):
+ """method called to set an option (registered in the options list)"""
+ if optdict is None:
+ optdict = self.get_option_def(opt)
+ if value is not None:
+ value = _validate(value, optdict, opt)
+ if action is None:
+ action = optdict.get('action', 'store')
+ if action == 'store':
+ setattr(self.config, self.option_attrname(opt, optdict), value)
+ elif action in ('store_true', 'count'):
+ setattr(self.config, self.option_attrname(opt, optdict), 0)
+ elif action == 'store_false':
+ setattr(self.config, self.option_attrname(opt, optdict), 1)
+ elif action == 'append':
+ opt = self.option_attrname(opt, optdict)
+ _list = getattr(self.config, opt, None)
+ if _list is None:
+ if isinstance(value, (list, tuple)):
+ _list = value
+ elif value is not None:
+ _list = []
+ _list.append(value)
+ setattr(self.config, opt, _list)
+ elif isinstance(_list, tuple):
+ setattr(self.config, opt, _list + (value,))
+ else:
+ _list.append(value)
+ elif action == 'callback':
+ optdict['callback'](None, opt, value, None)
+ else:
+ raise UnsupportedAction(action)
+
+ def get_option_def(self, opt):
+ """return the dictionary defining an option given its name"""
+ assert self.options
+ for option in self.options:
+ if option[0] == opt:
+ return option[1]
+ raise optparse.OptionError('no such option %s in section %r'
+ % (opt, self.name), opt)
+
+ def options_by_section(self):
+ """return an iterator on options grouped by section
+
+ (section, [list of (optname, optdict, optvalue)])
+ """
+ sections = {}
+ for optname, optdict in self.options:
+ sections.setdefault(optdict.get('group'), []).append(
+ (optname, optdict, self.option_value(optname)))
+ if None in sections:
+ yield None, sections.pop(None)
+ for section, options in sections.items():
+ yield section.upper(), options
+
+ def options_and_values(self, options=None):
+ if options is None:
+ options = self.options
+ for optname, optdict in options:
+ yield (optname, optdict, self.option_value(optname))
+
+
+class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn):
+ """basic mixin for simple configurations which don't need the
+ manager / providers model
+ """
+ def __init__(self, *args, **kwargs):
+ if not args:
+ kwargs.setdefault('usage', '')
+ kwargs.setdefault('quiet', 1)
+ OptionsManagerMixIn.__init__(self, *args, **kwargs)
+ OptionsProviderMixIn.__init__(self)
+ if not getattr(self, 'option_groups', None):
+ self.option_groups = []
+ for _, optdict in self.options:
+ try:
+ gdef = (optdict['group'].upper(), '')
+ except KeyError:
+ continue
+ if gdef not in self.option_groups:
+ self.option_groups.append(gdef)
+ self.register_options_provider(self, own_group=False)
+
+
+def _generate_manpage(optparser, pkginfo, section=1,
+ stream=sys.stdout, level=0):
+ formatter = _ManHelpFormatter()
+ formatter.output_level = level
+ formatter.parser = optparser
+ print(formatter.format_head(optparser, pkginfo, section), file=stream)
+ print(optparser.format_option_help(formatter), file=stream)
+ print(formatter.format_tail(pkginfo), file=stream)
diff --git a/pylint/lint.py b/pylint/lint.py
index 5fd3d10..0bc3234 100644
--- a/pylint/lint.py
+++ b/pylint/lint.py
@@ -42,7 +42,6 @@ import warnings
import astroid
from astroid.__pkginfo__ import version as astroid_version
from astroid import modutils
-from logilab.common import configuration
from logilab.common import ureports
import six
@@ -245,7 +244,7 @@ if multiprocessing is not None:
msgs, linter.stats, linter.msg_status)
-class PyLinter(configuration.OptionsManagerMixIn,
+class PyLinter(config.OptionsManagerMixIn,
utils.MessagesHandlerMixIn,
utils.ReportsHandlerMixIn,
checkers.BaseTokenChecker):
@@ -442,12 +441,12 @@ class PyLinter(configuration.OptionsManagerMixIn,
'enable-msg': self.enable}
full_version = '%%prog %s, \nastroid %s\nPython %s' % (
version, astroid_version, sys.version)
- configuration.OptionsManagerMixIn.__init__(
- self, usage=__doc__,
- version=full_version,
- config_file=pylintrc or config.PYLINTRC)
utils.MessagesHandlerMixIn.__init__(self)
utils.ReportsHandlerMixIn.__init__(self)
+ super(PyLinter, self).__init__(
+ usage=__doc__,
+ version=full_version,
+ config_file=pylintrc or config.PYLINTRC)
checkers.BaseTokenChecker.__init__(self)
# provided reports
self.reports = (('RP0001', 'Messages by category',
@@ -504,7 +503,7 @@ class PyLinter(configuration.OptionsManagerMixIn,
reporter.linter = self
def set_option(self, optname, value, action=None, optdict=None):
- """overridden from configuration.OptionsProviderMixin to handle some
+ """overridden from config.OptionsProviderMixin to handle some
special options
"""
if optname in self._options_methods or \
@@ -534,7 +533,7 @@ class PyLinter(configuration.OptionsManagerMixIn,
try:
checkers.BaseTokenChecker.set_option(self, optname,
value, action, optdict)
- except configuration.UnsupportedAction:
+ except config.UnsupportedAction:
print('option %s can\'t be read from config file' % \
optname, file=sys.stderr)
@@ -746,7 +745,7 @@ class PyLinter(configuration.OptionsManagerMixIn,
for opt_providers in six.itervalues(self._all_options):
for optname, optdict, val in opt_providers.options_and_values():
if optname not in filter_options:
- child_config[optname] = configuration.format_option_value(
+ child_config[optname] = utils._format_option_value(
optdict, val)
child_config['python3_porting_mode'] = self._python3_porting_mode
child_config['plugins'] = self._dynamic_plugins
diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py
index cb03435..0e7f87e 100644
--- a/pylint/pyreverse/main.py
+++ b/pylint/pyreverse/main.py
@@ -20,9 +20,10 @@
"""
from __future__ import print_function
-import sys, os
-from logilab.common.configuration import ConfigurationMixIn
+import os
+import sys
+from pylint.config import ConfigurationMixIn
from pylint.pyreverse.inspector import Linker, project_from_files
from pylint.pyreverse.diadefslib import DiadefsHandler
from pylint.pyreverse import writer
diff --git a/pylint/test/test_functional.py b/pylint/test/test_functional.py
index e9d6a9a..09d3934 100644
--- a/pylint/test/test_functional.py
+++ b/pylint/test/test_functional.py
@@ -225,7 +225,8 @@ class LintModuleTest(unittest.TestCase):
checkers.initialize(self._linter)
self._linter.disable('I')
try:
- self._linter.load_file_configuration(test_file.option_file)
+ self._linter.read_config_file(test_file.option_file)
+ self._linter.load_config_file()
except NoFileError:
pass
self._test_file = test_file
diff --git a/pylint/utils.py b/pylint/utils.py
index c918783..356868b 100644
--- a/pylint/utils.py
+++ b/pylint/utils.py
@@ -30,7 +30,6 @@ from os.path import dirname, basename, splitext, exists, isdir, join, normpath
import six
from six.moves import zip # pylint: disable=redefined-builtin
-from logilab.common.configuration import rest_format_section
from logilab.common.ureports import Section
from astroid import nodes, Module
@@ -456,7 +455,7 @@ class MessagesHandlerMixIn(object):
title = '%s options' % section.capitalize()
print(title)
print('~' * len(title))
- rest_format_section(sys.stdout, None, options)
+ _rest_format_section(sys.stdout, None, options)
print("")
else:
try:
@@ -491,7 +490,7 @@ class MessagesHandlerMixIn(object):
title = 'Options'
print(title)
print('^' * len(title))
- rest_format_section(sys.stdout, None, options)
+ _rest_format_section(sys.stdout, None, options)
print("")
if msgs:
title = 'Messages'
@@ -1048,3 +1047,92 @@ def _check_csv(value):
if isinstance(value, (list, tuple)):
return value
return _splitstrip(value)
+
+
+if six.PY2:
+ def _encode(string, encoding):
+ # pylint: disable=undefined-variable
+ if isinstance(string, unicode):
+ return string.encode(encoding)
+ return str(string)
+else:
+ def _encode(string, _):
+ return str(string)
+
+def _get_encoding(encoding, stream):
+ encoding = encoding or getattr(stream, 'encoding', None)
+ if not encoding:
+ import locale
+ encoding = locale.getpreferredencoding()
+ return encoding
+
+
+def _comment(string):
+ """return string as a comment"""
+ lines = [line.strip() for line in string.splitlines()]
+ return '# ' + ('%s# ' % os.linesep).join(lines)
+
+
+def _format_option_value(optdict, value):
+ """return the user input's value from a 'compiled' value"""
+ if isinstance(value, (list, tuple)):
+ value = ','.join(value)
+ elif isinstance(value, dict):
+ value = ','.join('%s:%s' % (k, v) for k, v in value.items())
+ elif hasattr(value, 'match'): # optdict.get('type') == 'regexp'
+ # compiled regexp
+ value = value.pattern
+ elif optdict.get('type') == 'yn':
+ value = value and 'yes' or 'no'
+ elif isinstance(value, six.string_types) and value.isspace():
+ value = "'%s'" % value
+ return value
+
+
+def _ini_format_section(stream, section, options, encoding=None, doc=None):
+ """format an options section using the INI format"""
+ encoding = _get_encoding(encoding, stream)
+ if doc:
+ print(_encode(_comment(doc), encoding), file=stream)
+ print('[%s]' % section, file=stream)
+ _ini_format(stream, options, encoding)
+
+
+def _ini_format(stream, options, encoding):
+ """format options using the INI format"""
+ for optname, optdict, value in options:
+ value = _format_option_value(optdict, value)
+ help = optdict.get('help')
+ if help:
+ help = _normalize_text(help, line_len=79, indent='# ')
+ print(file=stream)
+ print(_encode(help, encoding), file=stream)
+ else:
+ print(file=stream)
+ if value is None:
+ print('#%s=' % optname, file=stream)
+ else:
+ value = _encode(value, encoding).strip()
+ print('%s=%s' % (optname, value), file=stream)
+
+format_section = _ini_format_section
+
+
+def _rest_format_section(stream, section, options, encoding=None, doc=None):
+ """format an options section using as ReST formatted output"""
+ encoding = _get_encoding(encoding, stream)
+ if section:
+ print('%s\n%s' % (section, "'"*len(section)), file=stream)
+ if doc:
+ print(_encode(_normalize_text(doc, line_len=79, indent=''), encoding), file=stream)
+ print(file=stream)
+ for optname, optdict, value in options:
+ help = optdict.get('help')
+ print(':%s:' % optname, file=stream)
+ if help:
+ help = _normalize_text(help, line_len=79, indent=' ')
+ print(_encode(help, encoding), file=stream)
+ if value:
+ value = _encode(_format_option_value(optdict, value), encoding)
+ print(file=stream)
+ print(' Default: ``%s``' % value.replace("`` ", "```` ``"), file=stream)