Use black to reformat Flake8

Instead of just using Flake8 and pylint to keep Flake8 clean, let's also
use black to make it less manual for clean-up.
This commit is contained in:
Ian Stapleton Cordasco 2018-10-20 07:31:42 -05:00
parent a2b7a7e4c5
commit c58a4662d8
No known key found for this signature in database
GPG key ID: C9D7A2604B4FCB2A
27 changed files with 1052 additions and 812 deletions

View file

@ -40,7 +40,7 @@ python37:
script: tox -e py37 script: tox -e py37
linters: linters:
image: python:3.5 image: python:3.6
stage: test stage: test
script: tox -e linters script: tox -e linters

View file

@ -15,14 +15,16 @@ import sys
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LOG.addHandler(logging.NullHandler()) LOG.addHandler(logging.NullHandler())
__version__ = '3.5.0' __version__ = "3.5.0"
__version_info__ = tuple(int(i) for i in __version__.split('.') if i.isdigit()) __version_info__ = tuple(
int(i) for i in __version__.split(".") if i.isdigit()
)
# There is nothing lower than logging.DEBUG (10) in the logging library, # There is nothing lower than logging.DEBUG (10) in the logging library,
# but we want an extra level to avoid being too verbose when using -vv. # but we want an extra level to avoid being too verbose when using -vv.
_EXTRA_VERBOSE = 5 _EXTRA_VERBOSE = 5
logging.addLevelName(_EXTRA_VERBOSE, 'VERBOSE') logging.addLevelName(_EXTRA_VERBOSE, "VERBOSE")
_VERBOSITY_TO_LOG_LEVEL = { _VERBOSITY_TO_LOG_LEVEL = {
# output more than warnings but not debugging info # output more than warnings but not debugging info
@ -33,8 +35,10 @@ _VERBOSITY_TO_LOG_LEVEL = {
3: _EXTRA_VERBOSE, 3: _EXTRA_VERBOSE,
} }
LOG_FORMAT = ('%(name)-25s %(processName)-11s %(relativeCreated)6d ' LOG_FORMAT = (
'%(levelname)-8s %(message)s') "%(name)-25s %(processName)-11s %(relativeCreated)6d "
"%(levelname)-8s %(message)s"
)
def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT): def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT):
@ -55,8 +59,8 @@ def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT):
log_level = _VERBOSITY_TO_LOG_LEVEL[verbosity] log_level = _VERBOSITY_TO_LOG_LEVEL[verbosity]
if not filename or filename in ('stderr', 'stdout'): if not filename or filename in ("stderr", "stdout"):
fileobj = getattr(sys, filename or 'stderr') fileobj = getattr(sys, filename or "stderr")
handler_cls = logging.StreamHandler handler_cls = logging.StreamHandler
else: else:
fileobj = filename fileobj = filename
@ -66,5 +70,6 @@ def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT):
handler.setFormatter(logging.Formatter(logformat)) handler.setFormatter(logging.Formatter(logformat))
LOG.addHandler(handler) LOG.addHandler(handler)
LOG.setLevel(log_level) LOG.setLevel(log_level)
LOG.debug('Added a %s logging handler to logger root at %s', LOG.debug(
filename, __name__) "Added a %s logging handler to logger root at %s", filename, __name__
)

View file

@ -13,7 +13,7 @@ from flake8.main import application as app
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
__all__ = ('get_style_guide',) __all__ = ("get_style_guide",)
def get_style_guide(**kwargs): def get_style_guide(**kwargs):
@ -29,7 +29,8 @@ def get_style_guide(**kwargs):
application = app.Application() application = app.Application()
application.parse_preliminary_options_and_args([]) application.parse_preliminary_options_and_args([])
flake8.configure_logging( flake8.configure_logging(
application.prelim_opts.verbose, application.prelim_opts.output_file) application.prelim_opts.verbose, application.prelim_opts.output_file
)
application.make_config_finder() application.make_config_finder()
application.find_plugins() application.find_plugins()
application.register_plugin_options() application.register_plugin_options()
@ -113,18 +114,22 @@ class StyleGuide(object):
:rtype: :rtype:
bool bool
""" """
return (self._file_checker_manager.is_path_excluded(filename) or return self._file_checker_manager.is_path_excluded(filename) or (
(parent and parent
self._file_checker_manager.is_path_excluded( and self._file_checker_manager.is_path_excluded(
os.path.join(parent, filename)))) os.path.join(parent, filename)
)
)
def init_report(self, reporter=None): def init_report(self, reporter=None):
"""Set up a formatter for this run of Flake8.""" """Set up a formatter for this run of Flake8."""
if reporter is None: if reporter is None:
return return
if not issubclass(reporter, formatter.BaseFormatter): if not issubclass(reporter, formatter.BaseFormatter):
raise ValueError("Report should be subclass of " raise ValueError(
"flake8.formatter.BaseFormatter.") "Report should be subclass of "
"flake8.formatter.BaseFormatter."
)
self._application.formatter = None self._application.formatter = None
self._application.make_formatter(reporter) self._application.make_formatter(reporter)
self._application.guide = None self._application.guide = None
@ -197,6 +202,6 @@ class Report(object):
list list
""" """
return [ return [
'{} {} {}'.format(s.count, s.error_code, s.message) "{} {} {}".format(s.count, s.error_code, s.message)
for s in self._stats.statistics_for(violation) for s in self._stats.statistics_for(violation)
] ]

View file

@ -77,17 +77,17 @@ class Manager(object):
self.processes = [] self.processes = []
self.checkers = [] self.checkers = []
self.statistics = { self.statistics = {
'files': 0, "files": 0,
'logical lines': 0, "logical lines": 0,
'physical lines': 0, "physical lines": 0,
'tokens': 0, "tokens": 0,
} }
def _process_statistics(self): def _process_statistics(self):
for checker in self.checkers: for checker in self.checkers:
for statistic in defaults.STATISTIC_NAMES: for statistic in defaults.STATISTIC_NAMES:
self.statistics[statistic] += checker.statistics[statistic] self.statistics[statistic] += checker.statistics[statistic]
self.statistics['files'] += len(self.checkers) self.statistics["files"] += len(self.checkers)
def _job_count(self): def _job_count(self):
# type: () -> int # type: () -> int
@ -101,40 +101,53 @@ class Manager(object):
# multiprocessing and which really shouldn't require multiprocessing # multiprocessing and which really shouldn't require multiprocessing
# - the user provided some awful input # - the user provided some awful input
if not multiprocessing: if not multiprocessing:
LOG.warning('The multiprocessing module is not available. ' LOG.warning(
'Ignoring --jobs arguments.') "The multiprocessing module is not available. "
"Ignoring --jobs arguments."
)
return 0 return 0
if (utils.is_windows() and if (
not utils.can_run_multiprocessing_on_windows()): utils.is_windows()
LOG.warning('The --jobs option is not available on Windows due to' and not utils.can_run_multiprocessing_on_windows()
' a bug (https://bugs.python.org/issue27649) in ' ):
'Python 2.7.11+ and 3.3+. We have detected that you ' LOG.warning(
'are running an unsupported version of Python on ' "The --jobs option is not available on Windows due to"
'Windows. Ignoring --jobs arguments.') " a bug (https://bugs.python.org/issue27649) in "
"Python 2.7.11+ and 3.3+. We have detected that you "
"are running an unsupported version of Python on "
"Windows. Ignoring --jobs arguments."
)
return 0 return 0
if utils.is_using_stdin(self.arguments): if utils.is_using_stdin(self.arguments):
LOG.warning('The --jobs option is not compatible with supplying ' LOG.warning(
'input using - . Ignoring --jobs arguments.') "The --jobs option is not compatible with supplying "
"input using - . Ignoring --jobs arguments."
)
return 0 return 0
if self.options.diff: if self.options.diff:
LOG.warning('The --diff option was specified with --jobs but ' LOG.warning(
'they are not compatible. Ignoring --jobs arguments.') "The --diff option was specified with --jobs but "
"they are not compatible. Ignoring --jobs arguments."
)
return 0 return 0
jobs = self.options.jobs jobs = self.options.jobs
if jobs != 'auto' and not jobs.isdigit(): if jobs != "auto" and not jobs.isdigit():
LOG.warning('"%s" is not a valid parameter to --jobs. Must be one ' LOG.warning(
'of "auto" or a numerical value, e.g., 4.', jobs) '"%s" is not a valid parameter to --jobs. Must be one '
'of "auto" or a numerical value, e.g., 4.',
jobs,
)
return 0 return 0
# If the value is "auto", we want to let the multiprocessing library # If the value is "auto", we want to let the multiprocessing library
# decide the number based on the number of CPUs. However, if that # decide the number based on the number of CPUs. However, if that
# function is not implemented for this particular value of Python we # function is not implemented for this particular value of Python we
# default to 1 # default to 1
if jobs == 'auto': if jobs == "auto":
try: try:
return multiprocessing.cpu_count() return multiprocessing.cpu_count()
except NotImplementedError: except NotImplementedError:
@ -170,8 +183,8 @@ class Manager(object):
:rtype: :rtype:
bool bool
""" """
if path == '-': if path == "-":
if self.options.stdin_display_name == 'stdin': if self.options.stdin_display_name == "stdin":
return False return False
path = self.options.stdin_display_name path = self.options.stdin_display_name
@ -185,8 +198,9 @@ class Manager(object):
absolute_path = os.path.abspath(path) absolute_path = os.path.abspath(path)
match = utils.fnmatch(absolute_path, exclude) match = utils.fnmatch(absolute_path, exclude)
LOG.debug('"%s" has %sbeen excluded', absolute_path, LOG.debug(
'' if match else 'not ') '"%s" has %sbeen excluded', absolute_path, "" if match else "not "
)
return match return match
def make_checkers(self, paths=None): def make_checkers(self, paths=None):
@ -196,7 +210,7 @@ class Manager(object):
paths = self.arguments paths = self.arguments
if not paths: if not paths:
paths = ['.'] paths = ["."]
filename_patterns = self.options.filename filename_patterns = self.options.filename
running_from_vcs = self.options._running_from_vcs running_from_vcs = self.options._running_from_vcs
@ -209,7 +223,7 @@ class Manager(object):
matches_filename_patterns = utils.fnmatch( matches_filename_patterns = utils.fnmatch(
filename, filename_patterns filename, filename_patterns
) )
is_stdin = filename == '-' is_stdin = filename == "-"
# NOTE(sigmavirus24): If a user explicitly specifies something, # NOTE(sigmavirus24): If a user explicitly specifies something,
# e.g, ``flake8 bin/script`` then we should run Flake8 against # e.g, ``flake8 bin/script`` then we should run Flake8 against
# that. Since should_create_file_checker looks to see if the # that. Since should_create_file_checker looks to see if the
@ -217,24 +231,28 @@ class Manager(object):
# the event that the argument and the filename are identical. # the event that the argument and the filename are identical.
# If it was specified explicitly, the user intended for it to be # If it was specified explicitly, the user intended for it to be
# checked. # checked.
explicitly_provided = (not running_from_vcs and explicitly_provided = (
not running_from_diff and not running_from_vcs
(argument == filename)) and not running_from_diff
return ((explicitly_provided or matches_filename_patterns) or and (argument == filename)
is_stdin) )
return (
explicitly_provided or matches_filename_patterns
) or is_stdin
checks = self.checks.to_dictionary() checks = self.checks.to_dictionary()
checkers = ( checkers = (
FileChecker(filename, checks, self.options) FileChecker(filename, checks, self.options)
for argument in paths for argument in paths
for filename in utils.filenames_from(argument, for filename in utils.filenames_from(
self.is_path_excluded) argument, self.is_path_excluded
)
if should_create_file_checker(filename, argument) if should_create_file_checker(filename, argument)
) )
self.checkers = [ self.checkers = [
checker for checker in checkers if checker.should_process checker for checker in checkers if checker.should_process
] ]
LOG.info('Checking %d files', len(self.checkers)) LOG.info("Checking %d files", len(self.checkers))
def report(self): def report(self):
# type: () -> (int, int) # type: () -> (int, int)
@ -250,7 +268,9 @@ class Manager(object):
""" """
results_reported = results_found = 0 results_reported = results_found = 0
for checker in self.checkers: for checker in self.checkers:
results = sorted(checker.results, key=lambda tup: (tup[1], tup[2])) results = sorted(
checker.results, key=lambda tup: (tup[1], tup[2])
)
filename = checker.display_name filename = checker.display_name
with self.style_guide.processing_file(filename): with self.style_guide.processing_file(filename):
results_reported += self._handle_results(filename, results) results_reported += self._handle_results(filename, results)
@ -276,8 +296,7 @@ class Manager(object):
_run_checks, _run_checks,
self.checkers, self.checkers,
chunksize=calculate_pool_chunksize( chunksize=calculate_pool_chunksize(
len(self.checkers), len(self.checkers), self.jobs
self.jobs,
), ),
) )
for ret in pool_map: for ret in pool_map:
@ -294,8 +313,9 @@ class Manager(object):
for checker in self.checkers: for checker in self.checkers:
filename = checker.display_name filename = checker.display_name
checker.results = sorted(final_results[filename], checker.results = sorted(
key=lambda tup: (tup[2], tup[2])) final_results[filename], key=lambda tup: (tup[2], tup[2])
)
checker.statistics = final_statistics[filename] checker.statistics = final_statistics[filename]
def run_serial(self): def run_serial(self):
@ -322,11 +342,11 @@ class Manager(object):
if oserr.errno not in SERIAL_RETRY_ERRNOS: if oserr.errno not in SERIAL_RETRY_ERRNOS:
LOG.exception(oserr) LOG.exception(oserr)
raise raise
LOG.warning('Running in serial after OS exception, %r', oserr) LOG.warning("Running in serial after OS exception, %r", oserr)
self.run_serial() self.run_serial()
except KeyboardInterrupt: except KeyboardInterrupt:
LOG.warning('Flake8 was interrupted by the user') LOG.warning("Flake8 was interrupted by the user")
raise exceptions.EarlyQuit('Early quit while running checks') raise exceptions.EarlyQuit("Early quit while running checks")
def start(self, paths=None): def start(self, paths=None):
"""Start checking files. """Start checking files.
@ -335,14 +355,14 @@ class Manager(object):
Path names to check. This is passed directly to Path names to check. This is passed directly to
:meth:`~Manager.make_checkers`. :meth:`~Manager.make_checkers`.
""" """
LOG.info('Making checkers') LOG.info("Making checkers")
self.make_checkers(paths) self.make_checkers(paths)
def stop(self): def stop(self):
"""Stop checking files.""" """Stop checking files."""
self._process_statistics() self._process_statistics()
for proc in self.processes: for proc in self.processes:
LOG.info('Joining %s to the main process', proc.name) LOG.info("Joining %s to the main process", proc.name)
proc.join() proc.join()
@ -368,9 +388,9 @@ class FileChecker(object):
self.checks = checks self.checks = checks
self.results = [] self.results = []
self.statistics = { self.statistics = {
'tokens': 0, "tokens": 0,
'logical lines': 0, "logical lines": 0,
'physical lines': 0, "physical lines": 0,
} }
self.processor = self._make_processor() self.processor = self._make_processor()
self.display_name = filename self.display_name = filename
@ -378,11 +398,11 @@ class FileChecker(object):
if self.processor is not None: if self.processor is not None:
self.display_name = self.processor.filename self.display_name = self.processor.filename
self.should_process = not self.processor.should_ignore_file() self.should_process = not self.processor.should_ignore_file()
self.statistics['physical lines'] = len(self.processor.lines) self.statistics["physical lines"] = len(self.processor.lines)
def __repr__(self): def __repr__(self):
"""Provide helpful debugging representation.""" """Provide helpful debugging representation."""
return 'FileChecker for {}'.format(self.filename) return "FileChecker for {}".format(self.filename)
def _make_processor(self): def _make_processor(self):
try: try:
@ -395,20 +415,20 @@ class FileChecker(object):
# as an E902. We probably *want* a better error code for this # as an E902. We probably *want* a better error code for this
# going forward. # going forward.
(exc_type, exception) = sys.exc_info()[:2] (exc_type, exception) = sys.exc_info()[:2]
message = '{0}: {1}'.format(exc_type.__name__, exception) message = "{0}: {1}".format(exc_type.__name__, exception)
self.report('E902', 0, 0, message) self.report("E902", 0, 0, message)
return None return None
def report(self, error_code, line_number, column, text, line=None): def report(self, error_code, line_number, column, text, line=None):
# type: (str, int, int, str) -> str # type: (str, int, int, str) -> str
"""Report an error by storing it in the results list.""" """Report an error by storing it in the results list."""
if error_code is None: if error_code is None:
error_code, text = text.split(' ', 1) error_code, text = text.split(" ", 1)
physical_line = line physical_line = line
# If we're recovering from a problem in _make_processor, we will not # If we're recovering from a problem in _make_processor, we will not
# have this attribute. # have this attribute.
if not physical_line and getattr(self, 'processor', None): if not physical_line and getattr(self, "processor", None):
physical_line = self.processor.line_for(line_number) physical_line = self.processor.line_for(line_number)
error = (error_code, line_number, column, text, physical_line) error = (error_code, line_number, column, text, physical_line)
@ -417,26 +437,24 @@ class FileChecker(object):
def run_check(self, plugin, **arguments): def run_check(self, plugin, **arguments):
"""Run the check in a single plugin.""" """Run the check in a single plugin."""
LOG.debug('Running %r with %r', plugin, arguments) LOG.debug("Running %r with %r", plugin, arguments)
try: try:
self.processor.keyword_arguments_for( self.processor.keyword_arguments_for(
plugin['parameters'], plugin["parameters"], arguments
arguments,
) )
except AttributeError as ae: except AttributeError as ae:
LOG.error('Plugin requested unknown parameters.') LOG.error("Plugin requested unknown parameters.")
raise exceptions.PluginRequestedUnknownParameters( raise exceptions.PluginRequestedUnknownParameters(
plugin=plugin, plugin=plugin, exception=ae
exception=ae,
) )
try: try:
return plugin['plugin'](**arguments) return plugin["plugin"](**arguments)
except Exception as all_exc: except Exception as all_exc:
LOG.critical('Plugin %s raised an unexpected exception', LOG.critical(
plugin['name']) "Plugin %s raised an unexpected exception", plugin["name"]
)
raise exceptions.PluginExecutionFailed( raise exceptions.PluginExecutionFailed(
plugin=plugin, plugin=plugin, excetion=all_exc
excetion=all_exc,
) )
@staticmethod @staticmethod
@ -466,7 +484,7 @@ class FileChecker(object):
# "physical" line so much as what was accumulated by the point # "physical" line so much as what was accumulated by the point
# tokenizing failed. # tokenizing failed.
# See also: https://gitlab.com/pycqa/flake8/issues/237 # See also: https://gitlab.com/pycqa/flake8/issues/237
lines = physical_line.rstrip('\n').split('\n') lines = physical_line.rstrip("\n").split("\n")
row_offset = len(lines) - 1 row_offset = len(lines) - 1
logical_line = lines[0] logical_line = lines[0]
logical_line_length = len(logical_line) logical_line_length = len(logical_line)
@ -483,11 +501,15 @@ class FileChecker(object):
except (ValueError, SyntaxError, TypeError): except (ValueError, SyntaxError, TypeError):
(exc_type, exception) = sys.exc_info()[:2] (exc_type, exception) = sys.exc_info()[:2]
row, column = self._extract_syntax_information(exception) row, column = self._extract_syntax_information(exception)
self.report('E999', row, column, '%s: %s' % self.report(
(exc_type.__name__, exception.args[0])) "E999",
row,
column,
"%s: %s" % (exc_type.__name__, exception.args[0]),
)
return return
for plugin in self.checks['ast_plugins']: for plugin in self.checks["ast_plugins"]:
checker = self.run_check(plugin, tree=ast) checker = self.run_check(plugin, tree=ast)
# If the plugin uses a class, call the run method of it, otherwise # If the plugin uses a class, call the run method of it, otherwise
# the call should return something iterable itself # the call should return something iterable itself
@ -512,7 +534,7 @@ class FileChecker(object):
LOG.debug('Logical line: "%s"', logical_line.rstrip()) LOG.debug('Logical line: "%s"', logical_line.rstrip())
for plugin in self.checks['logical_line_plugins']: for plugin in self.checks["logical_line_plugins"]:
self.processor.update_checker_state_for(plugin) self.processor.update_checker_state_for(plugin)
results = self.run_check(plugin, logical_line=logical_line) or () results = self.run_check(plugin, logical_line=logical_line) or ()
for offset, text in results: for offset, text in results:
@ -529,7 +551,7 @@ class FileChecker(object):
def run_physical_checks(self, physical_line, override_error_line=None): def run_physical_checks(self, physical_line, override_error_line=None):
"""Run all checks for a given physical line.""" """Run all checks for a given physical line."""
for plugin in self.checks['physical_line_plugins']: for plugin in self.checks["physical_line_plugins"]:
self.processor.update_checker_state_for(plugin) self.processor.update_checker_state_for(plugin)
result = self.run_check(plugin, physical_line=physical_line) result = self.run_check(plugin, physical_line=physical_line)
if result is not None: if result is not None:
@ -555,7 +577,7 @@ class FileChecker(object):
statistics = self.statistics statistics = self.statistics
file_processor = self.processor file_processor = self.processor
for token in file_processor.generate_tokens(): for token in file_processor.generate_tokens():
statistics['tokens'] += 1 statistics["tokens"] += 1
self.check_physical_eol(token) self.check_physical_eol(token)
token_type, text = token[0:2] token_type, text = token[0:2]
processor.log_token(LOG, token) processor.log_token(LOG, token)
@ -564,8 +586,10 @@ class FileChecker(object):
elif parens == 0: elif parens == 0:
if processor.token_is_newline(token): if processor.token_is_newline(token):
self.handle_newline(token_type) self.handle_newline(token_type)
elif (processor.token_is_comment(token) and elif (
len(file_processor.tokens) == 1): processor.token_is_comment(token)
and len(file_processor.tokens) == 1
):
self.handle_comment(token, text) self.handle_comment(token, text)
if file_processor.tokens: if file_processor.tokens:
@ -578,20 +602,24 @@ class FileChecker(object):
try: try:
self.process_tokens() self.process_tokens()
except exceptions.InvalidSyntax as exc: except exceptions.InvalidSyntax as exc:
self.report(exc.error_code, exc.line_number, exc.column_number, self.report(
exc.error_message) exc.error_code,
exc.line_number,
exc.column_number,
exc.error_message,
)
self.run_ast_checks() self.run_ast_checks()
logical_lines = self.processor.statistics['logical lines'] logical_lines = self.processor.statistics["logical lines"]
self.statistics['logical lines'] = logical_lines self.statistics["logical lines"] = logical_lines
return self.filename, self.results, self.statistics return self.filename, self.results, self.statistics
def handle_comment(self, token, token_text): def handle_comment(self, token, token_text):
"""Handle the logic when encountering a comment token.""" """Handle the logic when encountering a comment token."""
# The comment also ends a physical line # The comment also ends a physical line
token = list(token) token = list(token)
token[1] = token_text.rstrip('\r\n') token[1] = token_text.rstrip("\r\n")
token[3] = (token[2][0], token[2][1] + len(token[1])) token[3] = (token[2][0], token[2][1] + len(token[1]))
self.processor.tokens = [tuple(token)] self.processor.tokens = [tuple(token)]
self.run_logical_checks() self.run_logical_checks()
@ -628,8 +656,9 @@ class FileChecker(object):
line_no = token[2][0] line_no = token[2][0]
with self.processor.inside_multiline(line_number=line_no): with self.processor.inside_multiline(line_number=line_no):
for line in self.processor.split_line(token): for line in self.processor.split_line(token):
self.run_physical_checks(line + '\n', self.run_physical_checks(
override_error_line=token[4]) line + "\n", override_error_line=token[4]
)
def _pool_init(): def _pool_init():

View file

@ -2,39 +2,26 @@
import re import re
EXCLUDE = ( EXCLUDE = (
'.svn', ".svn",
'CVS', "CVS",
'.bzr', ".bzr",
'.hg', ".hg",
'.git', ".git",
'__pycache__', "__pycache__",
'.tox', ".tox",
'.eggs', ".eggs",
'*.egg', "*.egg",
) )
IGNORE = ( IGNORE = ("E121", "E123", "E126", "E226", "E24", "E704", "W503", "W504")
'E121', SELECT = ("E", "F", "W", "C90")
'E123',
'E126',
'E226',
'E24',
'E704',
'W503',
'W504',
)
SELECT = ('E', 'F', 'W', 'C90')
MAX_LINE_LENGTH = 79 MAX_LINE_LENGTH = 79
TRUTHY_VALUES = {'true', '1', 't'} TRUTHY_VALUES = {"true", "1", "t"}
# Other constants # Other constants
WHITESPACE = frozenset(' \t') WHITESPACE = frozenset(" \t")
STATISTIC_NAMES = ( STATISTIC_NAMES = ("logical lines", "physical lines", "tokens")
'logical lines',
'physical lines',
'tokens',
)
NOQA_INLINE_REGEXP = re.compile( NOQA_INLINE_REGEXP = re.compile(
# We're looking for items that look like this: # We're looking for items that look like this:
@ -46,8 +33,8 @@ NOQA_INLINE_REGEXP = re.compile(
# We do not care about the ``: `` that follows ``noqa`` # We do not care about the ``: `` that follows ``noqa``
# We do not care about the casing of ``noqa`` # We do not care about the casing of ``noqa``
# We want a comma-separated list of errors # We want a comma-separated list of errors
r'# noqa(?:: (?P<codes>([A-Z][0-9]+(?:[,\s]+)?)+))?', r"# noqa(?:: (?P<codes>([A-Z][0-9]+(?:[,\s]+)?)+))?",
re.IGNORECASE re.IGNORECASE,
) )
NOQA_FILE = re.compile(r'\s*# flake8[:=]\s*noqa', re.I) NOQA_FILE = re.compile(r"\s*# flake8[:=]\s*noqa", re.I)

View file

@ -24,15 +24,17 @@ class FailedToLoadPlugin(Flake8Exception):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialize our FailedToLoadPlugin exception.""" """Initialize our FailedToLoadPlugin exception."""
self.plugin = kwargs.pop('plugin') self.plugin = kwargs.pop("plugin")
self.ep_name = self.plugin.name self.ep_name = self.plugin.name
self.original_exception = kwargs.pop('exception') self.original_exception = kwargs.pop("exception")
super(FailedToLoadPlugin, self).__init__(*args, **kwargs) super(FailedToLoadPlugin, self).__init__(*args, **kwargs)
def __str__(self): def __str__(self):
"""Return a nice string for our exception.""" """Return a nice string for our exception."""
return self.FORMAT % {'name': self.ep_name, return self.FORMAT % {
'exc': self.original_exception} "name": self.ep_name,
"exc": self.original_exception,
}
class InvalidSyntax(Flake8Exception): class InvalidSyntax(Flake8Exception):
@ -40,19 +42,16 @@ class InvalidSyntax(Flake8Exception):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialize our InvalidSyntax exception.""" """Initialize our InvalidSyntax exception."""
exception = kwargs.pop('exception', None) exception = kwargs.pop("exception", None)
self.original_exception = exception self.original_exception = exception
self.error_message = '{0}: {1}'.format( self.error_message = "{0}: {1}".format(
exception.__class__.__name__, exception.__class__.__name__, exception.args[0]
exception.args[0],
) )
self.error_code = 'E902' self.error_code = "E902"
self.line_number = 1 self.line_number = 1
self.column_number = 0 self.column_number = 0
super(InvalidSyntax, self).__init__( super(InvalidSyntax, self).__init__(
self.error_message, self.error_message, *args, **kwargs
*args,
**kwargs
) )
@ -63,17 +62,18 @@ class PluginRequestedUnknownParameters(Flake8Exception):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Pop certain keyword arguments for initialization.""" """Pop certain keyword arguments for initialization."""
self.original_exception = kwargs.pop('exception') self.original_exception = kwargs.pop("exception")
self.plugin = kwargs.pop('plugin') self.plugin = kwargs.pop("plugin")
super(PluginRequestedUnknownParameters, self).__init__( super(PluginRequestedUnknownParameters, self).__init__(
*args, *args, **kwargs
**kwargs
) )
def __str__(self): def __str__(self):
"""Format our exception message.""" """Format our exception message."""
return self.FORMAT % {'name': self.plugin['plugin_name'], return self.FORMAT % {
'exc': self.original_exception} "name": self.plugin["plugin_name"],
"exc": self.original_exception,
}
class PluginExecutionFailed(Flake8Exception): class PluginExecutionFailed(Flake8Exception):
@ -83,16 +83,18 @@ class PluginExecutionFailed(Flake8Exception):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Utilize keyword arguments for message generation.""" """Utilize keyword arguments for message generation."""
self.original_exception = kwargs.pop('exception') self.original_exception = kwargs.pop("exception")
self.plugin = kwargs.pop('plugin') self.plugin = kwargs.pop("plugin")
super(PluginExecutionFailed, self).__init__( super(PluginExecutionFailed, self).__init__(
str(self), *args, **kwargs str(self), *args, **kwargs
) )
def __str__(self): def __str__(self):
"""Format our exception message.""" """Format our exception message."""
return self.FORMAT % {'name': self.plugin['plugin_name'], return self.FORMAT % {
'exc': self.original_exception} "name": self.plugin["plugin_name"],
"exc": self.original_exception,
}
class HookInstallationError(Flake8Exception): class HookInstallationError(Flake8Exception):
@ -106,14 +108,16 @@ class GitHookAlreadyExists(HookInstallationError):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialize the path attribute.""" """Initialize the path attribute."""
self.path = kwargs.pop('path') self.path = kwargs.pop("path")
super(GitHookAlreadyExists, self).__init__(*args, **kwargs) super(GitHookAlreadyExists, self).__init__(*args, **kwargs)
def __str__(self): def __str__(self):
"""Provide a nice message regarding the exception.""" """Provide a nice message regarding the exception."""
msg = ('The Git pre-commit hook ({0}) already exists. To convince ' msg = (
'Flake8 to install the hook, please remove the existing ' "The Git pre-commit hook ({0}) already exists. To convince "
'hook.') "Flake8 to install the hook, please remove the existing "
"hook."
)
return msg.format(self.path) return msg.format(self.path)
@ -124,25 +128,27 @@ class MercurialHookAlreadyExists(HookInstallationError):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialize the relevant attributes.""" """Initialize the relevant attributes."""
self.path = kwargs.pop('path') self.path = kwargs.pop("path")
self.value = kwargs.pop('value') self.value = kwargs.pop("value")
super(MercurialHookAlreadyExists, self).__init__(*args, **kwargs) super(MercurialHookAlreadyExists, self).__init__(*args, **kwargs)
def __str__(self): def __str__(self):
"""Return a nicely formatted string for these errors.""" """Return a nicely formatted string for these errors."""
msg = ('The Mercurial {0} hook already exists with "{1}" in {2}. ' msg = (
'To convince Flake8 to install the hook, please remove the ' 'The Mercurial {0} hook already exists with "{1}" in {2}. '
'{0} configuration from the [hooks] section of your hgrc.') "To convince Flake8 to install the hook, please remove the "
"{0} configuration from the [hooks] section of your hgrc."
)
return msg.format(self.hook_name, self.value, self.path) return msg.format(self.hook_name, self.value, self.path)
class MercurialCommitHookAlreadyExists(MercurialHookAlreadyExists): class MercurialCommitHookAlreadyExists(MercurialHookAlreadyExists):
"""Exception raised when the hg commit hook is already configured.""" """Exception raised when the hg commit hook is already configured."""
hook_name = 'commit' hook_name = "commit"
class MercurialQRefreshHookAlreadyExists(MercurialHookAlreadyExists): class MercurialQRefreshHookAlreadyExists(MercurialHookAlreadyExists):
"""Exception raised when the hg commit hook is already configured.""" """Exception raised when the hg commit hook is already configured."""
hook_name = 'qrefresh' hook_name = "qrefresh"

View file

@ -37,7 +37,7 @@ class BaseFormatter(object):
self.options = options self.options = options
self.filename = options.output_file self.filename = options.output_file
self.output_fd = None self.output_fd = None
self.newline = '\n' self.newline = "\n"
self.after_init() self.after_init()
def after_init(self): def after_init(self):
@ -68,7 +68,7 @@ class BaseFormatter(object):
This defaults to initializing :attr:`output_fd` if :attr:`filename` This defaults to initializing :attr:`output_fd` if :attr:`filename`
""" """
if self.filename: if self.filename:
self.output_fd = open(self.filename, 'a') self.output_fd = open(self.filename, "a")
def handle(self, error): def handle(self, error):
"""Handle an error reported by Flake8. """Handle an error reported by Flake8.
@ -102,8 +102,9 @@ class BaseFormatter(object):
:rtype: :rtype:
str str
""" """
raise NotImplementedError('Subclass of BaseFormatter did not implement' raise NotImplementedError(
' format.') "Subclass of BaseFormatter did not implement" " format."
)
def show_statistics(self, statistics): def show_statistics(self, statistics):
"""Format and print the statistics.""" """Format and print the statistics."""
@ -112,11 +113,13 @@ class BaseFormatter(object):
statistic = next(stats_for_error_code) statistic = next(stats_for_error_code)
count = statistic.count count = statistic.count
count += sum(stat.count for stat in stats_for_error_code) count += sum(stat.count for stat in stats_for_error_code)
self._write('{count:<5} {error_code} {message}'.format( self._write(
count=count, "{count:<5} {error_code} {message}".format(
error_code=error_code, count=count,
message=statistic.message, error_code=error_code,
)) message=statistic.message,
)
)
def show_benchmarks(self, benchmarks): def show_benchmarks(self, benchmarks):
"""Format and print the benchmarks.""" """Format and print the benchmarks."""
@ -130,8 +133,8 @@ class BaseFormatter(object):
# the decimal point to be displayed. This is the precision and it # the decimal point to be displayed. This is the precision and it
# can not be specified for integers which is why we need two separate # can not be specified for integers which is why we need two separate
# format strings. # format strings.
float_format = '{value:<10.3} {statistic}'.format float_format = "{value:<10.3} {statistic}".format
int_format = '{value:<10} {statistic}'.format int_format = "{value:<10} {statistic}".format
for statistic, value in benchmarks: for statistic, value in benchmarks:
if isinstance(value, int): if isinstance(value, int):
benchmark = int_format(statistic=statistic, value=value) benchmark = int_format(statistic=statistic, value=value)
@ -158,11 +161,11 @@ class BaseFormatter(object):
str str
""" """
if not self.options.show_source or error.physical_line is None: if not self.options.show_source or error.physical_line is None:
return '' return ""
# Because column numbers are 1-indexed, we need to remove one to get # Because column numbers are 1-indexed, we need to remove one to get
# the proper number of space characters. # the proper number of space characters.
pointer = (' ' * (error.column_number - 1)) + '^' pointer = (" " * (error.column_number - 1)) + "^"
# Physical lines have a newline at the end, no need to add an extra # Physical lines have a newline at the end, no need to add an extra
# one # one
return error.physical_line + pointer return error.physical_line + pointer

View file

@ -42,24 +42,24 @@ class Default(SimpleFormatter):
format string. format string.
""" """
error_format = '%(path)s:%(row)d:%(col)d: %(code)s %(text)s' error_format = "%(path)s:%(row)d:%(col)d: %(code)s %(text)s"
def after_init(self): def after_init(self):
"""Check for a custom format string.""" """Check for a custom format string."""
if self.options.format.lower() != 'default': if self.options.format.lower() != "default":
self.error_format = self.options.format self.error_format = self.options.format
class Pylint(SimpleFormatter): class Pylint(SimpleFormatter):
"""Pylint formatter for Flake8.""" """Pylint formatter for Flake8."""
error_format = '%(path)s:%(row)d: [%(code)s] %(text)s' error_format = "%(path)s:%(row)d: [%(code)s] %(text)s"
class FilenameOnly(SimpleFormatter): class FilenameOnly(SimpleFormatter):
"""Only print filenames, e.g., flake8 -q.""" """Only print filenames, e.g., flake8 -q."""
error_format = '%(path)s' error_format = "%(path)s"
def after_init(self): def after_init(self):
"""Initialize our set of filenames.""" """Initialize our set of filenames."""

View file

@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__)
class Application(object): class Application(object):
"""Abstract our application into a class.""" """Abstract our application into a class."""
def __init__(self, program='flake8', version=flake8.__version__): def __init__(self, program="flake8", version=flake8.__version__):
# type: (str, str) -> NoneType # type: (str, str) -> NoneType
"""Initialize our application. """Initialize our application.
@ -42,7 +42,7 @@ class Application(object):
#: The instance of :class:`flake8.options.manager.OptionManager` used #: The instance of :class:`flake8.options.manager.OptionManager` used
#: to parse and handle the options and arguments passed by the user #: to parse and handle the options and arguments passed by the user
self.option_manager = manager.OptionManager( self.option_manager = manager.OptionManager(
prog='flake8', version=flake8.__version__ prog="flake8", version=flake8.__version__
) )
options.register_default_options(self.option_manager) options.register_default_options(self.option_manager)
#: The preliminary options parsed from CLI before plugins are loaded, #: The preliminary options parsed from CLI before plugins are loaded,
@ -118,21 +118,21 @@ class Application(object):
# Similarly we have to defer printing the help text until later. # Similarly we have to defer printing the help text until later.
args = (argv or sys.argv)[:] args = (argv or sys.argv)[:]
try: try:
args.remove('--version') args.remove("--version")
except ValueError: except ValueError:
pass pass
try: try:
args.remove('--help') args.remove("--help")
except ValueError: except ValueError:
pass pass
try: try:
args.remove('-h') args.remove("-h")
except ValueError: except ValueError:
pass pass
opts, args = self.option_manager.parse_known_args(args) opts, args = self.option_manager.parse_known_args(args)
# parse_known_args includes program name and unknown options as args # parse_known_args includes program name and unknown options as args
args = [a for a in args[1:] if not a.startswith('-')] args = [a for a in args[1:] if not a.startswith("-")]
self.prelim_opts, self.prelim_args = opts, args self.prelim_opts, self.prelim_args = opts, args
def exit(self): def exit(self):
@ -146,14 +146,16 @@ class Application(object):
print(self.result_count) print(self.result_count)
if not self.options.exit_zero: if not self.options.exit_zero:
raise SystemExit((self.result_count > 0) or raise SystemExit(
self.catastrophic_failure) (self.result_count > 0) or self.catastrophic_failure
)
def make_config_finder(self): def make_config_finder(self):
"""Make our ConfigFileFinder based on preliminary opts and args.""" """Make our ConfigFileFinder based on preliminary opts and args."""
if self.config_finder is None: if self.config_finder is None:
extra_config_files = utils.normalize_paths( extra_config_files = utils.normalize_paths(
self.prelim_opts.append_config) self.prelim_opts.append_config
)
self.config_finder = config.ConfigFileFinder( self.config_finder = config.ConfigFileFinder(
self.option_manager.program_name, self.option_manager.program_name,
self.prelim_args, self.prelim_args,
@ -181,14 +183,16 @@ class Application(object):
if self.check_plugins is None: if self.check_plugins is None:
self.check_plugins = plugin_manager.Checkers( self.check_plugins = plugin_manager.Checkers(
self.local_plugins.extension) self.local_plugins.extension
)
if self.listening_plugins is None: if self.listening_plugins is None:
self.listening_plugins = plugin_manager.Listeners() self.listening_plugins = plugin_manager.Listeners()
if self.formatting_plugins is None: if self.formatting_plugins is None:
self.formatting_plugins = plugin_manager.ReportFormatters( self.formatting_plugins = plugin_manager.ReportFormatters(
self.local_plugins.report) self.local_plugins.report
)
self.check_plugins.load_plugins() self.check_plugins.load_plugins()
self.listening_plugins.load_plugins() self.listening_plugins.load_plugins()
@ -222,19 +226,20 @@ class Application(object):
self.options._running_from_vcs = False self.options._running_from_vcs = False
self.check_plugins.provide_options(self.option_manager, self.options, self.check_plugins.provide_options(
self.args) self.option_manager, self.options, self.args
self.listening_plugins.provide_options(self.option_manager, )
self.options, self.listening_plugins.provide_options(
self.args) self.option_manager, self.options, self.args
self.formatting_plugins.provide_options(self.option_manager, )
self.options, self.formatting_plugins.provide_options(
self.args) self.option_manager, self.options, self.args
)
def formatter_for(self, formatter_plugin_name): def formatter_for(self, formatter_plugin_name):
"""Retrieve the formatter class by plugin name.""" """Retrieve the formatter class by plugin name."""
try: try:
default_formatter = self.formatting_plugins['default'] default_formatter = self.formatting_plugins["default"]
except KeyError: except KeyError:
raise exceptions.ExecutionError( raise exceptions.ExecutionError(
"The 'default' Flake8 formatting plugin is unavailable. " "The 'default' Flake8 formatting plugin is unavailable. "
@ -259,9 +264,9 @@ class Application(object):
if self.formatter is None: if self.formatter is None:
format_plugin = self.options.format format_plugin = self.options.format
if 1 <= self.options.quiet < 2: if 1 <= self.options.quiet < 2:
format_plugin = 'quiet-filename' format_plugin = "quiet-filename"
elif 2 <= self.options.quiet: elif 2 <= self.options.quiet:
format_plugin = 'quiet-nothing' format_plugin = "quiet-nothing"
if formatter_class is None: if formatter_class is None:
formatter_class = self.formatter_for(format_plugin) formatter_class = self.formatter_for(format_plugin)
@ -313,9 +318,9 @@ class Application(object):
self.file_checker_manager.run() self.file_checker_manager.run()
except exceptions.PluginExecutionFailed as plugin_failed: except exceptions.PluginExecutionFailed as plugin_failed:
print(str(plugin_failed)) print(str(plugin_failed))
print('Run flake8 with greater verbosity to see more details') print("Run flake8 with greater verbosity to see more details")
self.catastrophic_failure = True self.catastrophic_failure = True
LOG.info('Finished running') LOG.info("Finished running")
self.file_checker_manager.stop() self.file_checker_manager.stop()
self.end_time = time.time() self.end_time = time.time()
@ -325,13 +330,13 @@ class Application(object):
return return
time_elapsed = self.end_time - self.start_time time_elapsed = self.end_time - self.start_time
statistics = [('seconds elapsed', time_elapsed)] statistics = [("seconds elapsed", time_elapsed)]
add_statistic = statistics.append add_statistic = statistics.append
for statistic in (defaults.STATISTIC_NAMES + ('files',)): for statistic in defaults.STATISTIC_NAMES + ("files",):
value = self.file_checker_manager.statistics[statistic] value = self.file_checker_manager.statistics[statistic]
total_description = 'total ' + statistic + ' processed' total_description = "total " + statistic + " processed"
add_statistic((total_description, value)) add_statistic((total_description, value))
per_second_description = statistic + ' processed per second' per_second_description = statistic + " processed per second"
add_statistic((per_second_description, int(value / time_elapsed))) add_statistic((per_second_description, int(value / time_elapsed)))
self.formatter.show_benchmarks(statistics) self.formatter.show_benchmarks(statistics)
@ -343,11 +348,14 @@ class Application(object):
This also updates the :attr:`result_count` attribute with the total This also updates the :attr:`result_count` attribute with the total
number of errors, warnings, and other messages found. number of errors, warnings, and other messages found.
""" """
LOG.info('Reporting errors') LOG.info("Reporting errors")
results = self.file_checker_manager.report() results = self.file_checker_manager.report()
self.total_result_count, self.result_count = results self.total_result_count, self.result_count = results
LOG.info('Found a total of %d violations and reported %d', LOG.info(
self.total_result_count, self.result_count) "Found a total of %d violations and reported %d",
self.total_result_count,
self.result_count,
)
def report_statistics(self): def report_statistics(self):
"""Aggregate and report statistics from this run.""" """Aggregate and report statistics from this run."""
@ -367,7 +375,8 @@ class Application(object):
# our legacy API calls to these same methods. # our legacy API calls to these same methods.
self.parse_preliminary_options_and_args(argv) self.parse_preliminary_options_and_args(argv)
flake8.configure_logging( flake8.configure_logging(
self.prelim_opts.verbose, self.prelim_opts.output_file) self.prelim_opts.verbose, self.prelim_opts.output_file
)
self.make_config_finder() self.make_config_finder()
self.find_plugins() self.find_plugins()
self.register_plugin_options() self.register_plugin_options()
@ -402,15 +411,15 @@ class Application(object):
try: try:
self._run(argv) self._run(argv)
except KeyboardInterrupt as exc: except KeyboardInterrupt as exc:
print('... stopped') print("... stopped")
LOG.critical('Caught keyboard interrupt from user') LOG.critical("Caught keyboard interrupt from user")
LOG.exception(exc) LOG.exception(exc)
self.catastrophic_failure = True self.catastrophic_failure = True
except exceptions.ExecutionError as exc: except exceptions.ExecutionError as exc:
print('There was a critical error during execution of Flake8:') print("There was a critical error during execution of Flake8:")
print(exc.message) print(exc.message)
LOG.exception(exc) LOG.exception(exc)
self.catastrophic_failure = True self.catastrophic_failure = True
except exceptions.EarlyQuit: except exceptions.EarlyQuit:
self.catastrophic_failure = True self.catastrophic_failure = True
print('... stopped while processing files') print("... stopped while processing files")

View file

@ -5,8 +5,9 @@ import json
import platform import platform
def print_information(option, option_string, value, parser, def print_information(
option_manager=None): option, option_string, value, parser, option_manager=None
):
"""Print debugging information used in bug reports. """Print debugging information used in bug reports.
:param option: :param option:
@ -38,13 +39,13 @@ def print_information(option, option_string, value, parser,
def information(option_manager): def information(option_manager):
"""Generate the information to be printed for the bug report.""" """Generate the information to be printed for the bug report."""
return { return {
'version': option_manager.version, "version": option_manager.version,
'plugins': plugins_from(option_manager), "plugins": plugins_from(option_manager),
'dependencies': dependencies(), "dependencies": dependencies(),
'platform': { "platform": {
'python_implementation': platform.python_implementation(), "python_implementation": platform.python_implementation(),
'python_version': platform.python_version(), "python_version": platform.python_version(),
'system': platform.system(), "system": platform.system(),
}, },
} }
@ -53,9 +54,9 @@ def plugins_from(option_manager):
"""Generate the list of plugins installed.""" """Generate the list of plugins installed."""
return [ return [
{ {
'plugin': plugin.name, "plugin": plugin.name,
'version': plugin.version, "version": plugin.version,
'is_local': plugin.local, "is_local": plugin.local,
} }
for plugin in sorted(option_manager.registered_plugins) for plugin in sorted(option_manager.registered_plugins)
] ]
@ -66,4 +67,4 @@ def dependencies():
# defer this expensive import, not used outside --bug-report # defer this expensive import, not used outside --bug-report
import setuptools import setuptools
return [{'dependency': 'setuptools', 'version': setuptools.__version__}] return [{"dependency": "setuptools", "version": setuptools.__version__}]

View file

@ -16,7 +16,7 @@ import tempfile
from flake8 import defaults from flake8 import defaults
from flake8 import exceptions from flake8 import exceptions
__all__ = ('hook', 'install') __all__ = ("hook", "install")
def hook(lazy=False, strict=False): def hook(lazy=False, strict=False):
@ -39,10 +39,11 @@ def hook(lazy=False, strict=False):
""" """
# NOTE(sigmavirus24): Delay import of application until we need it. # NOTE(sigmavirus24): Delay import of application until we need it.
from flake8.main import application from flake8.main import application
app = application.Application() app = application.Application()
with make_temporary_directory() as tempdir: with make_temporary_directory() as tempdir:
filepaths = list(copy_indexed_files_to(tempdir, lazy)) filepaths = list(copy_indexed_files_to(tempdir, lazy))
app.initialize(['.']) app.initialize(["."])
app.options.exclude = update_excludes(app.options.exclude, tempdir) app.options.exclude = update_excludes(app.options.exclude, tempdir)
app.options._running_from_vcs = True app.options._running_from_vcs = True
# Apparently there are times when there are no files to check (e.g., # Apparently there are times when there are no files to check (e.g.,
@ -81,22 +82,21 @@ def install():
if git_directory is None or not os.path.exists(git_directory): if git_directory is None or not os.path.exists(git_directory):
return False return False
hooks_directory = os.path.join(git_directory, 'hooks') hooks_directory = os.path.join(git_directory, "hooks")
if not os.path.exists(hooks_directory): if not os.path.exists(hooks_directory):
os.mkdir(hooks_directory) os.mkdir(hooks_directory)
pre_commit_file = os.path.abspath( pre_commit_file = os.path.abspath(
os.path.join(hooks_directory, 'pre-commit') os.path.join(hooks_directory, "pre-commit")
) )
if os.path.exists(pre_commit_file): if os.path.exists(pre_commit_file):
raise exceptions.GitHookAlreadyExists( raise exceptions.GitHookAlreadyExists(
'File already exists', "File already exists", path=pre_commit_file
path=pre_commit_file,
) )
executable = get_executable() executable = get_executable()
with open(pre_commit_file, 'w') as fd: with open(pre_commit_file, "w") as fd:
fd.write(_HOOK_TEMPLATE.format(executable=executable)) fd.write(_HOOK_TEMPLATE.format(executable=executable))
# NOTE(sigmavirus24): The following sets: # NOTE(sigmavirus24): The following sets:
@ -108,8 +108,8 @@ def install():
pre_commit_permissions = stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH pre_commit_permissions = stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH
os.chmod(pre_commit_file, pre_commit_permissions) os.chmod(pre_commit_file, pre_commit_permissions)
print('git pre-commit hook installed, for configuration options see') print("git pre-commit hook installed, for configuration options see")
print('http://flake8.pycqa.org/en/latest/user/using-hooks.html') print("http://flake8.pycqa.org/en/latest/user/using-hooks.html")
return True return True
@ -117,11 +117,11 @@ def install():
def get_executable(): def get_executable():
if sys.executable is not None: if sys.executable is not None:
return sys.executable return sys.executable
return '/usr/bin/env python' return "/usr/bin/env python"
def find_git_directory(): def find_git_directory():
rev_parse = piped_process(['git', 'rev-parse', '--git-dir']) rev_parse = piped_process(["git", "rev-parse", "--git-dir"])
(stdout, _) = rev_parse.communicate() (stdout, _) = rev_parse.communicate()
stdout = to_text(stdout) stdout = to_text(stdout)
@ -146,12 +146,13 @@ def copy_indexed_files_to(temporary_directory, lazy):
def copy_file_to(destination_directory, filepath, contents): def copy_file_to(destination_directory, filepath, contents):
directory, filename = os.path.split(os.path.abspath(filepath)) directory, filename = os.path.split(os.path.abspath(filepath))
temporary_directory = make_temporary_directory_from(destination_directory, temporary_directory = make_temporary_directory_from(
directory) destination_directory, directory
)
if not os.path.exists(temporary_directory): if not os.path.exists(temporary_directory):
os.makedirs(temporary_directory) os.makedirs(temporary_directory)
temporary_filepath = os.path.join(temporary_directory, filename) temporary_filepath = os.path.join(temporary_directory, filename)
with open(temporary_filepath, 'wb') as fd: with open(temporary_filepath, "wb") as fd:
fd.write(contents) fd.write(contents)
return temporary_filepath return temporary_filepath
@ -164,11 +165,15 @@ def make_temporary_directory_from(destination, directory):
def find_modified_files(lazy): def find_modified_files(lazy):
diff_index_cmd = [ diff_index_cmd = [
'git', 'diff-index', '--cached', '--name-only', "git",
'--diff-filter=ACMRTUXB', 'HEAD' "diff-index",
"--cached",
"--name-only",
"--diff-filter=ACMRTUXB",
"HEAD",
] ]
if lazy: if lazy:
diff_index_cmd.remove('--cached') diff_index_cmd.remove("--cached")
diff_index = piped_process(diff_index_cmd) diff_index = piped_process(diff_index_cmd)
(stdout, _) = diff_index.communicate() (stdout, _) = diff_index.communicate()
@ -177,11 +182,9 @@ def find_modified_files(lazy):
def find_setup_cfgs(lazy): def find_setup_cfgs(lazy):
setup_cfg_cmd = [ setup_cfg_cmd = ["git", "ls-files", "--cached", "*setup.cfg"]
'git', 'ls-files', '--cached', '*setup.cfg'
]
if lazy: if lazy:
setup_cfg_cmd.remove('--cached') setup_cfg_cmd.remove("--cached")
extra_files = piped_process(setup_cfg_cmd) extra_files = piped_process(setup_cfg_cmd)
(stdout, _) = extra_files.communicate() (stdout, _) = extra_files.communicate()
stdout = to_text(stdout) stdout = to_text(stdout)
@ -189,7 +192,7 @@ def find_setup_cfgs(lazy):
def get_staged_contents_from(filename): def get_staged_contents_from(filename):
git_show = piped_process(['git', 'show', ':{0}'.format(filename)]) git_show = piped_process(["git", "show", ":{0}".format(filename)])
(stdout, _) = git_show.communicate() (stdout, _) = git_show.communicate()
return stdout return stdout
@ -203,28 +206,26 @@ def make_temporary_directory():
def to_text(string): def to_text(string):
"""Ensure that the string is text.""" """Ensure that the string is text."""
if callable(getattr(string, 'decode', None)): if callable(getattr(string, "decode", None)):
return string.decode('utf-8') return string.decode("utf-8")
return string return string
def piped_process(command): def piped_process(command):
return subprocess.Popen( return subprocess.Popen(
command, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) )
def git_config_for(parameter): def git_config_for(parameter):
config = piped_process(['git', 'config', '--get', '--bool', parameter]) config = piped_process(["git", "config", "--get", "--bool", parameter])
(stdout, _) = config.communicate() (stdout, _) = config.communicate()
return to_text(stdout).strip() return to_text(stdout).strip()
def config_for(parameter): def config_for(parameter):
environment_variable = 'flake8_{0}'.format(parameter).upper() environment_variable = "flake8_{0}".format(parameter).upper()
git_variable = 'flake8.{0}'.format(parameter) git_variable = "flake8.{0}".format(parameter)
value = os.environ.get(environment_variable, git_config_for(git_variable)) value = os.environ.get(environment_variable, git_config_for(git_variable))
return value.lower() in defaults.TRUTHY_VALUES return value.lower() in defaults.TRUTHY_VALUES
@ -232,7 +233,8 @@ def config_for(parameter):
def update_excludes(exclude_list, temporary_directory_path): def update_excludes(exclude_list, temporary_directory_path):
return [ return [
(temporary_directory_path + pattern) (temporary_directory_path + pattern)
if os.path.isabs(pattern) else pattern if os.path.isabs(pattern)
else pattern
for pattern in exclude_list for pattern in exclude_list
] ]

View file

@ -10,7 +10,7 @@ import subprocess
from flake8 import exceptions as exc from flake8 import exceptions as exc
__all__ = ('hook', 'install') __all__ = ("hook", "install")
def hook(ui, repo, **kwargs): def hook(ui, repo, **kwargs):
@ -24,13 +24,14 @@ def hook(ui, repo, **kwargs):
avoid using it all the same. avoid using it all the same.
""" """
from flake8.main import application from flake8.main import application
hgrc = find_hgrc(create_if_missing=False) hgrc = find_hgrc(create_if_missing=False)
if hgrc is None: if hgrc is None:
print('Cannot locate your root mercurial repository.') print("Cannot locate your root mercurial repository.")
raise SystemExit(True) raise SystemExit(True)
hgconfig = configparser_for(hgrc) hgconfig = configparser_for(hgrc)
strict = hgconfig.get('flake8', 'strict', fallback=True) strict = hgconfig.get("flake8", "strict", fallback=True)
filenames = list(get_filenames_from(repo, kwargs)) filenames = list(get_filenames_from(repo, kwargs))
@ -68,42 +69,40 @@ def install():
hgconfig = configparser_for(hgrc) hgconfig = configparser_for(hgrc)
if not hgconfig.has_section('hooks'): if not hgconfig.has_section("hooks"):
hgconfig.add_section('hooks') hgconfig.add_section("hooks")
if hgconfig.has_option('hooks', 'commit'): if hgconfig.has_option("hooks", "commit"):
raise exc.MercurialCommitHookAlreadyExists( raise exc.MercurialCommitHookAlreadyExists(
path=hgrc, path=hgrc, value=hgconfig.get("hooks", "commit")
value=hgconfig.get('hooks', 'commit'),
) )
if hgconfig.has_option('hooks', 'qrefresh'): if hgconfig.has_option("hooks", "qrefresh"):
raise exc.MercurialQRefreshHookAlreadyExists( raise exc.MercurialQRefreshHookAlreadyExists(
path=hgrc, path=hgrc, value=hgconfig.get("hooks", "qrefresh")
value=hgconfig.get('hooks', 'qrefresh'),
) )
hgconfig.set('hooks', 'commit', 'python:flake8.main.mercurial.hook') hgconfig.set("hooks", "commit", "python:flake8.main.mercurial.hook")
hgconfig.set('hooks', 'qrefresh', 'python:flake8.main.mercurial.hook') hgconfig.set("hooks", "qrefresh", "python:flake8.main.mercurial.hook")
if not hgconfig.has_section('flake8'): if not hgconfig.has_section("flake8"):
hgconfig.add_section('flake8') hgconfig.add_section("flake8")
if not hgconfig.has_option('flake8', 'strict'): if not hgconfig.has_option("flake8", "strict"):
hgconfig.set('flake8', 'strict', False) hgconfig.set("flake8", "strict", False)
with open(hgrc, 'w') as fd: with open(hgrc, "w") as fd:
hgconfig.write(fd) hgconfig.write(fd)
print('mercurial hooks installed, for configuration options see') print("mercurial hooks installed, for configuration options see")
print('http://flake8.pycqa.org/en/latest/user/using-hooks.html') print("http://flake8.pycqa.org/en/latest/user/using-hooks.html")
return True return True
def get_filenames_from(repository, kwargs): def get_filenames_from(repository, kwargs):
seen_filenames = set() seen_filenames = set()
node = kwargs['node'] node = kwargs["node"]
for revision in range(repository[node], len(repository)): for revision in range(repository[node], len(repository)):
for filename in repository[revision].files(): for filename in repository[revision].files():
full_filename = os.path.join(repository.root, filename) full_filename = os.path.join(repository.root, filename)
@ -113,30 +112,26 @@ def get_filenames_from(repository, kwargs):
continue continue
seen_filenames.add(full_filename) seen_filenames.add(full_filename)
if full_filename.endswith('.py'): if full_filename.endswith(".py"):
yield full_filename yield full_filename
def find_hgrc(create_if_missing=False): def find_hgrc(create_if_missing=False):
root = subprocess.Popen( root = subprocess.Popen(
['hg', 'root'], ["hg", "root"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) )
(hg_directory, _) = root.communicate() (hg_directory, _) = root.communicate()
if callable(getattr(hg_directory, 'decode', None)): if callable(getattr(hg_directory, "decode", None)):
hg_directory = hg_directory.decode('utf-8') hg_directory = hg_directory.decode("utf-8")
if not os.path.isdir(hg_directory): if not os.path.isdir(hg_directory):
return None return None
hgrc = os.path.abspath( hgrc = os.path.abspath(os.path.join(hg_directory, ".hg", "hgrc"))
os.path.join(hg_directory, '.hg', 'hgrc')
)
if not os.path.exists(hgrc): if not os.path.exists(hgrc):
if create_if_missing: if create_if_missing:
open(hgrc, 'w').close() open(hgrc, "w").close()
else: else:
return None return None

View file

@ -39,50 +39,66 @@ def register_default_options(option_manager):
# pep8 options # pep8 options
add_option( add_option(
'-v', '--verbose', default=0, action='count', "-v",
"--verbose",
default=0,
action="count",
parse_from_config=True, parse_from_config=True,
help='Print more information about what is happening in flake8.' help="Print more information about what is happening in flake8."
' This option is repeatable and will increase verbosity each ' " This option is repeatable and will increase verbosity each "
'time it is repeated.', "time it is repeated.",
) )
add_option( add_option(
'-q', '--quiet', default=0, action='count', "-q",
"--quiet",
default=0,
action="count",
parse_from_config=True, parse_from_config=True,
help='Report only file names, or nothing. This option is repeatable.', help="Report only file names, or nothing. This option is repeatable.",
) )
add_option( add_option(
'--count', action='store_true', parse_from_config=True, "--count",
help='Print total number of errors and warnings to standard error and' action="store_true",
' set the exit code to 1 if total is not empty.', parse_from_config=True,
help="Print total number of errors and warnings to standard error and"
" set the exit code to 1 if total is not empty.",
) )
add_option( add_option(
'--diff', action='store_true', "--diff",
help='Report changes only within line number ranges in the unified ' action="store_true",
'diff provided on standard in by the user.', help="Report changes only within line number ranges in the unified "
"diff provided on standard in by the user.",
) )
add_option( add_option(
'--exclude', metavar='patterns', default=','.join(defaults.EXCLUDE), "--exclude",
comma_separated_list=True, parse_from_config=True, metavar="patterns",
default=",".join(defaults.EXCLUDE),
comma_separated_list=True,
parse_from_config=True,
normalize_paths=True, normalize_paths=True,
help='Comma-separated list of files or directories to exclude.' help="Comma-separated list of files or directories to exclude."
' (Default: %default)', " (Default: %default)",
) )
add_option( add_option(
'--filename', metavar='patterns', default='*.py', "--filename",
parse_from_config=True, comma_separated_list=True, metavar="patterns",
help='Only check for filenames matching the patterns in this comma-' default="*.py",
'separated list. (Default: %default)', parse_from_config=True,
comma_separated_list=True,
help="Only check for filenames matching the patterns in this comma-"
"separated list. (Default: %default)",
) )
add_option( add_option(
'--stdin-display-name', default='stdin', "--stdin-display-name",
help='The name used when reporting errors from code passed via stdin.' default="stdin",
' This is useful for editors piping the file contents to flake8.' help="The name used when reporting errors from code passed via stdin."
' (Default: %default)', " This is useful for editors piping the file contents to flake8."
" (Default: %default)",
) )
# TODO(sigmavirus24): Figure out --first/--repeat # TODO(sigmavirus24): Figure out --first/--repeat
@ -91,136 +107,183 @@ def register_default_options(option_manager):
# freely provide a format string and that will break if we restrict their # freely provide a format string and that will break if we restrict their
# choices. # choices.
add_option( add_option(
'--format', metavar='format', default='default', "--format",
metavar="format",
default="default",
parse_from_config=True, parse_from_config=True,
help='Format errors according to the chosen formatter.', help="Format errors according to the chosen formatter.",
) )
add_option( add_option(
'--hang-closing', action='store_true', parse_from_config=True, "--hang-closing",
help='Hang closing bracket instead of matching indentation of opening' action="store_true",
" bracket's line.", parse_from_config=True,
help="Hang closing bracket instead of matching indentation of opening"
" bracket's line.",
) )
add_option( add_option(
'--ignore', metavar='errors', default=','.join(defaults.IGNORE), "--ignore",
parse_from_config=True, comma_separated_list=True, metavar="errors",
help='Comma-separated list of errors and warnings to ignore (or skip).' default=",".join(defaults.IGNORE),
' For example, ``--ignore=E4,E51,W234``. (Default: %default)', parse_from_config=True,
comma_separated_list=True,
help="Comma-separated list of errors and warnings to ignore (or skip)."
" For example, ``--ignore=E4,E51,W234``. (Default: %default)",
) )
add_option( add_option(
'--extend-ignore', metavar='errors', default='', "--extend-ignore",
parse_from_config=True, comma_separated_list=True, metavar="errors",
help='Comma-separated list of errors and warnings to add to the list' default="",
' of ignored ones. For example, ``--extend-ignore=E4,E51,W234``.', parse_from_config=True,
comma_separated_list=True,
help="Comma-separated list of errors and warnings to add to the list"
" of ignored ones. For example, ``--extend-ignore=E4,E51,W234``.",
) )
add_option( add_option(
'--max-line-length', type='int', metavar='n', "--max-line-length",
default=defaults.MAX_LINE_LENGTH, parse_from_config=True, type="int",
help='Maximum allowed line length for the entirety of this run. ' metavar="n",
'(Default: %default)', default=defaults.MAX_LINE_LENGTH,
parse_from_config=True,
help="Maximum allowed line length for the entirety of this run. "
"(Default: %default)",
) )
add_option( add_option(
'--select', metavar='errors', default=','.join(defaults.SELECT), "--select",
parse_from_config=True, comma_separated_list=True, metavar="errors",
help='Comma-separated list of errors and warnings to enable.' default=",".join(defaults.SELECT),
' For example, ``--select=E4,E51,W234``. (Default: %default)', parse_from_config=True,
comma_separated_list=True,
help="Comma-separated list of errors and warnings to enable."
" For example, ``--select=E4,E51,W234``. (Default: %default)",
) )
add_option( add_option(
'--disable-noqa', default=False, parse_from_config=True, "--disable-noqa",
action='store_true', default=False,
parse_from_config=True,
action="store_true",
help='Disable the effect of "# noqa". This will report errors on ' help='Disable the effect of "# noqa". This will report errors on '
'lines with "# noqa" at the end.' 'lines with "# noqa" at the end.',
) )
# TODO(sigmavirus24): Decide what to do about --show-pep8 # TODO(sigmavirus24): Decide what to do about --show-pep8
add_option( add_option(
'--show-source', action='store_true', parse_from_config=True, "--show-source",
help='Show the source generate each error or warning.', action="store_true",
parse_from_config=True,
help="Show the source generate each error or warning.",
) )
add_option( add_option(
'--statistics', action='store_true', parse_from_config=True, "--statistics",
help='Count errors and warnings.', action="store_true",
parse_from_config=True,
help="Count errors and warnings.",
) )
# Flake8 options # Flake8 options
add_option( add_option(
'--enable-extensions', default='', parse_from_config=True, "--enable-extensions",
comma_separated_list=True, type='string', default="",
help='Enable plugins and extensions that are otherwise disabled ' parse_from_config=True,
'by default', comma_separated_list=True,
type="string",
help="Enable plugins and extensions that are otherwise disabled "
"by default",
) )
add_option( add_option(
'--exit-zero', action='store_true', "--exit-zero",
action="store_true",
help='Exit with status code "0" even if there are errors.', help='Exit with status code "0" even if there are errors.',
) )
add_option( add_option(
'--install-hook', action='callback', type='choice', "--install-hook",
choices=vcs.choices(), callback=vcs.install, action="callback",
help='Install a hook that is run prior to a commit for the supported ' type="choice",
'version control system.' choices=vcs.choices(),
callback=vcs.install,
help="Install a hook that is run prior to a commit for the supported "
"version control system.",
) )
add_option( add_option(
'-j', '--jobs', type='string', default='auto', parse_from_config=True, "-j",
help='Number of subprocesses to use to run checks in parallel. ' "--jobs",
'This is ignored on Windows. The default, "auto", will ' type="string",
'auto-detect the number of processors available to use.' default="auto",
' (Default: %default)', parse_from_config=True,
help="Number of subprocesses to use to run checks in parallel. "
'This is ignored on Windows. The default, "auto", will '
"auto-detect the number of processors available to use."
" (Default: %default)",
) )
add_option( add_option(
'--output-file', default=None, type='string', parse_from_config=True, "--output-file",
default=None,
type="string",
parse_from_config=True,
# callback=callbacks.redirect_stdout, # callback=callbacks.redirect_stdout,
help='Redirect report to a file.', help="Redirect report to a file.",
) )
add_option( add_option(
'--tee', default=False, parse_from_config=True, action='store_true', "--tee",
help='Write to stdout and output-file.', default=False,
parse_from_config=True,
action="store_true",
help="Write to stdout and output-file.",
) )
# Config file options # Config file options
add_option( add_option(
'--append-config', action='append', "--append-config",
help='Provide extra config files to parse in addition to the files ' action="append",
'found by Flake8 by default. These files are the last ones read ' help="Provide extra config files to parse in addition to the files "
'and so they take the highest precedence when multiple files ' "found by Flake8 by default. These files are the last ones read "
'provide the same option.', "and so they take the highest precedence when multiple files "
"provide the same option.",
) )
add_option( add_option(
'--config', default=None, "--config",
help='Path to the config file that will be the authoritative config ' default=None,
'source. This will cause Flake8 to ignore all other ' help="Path to the config file that will be the authoritative config "
'configuration files.' "source. This will cause Flake8 to ignore all other "
"configuration files.",
) )
add_option( add_option(
'--isolated', default=False, action='store_true', "--isolated",
help='Ignore all configuration files.', default=False,
action="store_true",
help="Ignore all configuration files.",
) )
# Benchmarking # Benchmarking
add_option( add_option(
'--benchmark', default=False, action='store_true', "--benchmark",
help='Print benchmark information about this run of Flake8', default=False,
action="store_true",
help="Print benchmark information about this run of Flake8",
) )
# Debugging # Debugging
add_option( add_option(
'--bug-report', action='callback', callback=debug.print_information, "--bug-report",
callback_kwargs={'option_manager': option_manager}, action="callback",
help='Print information necessary when preparing a bug report', callback=debug.print_information,
callback_kwargs={"option_manager": option_manager},
help="Print information necessary when preparing a bug report",
) )

View file

@ -11,7 +11,7 @@ UNSET = object()
class Flake8(setuptools.Command): class Flake8(setuptools.Command):
"""Run Flake8 via setuptools/distutils for registered modules.""" """Run Flake8 via setuptools/distutils for registered modules."""
description = 'Run Flake8 on modules registered in setup.py' description = "Run Flake8 on modules registered in setup.py"
# NOTE(sigmavirus24): If we populated this with a list of tuples, users # NOTE(sigmavirus24): If we populated this with a list of tuples, users
# could do something like ``python setup.py flake8 --ignore=E123,E234`` # could do something like ``python setup.py flake8 --ignore=E123,E234``
# but we would have to redefine it and we can't define it dynamically. # but we would have to redefine it and we can't define it dynamically.
@ -39,23 +39,26 @@ class Flake8(setuptools.Command):
value = getattr(self, name, UNSET) value = getattr(self, name, UNSET)
if value is UNSET: if value is UNSET:
continue continue
setattr(self.flake8.options, setattr(
name, self.flake8.options,
option.normalize_from_setuptools(value)) name,
option.normalize_from_setuptools(value),
)
def package_files(self): def package_files(self):
"""Collect the files/dirs included in the registered modules.""" """Collect the files/dirs included in the registered modules."""
seen_package_directories = () seen_package_directories = ()
directories = self.distribution.package_dir or {} directories = self.distribution.package_dir or {}
empty_directory_exists = '' in directories empty_directory_exists = "" in directories
packages = self.distribution.packages or [] packages = self.distribution.packages or []
for package in packages: for package in packages:
package_directory = package package_directory = package
if package in directories: if package in directories:
package_directory = directories[package] package_directory = directories[package]
elif empty_directory_exists: elif empty_directory_exists:
package_directory = os.path.join(directories[''], package_directory = os.path.join(
package_directory) directories[""], package_directory
)
# NOTE(sigmavirus24): Do not collect submodules, e.g., # NOTE(sigmavirus24): Do not collect submodules, e.g.,
# if we have: # if we have:
@ -66,13 +69,13 @@ class Flake8(setuptools.Command):
if package_directory.startswith(seen_package_directories): if package_directory.startswith(seen_package_directories):
continue continue
seen_package_directories += (package_directory + '.',) seen_package_directories += (package_directory + ".",)
yield package_directory yield package_directory
def module_files(self): def module_files(self):
"""Collect the files listed as py_modules.""" """Collect the files listed as py_modules."""
modules = self.distribution.py_modules or [] modules = self.distribution.py_modules or []
filename_from = '{0}.py'.format filename_from = "{0}.py".format
for module in modules: for module in modules:
yield filename_from(module) yield filename_from(module)
@ -84,7 +87,7 @@ class Flake8(setuptools.Command):
for module in self.module_files(): for module in self.module_files():
yield module yield module
yield 'setup.py' yield "setup.py"
def run(self): def run(self):
"""Run the Flake8 application.""" """Run the Flake8 application."""

View file

@ -8,10 +8,7 @@ from flake8.main import mercurial
# as plugins, e.g., adding a flake8.vcs entry-point. In that case, this # as plugins, e.g., adding a flake8.vcs entry-point. In that case, this
# dictionary should disappear, and this module might contain more code for # dictionary should disappear, and this module might contain more code for
# managing those bits (in conjuntion with flake8.plugins.manager). # managing those bits (in conjuntion with flake8.plugins.manager).
_INSTALLERS = { _INSTALLERS = {"git": git.install, "mercurial": mercurial.install}
'git': git.install,
'mercurial': mercurial.install,
}
def install(option, option_string, value, parser): def install(option, option_string, value, parser):
@ -30,7 +27,7 @@ def install(option, option_string, value, parser):
errored = True errored = True
if not successful: if not successful:
print('Could not find the {0} directory'.format(value)) print("Could not find the {0} directory".format(value))
raise SystemExit(not successful and errored) raise SystemExit(not successful and errored)

View file

@ -37,26 +37,28 @@ def aggregate_options(manager, config_finder, arglist=None, values=None):
# Make our new configuration file mergerator # Make our new configuration file mergerator
config_parser = config.MergedConfigParser( config_parser = config.MergedConfigParser(
option_manager=manager, option_manager=manager, config_finder=config_finder
config_finder=config_finder,
) )
# Get the parsed config # Get the parsed config
parsed_config = config_parser.parse(original_values.config, parsed_config = config_parser.parse(
original_values.isolated) original_values.config, original_values.isolated
)
# Extend the default ignore value with the extended default ignore list, # Extend the default ignore value with the extended default ignore list,
# registered by plugins. # registered by plugins.
extended_default_ignore = manager.extended_default_ignore.copy() extended_default_ignore = manager.extended_default_ignore.copy()
LOG.debug('Extended default ignore list: %s', LOG.debug(
list(extended_default_ignore)) "Extended default ignore list: %s", list(extended_default_ignore)
)
extended_default_ignore.update(default_values.ignore) extended_default_ignore.update(default_values.ignore)
default_values.ignore = list(extended_default_ignore) default_values.ignore = list(extended_default_ignore)
LOG.debug('Merged default ignore list: %s', default_values.ignore) LOG.debug("Merged default ignore list: %s", default_values.ignore)
extended_default_select = manager.extended_default_select.copy() extended_default_select = manager.extended_default_select.copy()
LOG.debug('Extended default select list: %s', LOG.debug(
list(extended_default_select)) "Extended default select list: %s", list(extended_default_select)
)
default_values.extended_default_select = extended_default_select default_values.extended_default_select = extended_default_select
# Merge values parsed from config onto the default values returned # Merge values parsed from config onto the default values returned
@ -67,10 +69,12 @@ def aggregate_options(manager, config_finder, arglist=None, values=None):
if not hasattr(default_values, config_name): if not hasattr(default_values, config_name):
dest_name = config_parser.config_options[config_name].dest dest_name = config_parser.config_options[config_name].dest
LOG.debug('Overriding default value of (%s) for "%s" with (%s)', LOG.debug(
getattr(default_values, dest_name, None), 'Overriding default value of (%s) for "%s" with (%s)',
dest_name, getattr(default_values, dest_name, None),
value) dest_name,
value,
)
# Override the default values with the config values # Override the default values with the config values
setattr(default_values, dest_name, value) setattr(default_values, dest_name, value)

View file

@ -9,13 +9,13 @@ from flake8 import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
__all__ = ('ConfigFileFinder', 'MergedConfigParser') __all__ = ("ConfigFileFinder", "MergedConfigParser")
class ConfigFileFinder(object): class ConfigFileFinder(object):
"""Encapsulate the logic for finding and reading config files.""" """Encapsulate the logic for finding and reading config files."""
PROJECT_FILENAMES = ('setup.cfg', 'tox.ini') PROJECT_FILENAMES = ("setup.cfg", "tox.ini")
def __init__(self, program_name, args, extra_config_files): def __init__(self, program_name, args, extra_config_files):
"""Initialize object to find config files. """Initialize object to find config files.
@ -31,25 +31,27 @@ class ConfigFileFinder(object):
extra_config_files = extra_config_files or [] extra_config_files = extra_config_files or []
self.extra_config_files = [ self.extra_config_files = [
# Ensure the paths are absolute paths for local_config_files # Ensure the paths are absolute paths for local_config_files
os.path.abspath(f) for f in extra_config_files os.path.abspath(f)
for f in 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(
os.path.expanduser('~/.config')) "XDG_CONFIG_HOME", os.path.expanduser("~/.config")
)
# Look for '.<program_name>' files # Look for '.<program_name>' files
self.program_config = '.' + program_name self.program_config = "." + program_name
self.program_name = program_name self.program_name = program_name
# 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)
self.local_directory = os.path.abspath(os.curdir) self.local_directory = os.path.abspath(os.curdir)
if not args: if not args:
args = ['.'] args = ["."]
self.parent = self.tail = os.path.abspath(os.path.commonprefix(args)) self.parent = self.tail = os.path.abspath(os.path.commonprefix(args))
# caches to avoid double-reading config files # caches to avoid double-reading config files
@ -61,7 +63,7 @@ class ConfigFileFinder(object):
@staticmethod @staticmethod
def _read_config(files): def _read_config(files):
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
if isinstance(files, (str, type(u''))): if isinstance(files, (str, type(u""))):
files = [files] files = [files]
found_files = [] found_files = []
@ -69,13 +71,17 @@ class ConfigFileFinder(object):
try: try:
found_files.extend(config.read(filename)) found_files.extend(config.read(filename))
except UnicodeDecodeError: except UnicodeDecodeError:
LOG.exception("There was an error decoding a config file." LOG.exception(
"The file with a problem was %s.", "There was an error decoding a config file."
filename) "The file with a problem was %s.",
filename,
)
except configparser.ParsingError: except configparser.ParsingError:
LOG.exception("There was an error trying to parse a config " LOG.exception(
"file. The file with a problem was %s.", "There was an error trying to parse a config "
filename) "file. The file with a problem was %s.",
filename,
)
return (config, found_files) return (config, found_files)
def cli_config(self, files): def cli_config(self, files):
@ -83,7 +89,7 @@ class ConfigFileFinder(object):
if files not in self._cli_configs: if files not in self._cli_configs:
config, found_files = self._read_config(files) config, found_files = self._read_config(files)
if found_files: if found_files:
LOG.debug('Found cli configuration files: %s', found_files) LOG.debug("Found cli configuration files: %s", found_files)
self._cli_configs[files] = config self._cli_configs[files] = config
return self._cli_configs[files] return self._cli_configs[files]
@ -94,8 +100,9 @@ class ConfigFileFinder(object):
found_config_files = False found_config_files = False
while tail and not found_config_files: while tail and not found_config_files:
for project_filename in self.project_filenames: for project_filename in self.project_filenames:
filename = os.path.abspath(os.path.join(parent, filename = os.path.abspath(
project_filename)) os.path.join(parent, project_filename)
)
if os.path.exists(filename): if os.path.exists(filename):
yield filename yield filename
found_config_files = True found_config_files = True
@ -117,8 +124,7 @@ class ConfigFileFinder(object):
""" """
exists = os.path.exists exists = os.path.exists
return [ return [
filename filename for filename in self.generate_possible_local_files()
for filename in self.generate_possible_local_files()
] + [f for f in self.extra_config_files if exists(f)] ] + [f for f in self.extra_config_files if exists(f)]
def local_configs_with_files(self): def local_configs_with_files(self):
@ -129,7 +135,7 @@ class ConfigFileFinder(object):
if self._local_configs is None: if self._local_configs is None:
config, found_files = self._read_config(self.local_config_files()) config, found_files = self._read_config(self.local_config_files())
if found_files: if found_files:
LOG.debug('Found local configuration files: %s', found_files) LOG.debug("Found local configuration files: %s", found_files)
self._local_configs = config self._local_configs = config
self._local_found_files = found_files self._local_found_files = found_files
return (self._local_configs, self._local_found_files) return (self._local_configs, self._local_found_files)
@ -141,7 +147,7 @@ class ConfigFileFinder(object):
def user_config_file(self): def user_config_file(self):
"""Find the user-level config file.""" """Find the user-level config file."""
if self.is_windows: if self.is_windows:
return os.path.expanduser('~\\' + self.program_config) return os.path.expanduser("~\\" + self.program_config)
return os.path.join(self.xdg_home, self.program_name) return os.path.join(self.xdg_home, self.program_name)
def user_config(self): def user_config(self):
@ -149,7 +155,7 @@ class ConfigFileFinder(object):
if self._user_config is None: if self._user_config is None:
config, found_files = self._read_config(self.user_config_file()) config, found_files = self._read_config(self.user_config_file())
if found_files: if found_files:
LOG.debug('Found user configuration files: %s', found_files) LOG.debug("Found user configuration files: %s", found_files)
self._user_config = config self._user_config = config
return self._user_config return self._user_config
@ -164,10 +170,10 @@ class MergedConfigParser(object):
#: Set of types that should use the #: Set of types that should use the
#: :meth:`~configparser.RawConfigParser.getint` method. #: :meth:`~configparser.RawConfigParser.getint` method.
GETINT_TYPES = {'int', 'count'} GETINT_TYPES = {"int", "count"}
#: Set of actions that should use the #: Set of actions that should use the
#: :meth:`~configparser.RawConfigParser.getbool` method. #: :meth:`~configparser.RawConfigParser.getbool` method.
GETBOOL_ACTIONS = {'store_true', 'store_false'} GETBOOL_ACTIONS = {"store_true", "store_false"}
def __init__(self, option_manager, config_finder): def __init__(self, option_manager, config_finder):
"""Initialize the MergedConfigParser instance. """Initialize the MergedConfigParser instance.
@ -189,26 +195,32 @@ class MergedConfigParser(object):
def _normalize_value(self, option, value): def _normalize_value(self, option, value):
final_value = option.normalize( final_value = option.normalize(
value, value, self.config_finder.local_directory
self.config_finder.local_directory, )
LOG.debug(
'%r has been normalized to %r for option "%s"',
value,
final_value,
option.config_name,
) )
LOG.debug('%r has been normalized to %r for option "%s"',
value, final_value, option.config_name)
return final_value return final_value
def _parse_config(self, config_parser): def _parse_config(self, config_parser):
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:
LOG.debug('Option "%s" is not registered. Ignoring.', LOG.debug(
option_name) 'Option "%s" is not registered. Ignoring.', option_name
)
continue continue
option = self.config_options[option_name] option = self.config_options[option_name]
# Use the appropriate method to parse the config value # Use the appropriate method to parse the config value
method = config_parser.get method = config_parser.get
if (option.type in self.GETINT_TYPES or if (
option.action in self.GETINT_TYPES): option.type in self.GETINT_TYPES
or option.action in self.GETINT_TYPES
):
method = config_parser.getint method = config_parser.getint
elif option.action in self.GETBOOL_ACTIONS: elif option.action in self.GETBOOL_ACTIONS:
method = config_parser.getboolean method = config_parser.getboolean
@ -229,33 +241,39 @@ class MergedConfigParser(object):
"""Parse and return the local configuration files.""" """Parse and return the local configuration files."""
config = self.config_finder.local_configs() config = self.config_finder.local_configs()
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(
self.program_name) "Local configuration files have no %s section",
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)
def parse_user_config(self): def parse_user_config(self):
"""Parse and return the user configuration files.""" """Parse and return the user configuration files."""
config = self.config_finder.user_config() config = self.config_finder.user_config()
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(
self.program_name) "User configuration files have no %s section",
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_cli_config(self, config_path): def parse_cli_config(self, config_path):
"""Parse and return the file specified by --config.""" """Parse and return the file specified by --config."""
config = self.config_finder.cli_config(config_path) config = self.config_finder.cli_config(config_path)
if not self.is_configured_by(config): if not self.is_configured_by(config):
LOG.debug('CLI configuration files have no %s section', LOG.debug(
self.program_name) "CLI configuration files have no %s section",
self.program_name,
)
return {} return {}
LOG.debug('Parsing CLI configuration files.') LOG.debug("Parsing CLI configuration files.")
return self._parse_config(config) return self._parse_config(config)
def merge_user_and_local_config(self): def merge_user_and_local_config(self):
@ -293,14 +311,19 @@ class MergedConfigParser(object):
dict dict
""" """
if isolated: if isolated:
LOG.debug('Refusing to parse configuration files due to user-' LOG.debug(
'requested isolation') "Refusing to parse configuration files due to user-"
"requested isolation"
)
return {} return {}
if cli_config: if cli_config:
LOG.debug('Ignoring user and locally found configuration files. ' LOG.debug(
'Reading only configuration from "%s" specified via ' "Ignoring user and locally found configuration files. "
'--config by the user', cli_config) 'Reading only configuration from "%s" specified via '
"--config by the user",
cli_config,
)
return self.parse_cli_config(cli_config) return self.parse_cli_config(cli_config)
return self.merge_user_and_local_config() return self.merge_user_and_local_config()
@ -325,13 +348,18 @@ def get_local_plugins(config_finder, cli_config=None, isolated=False):
""" """
local_plugins = LocalPlugins(extension=[], report=[], paths=[]) local_plugins = LocalPlugins(extension=[], report=[], paths=[])
if isolated: if isolated:
LOG.debug('Refusing to look for local plugins in configuration' LOG.debug(
'files due to user-requested isolation') "Refusing to look for local plugins in configuration"
"files due to user-requested isolation"
)
return local_plugins return local_plugins
if cli_config: if cli_config:
LOG.debug('Reading local plugins only from "%s" specified via ' LOG.debug(
'--config by the user', cli_config) 'Reading local plugins only from "%s" specified via '
"--config by the user",
cli_config,
)
config = config_finder.cli_config(cli_config) config = config_finder.cli_config(cli_config)
config_files = [cli_config] config_files = [cli_config]
else: else:
@ -339,28 +367,31 @@ def get_local_plugins(config_finder, cli_config=None, isolated=False):
base_dirs = {os.path.dirname(cf) for cf in config_files} base_dirs = {os.path.dirname(cf) for cf in config_files}
section = '%s:local-plugins' % config_finder.program_name section = "%s:local-plugins" % config_finder.program_name
for plugin_type in ['extension', 'report']: for plugin_type in ["extension", "report"]:
if config.has_option(section, plugin_type): if config.has_option(section, plugin_type):
local_plugins_string = config.get(section, plugin_type).strip() local_plugins_string = config.get(section, plugin_type).strip()
plugin_type_list = getattr(local_plugins, plugin_type) plugin_type_list = getattr(local_plugins, plugin_type)
plugin_type_list.extend(utils.parse_comma_separated_list( plugin_type_list.extend(
local_plugins_string, utils.parse_comma_separated_list(
regexp=utils.LOCAL_PLUGIN_LIST_RE, local_plugins_string, regexp=utils.LOCAL_PLUGIN_LIST_RE
)) )
if config.has_option(section, 'paths'): )
if config.has_option(section, "paths"):
raw_paths = utils.parse_comma_separated_list( raw_paths = utils.parse_comma_separated_list(
config.get(section, 'paths').strip() config.get(section, "paths").strip()
) )
norm_paths = [] norm_paths = []
for base_dir in base_dirs: for base_dir in base_dirs:
norm_paths.extend( norm_paths.extend(
path for path in path
utils.normalize_paths(raw_paths, parent=base_dir) for path in utils.normalize_paths(raw_paths, parent=base_dir)
if os.path.exists(path) if os.path.exists(path)
) )
local_plugins.paths.extend(norm_paths) local_plugins.paths.extend(norm_paths)
return local_plugins return local_plugins
LocalPlugins = collections.namedtuple('LocalPlugins', 'extension report paths') LocalPlugins = collections.namedtuple(
"LocalPlugins", "extension report paths"
)

View file

@ -11,15 +11,28 @@ LOG = logging.getLogger(__name__)
class Option(object): class Option(object):
"""Our wrapper around an optparse.Option object to add features.""" """Our wrapper around an optparse.Option object to add features."""
def __init__(self, short_option_name=None, long_option_name=None, def __init__(
# Options below here are taken from the optparse.Option class self,
action=None, default=None, type=None, dest=None, short_option_name=None,
nargs=None, const=None, choices=None, callback=None, long_option_name=None,
callback_args=None, callback_kwargs=None, help=None, # Options below here are taken from the optparse.Option class
metavar=None, action=None,
# Options below here are specific to Flake8 default=None,
parse_from_config=False, comma_separated_list=False, type=None,
normalize_paths=False): 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. """Initialize an Option instance wrapping optparse.Option.
The following are all passed directly through to optparse. The following are all passed directly through to optparse.
@ -73,18 +86,18 @@ class Option(object):
x for x in (short_option_name, long_option_name) if x is not None x for x in (short_option_name, long_option_name) if x is not None
] ]
self.option_kwargs = { self.option_kwargs = {
'action': action, "action": action,
'default': default, "default": default,
'type': type, "type": type,
'dest': self._make_dest(dest), "dest": self._make_dest(dest),
'nargs': nargs, "nargs": nargs,
'const': const, "const": const,
'choices': choices, "choices": choices,
'callback': callback, "callback": callback,
'callback_args': callback_args, "callback_args": callback_args,
'callback_kwargs': callback_kwargs, "callback_kwargs": callback_kwargs,
'help': help, "help": help,
'metavar': metavar, "metavar": metavar,
} }
# Set attributes for our option arguments # Set attributes for our option arguments
for key, value in self.option_kwargs.items(): for key, value in self.option_kwargs.items():
@ -98,27 +111,32 @@ class Option(object):
self.config_name = None 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(
'a long_option_name must also be specified.') "When specifying parse_from_config=True, "
self.config_name = long_option_name[2:].replace('-', '_') "a long_option_name must also be specified."
)
self.config_name = long_option_name[2:].replace("-", "_")
self._opt = None self._opt = None
def __repr__(self): # noqa: D105 def __repr__(self): # noqa: D105
return ( return (
'Option({0}, {1}, action={action}, default={default}, ' "Option({0}, {1}, action={action}, default={default}, "
'dest={dest}, type={type}, callback={callback}, help={help},' "dest={dest}, type={type}, callback={callback}, help={help},"
' callback={callback}, callback_args={callback_args}, ' " callback={callback}, callback_args={callback_args}, "
'callback_kwargs={callback_kwargs}, metavar={metavar})' "callback_kwargs={callback_kwargs}, metavar={metavar})"
).format(self.short_option_name, self.long_option_name, ).format(
**self.option_kwargs) self.short_option_name,
self.long_option_name,
**self.option_kwargs
)
def _make_dest(self, dest): def _make_dest(self, dest):
if dest: if dest:
return dest return dest
if self.long_option_name: if self.long_option_name:
return self.long_option_name[2:].replace('-', '_') return self.long_option_name[2:].replace("-", "_")
return self.short_option_name[1] return self.short_option_name[1]
def normalize(self, value, *normalize_args): def normalize(self, value, *normalize_args):
@ -136,33 +154,36 @@ class Option(object):
def normalize_from_setuptools(self, value): def normalize_from_setuptools(self, value):
"""Normalize the value received from setuptools.""" """Normalize the value received from setuptools."""
value = self.normalize(value) value = self.normalize(value)
if self.type == 'int' or self.action == 'count': if self.type == "int" or self.action == "count":
return int(value) return int(value)
if self.action in ('store_true', 'store_false'): if self.action in ("store_true", "store_false"):
value = str(value).upper() value = str(value).upper()
if value in ('1', 'T', 'TRUE', 'ON'): if value in ("1", "T", "TRUE", "ON"):
return True return True
if value in ('0', 'F', 'FALSE', 'OFF'): if value in ("0", "F", "FALSE", "OFF"):
return False return False
return value return value
def to_optparse(self): def to_optparse(self):
"""Convert a Flake8 Option to an optparse Option.""" """Convert a Flake8 Option to an optparse Option."""
if self._opt is None: if self._opt is None:
self._opt = optparse.Option(*self.option_args, self._opt = optparse.Option(
**self.option_kwargs) *self.option_args, **self.option_kwargs
)
return self._opt return self._opt
PluginVersion = collections.namedtuple("PluginVersion", PluginVersion = collections.namedtuple(
["name", "version", "local"]) "PluginVersion", ["name", "version", "local"]
)
class OptionManager(object): class OptionManager(object):
"""Manage Options and OptionParser while adding post-processing.""" """Manage Options and OptionParser while adding post-processing."""
def __init__(self, prog=None, version=None, def __init__(
usage='%prog [options] file file ...'): self, prog=None, version=None, usage="%prog [options] file file ..."
):
"""Initialize an instance of an OptionManager. """Initialize an instance of an OptionManager.
:param str prog: :param str prog:
@ -172,8 +193,9 @@ class OptionManager(object):
:param str usage: :param str usage:
Basic usage string used by the OptionParser. Basic usage string used by the OptionParser.
""" """
self.parser = optparse.OptionParser(prog=prog, version=version, self.parser = optparse.OptionParser(
usage=usage) prog=prog, version=version, usage=usage
)
self.config_options_dict = {} self.config_options_dict = {}
self.options = [] self.options = []
self.program_name = prog self.program_name = prog
@ -198,7 +220,7 @@ class OptionManager(object):
``short_option_name`` and ``long_option_name`` may be specified ``short_option_name`` and ``long_option_name`` may be specified
positionally as they are with optparse normally. positionally as they are with optparse normally.
""" """
if len(args) == 1 and args[0].startswith('--'): if len(args) == 1 and args[0].startswith("--"):
args = (None, args[0]) args = (None, args[0])
option = Option(*args, **kwargs) option = Option(*args, **kwargs)
self.parser.add_option(option.to_optparse()) self.parser.add_option(option.to_optparse())
@ -206,7 +228,7 @@ class OptionManager(object):
if option.parse_from_config: if option.parse_from_config:
name = option.config_name name = option.config_name
self.config_options_dict[name] = option self.config_options_dict[name] = option
self.config_options_dict[name.replace('_', '-')] = option self.config_options_dict[name.replace("_", "-")] = option
LOG.debug('Registered option "%s".', option) LOG.debug('Registered option "%s".', option)
def remove_from_default_ignore(self, error_codes): def remove_from_default_ignore(self, error_codes):
@ -216,13 +238,16 @@ class OptionManager(object):
List of strings that are the error/warning codes to attempt to List of strings that are the error/warning codes to attempt to
remove from the extended default ignore list. remove from the extended default ignore list.
""" """
LOG.debug('Removing %r from the default ignore list', error_codes) LOG.debug("Removing %r from the default ignore list", error_codes)
for error_code in error_codes: for error_code in error_codes:
try: try:
self.extended_default_ignore.remove(error_code) self.extended_default_ignore.remove(error_code)
except (ValueError, KeyError): except (ValueError, KeyError):
LOG.debug('Attempted to remove %s from default ignore' LOG.debug(
' but it was not a member of the list.', error_code) "Attempted to remove %s from default ignore"
" but it was not a member of the list.",
error_code,
)
def extend_default_ignore(self, error_codes): def extend_default_ignore(self, error_codes):
"""Extend the default ignore list with the error codes provided. """Extend the default ignore list with the error codes provided.
@ -231,7 +256,7 @@ class OptionManager(object):
List of strings that are the error/warning codes with which to List of strings that are the error/warning codes with which to
extend the default ignore list. extend the default ignore list.
""" """
LOG.debug('Extending default ignore list with %r', error_codes) LOG.debug("Extending default ignore list with %r", error_codes)
self.extended_default_ignore.update(error_codes) self.extended_default_ignore.update(error_codes)
def extend_default_select(self, error_codes): def extend_default_select(self, error_codes):
@ -241,11 +266,12 @@ class OptionManager(object):
List of strings that are the error/warning codes with which List of strings that are the error/warning codes with which
to extend the default select list. to extend the default select list.
""" """
LOG.debug('Extending default select list with %r', error_codes) LOG.debug("Extending default select list with %r", error_codes)
self.extended_default_select.update(error_codes) self.extended_default_select.update(error_codes)
def generate_versions(self, format_str='%(name)s: %(version)s', def generate_versions(
join_on=', '): self, format_str="%(name)s: %(version)s", join_on=", "
):
"""Generate a comma-separated list of versions of plugins.""" """Generate a comma-separated list of versions of plugins."""
return join_on.join( return join_on.join(
format_str % self.format_plugin(plugin) format_str % self.format_plugin(plugin)
@ -255,14 +281,17 @@ class OptionManager(object):
def update_version_string(self): def update_version_string(self):
"""Update the flake8 version string.""" """Update the flake8 version string."""
self.parser.version = ( self.parser.version = (
self.version + ' (' + self.generate_versions() + ') ' + self.version
utils.get_python_version() + " ("
+ self.generate_versions()
+ ") "
+ utils.get_python_version()
) )
def generate_epilog(self): def generate_epilog(self):
"""Create an epilog with the version and name of each of plugin.""" """Create an epilog with the version and name of each of plugin."""
plugin_version_format = '%(name)s: %(version)s' plugin_version_format = "%(name)s: %(version)s"
self.parser.epilog = 'Installed plugins: ' + self.generate_versions( self.parser.epilog = "Installed plugins: " + self.generate_versions(
plugin_version_format plugin_version_format
) )
@ -303,7 +332,10 @@ class OptionManager(object):
# Unfortunately, we need to rely on a private method here. # Unfortunately, we need to rely on a private method here.
try: try:
self.parser._process_args(largs, rargs, values) self.parser._process_args(largs, rargs, values)
except (optparse.BadOptionError, optparse.OptionValueError) as err: except (
optparse.BadOptionError,
optparse.OptionValueError,
) as err:
self.parser.largs.append(err.opt_str) self.parser.largs.append(err.opt_str)
args = largs + rargs args = largs + rargs

View file

@ -1,11 +1,11 @@
"""Independent implementation of a Trie tree.""" """Independent implementation of a Trie tree."""
__all__ = ('Trie', 'TrieNode') __all__ = ("Trie", "TrieNode")
def _iterate_stringlike_objects(string): def _iterate_stringlike_objects(string):
for i in range(len(string)): for i in range(len(string)):
yield string[i:i + 1] yield string[i : i + 1]
class Trie(object): class Trie(object):
@ -57,9 +57,7 @@ class TrieNode(object):
def __repr__(self): def __repr__(self):
"""Generate an easy to read representation of the node.""" """Generate an easy to read representation of the node."""
return 'TrieNode(prefix={0}, data={1})'.format( return "TrieNode(prefix={0}, data={1})".format(self.prefix, self.data)
self.prefix, self.data
)
def find_prefix(self, prefix): def find_prefix(self, prefix):
"""Find the prefix in the children of this node. """Find the prefix in the children of this node.

View file

@ -11,11 +11,11 @@ from flake8.plugins import notifier
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
__all__ = ( __all__ = (
'Checkers', "Checkers",
'Listeners', "Listeners",
'Plugin', "Plugin",
'PluginManager', "PluginManager",
'ReportFormatters', "ReportFormatters",
) )
NO_GROUP_FOUND = object() NO_GROUP_FOUND = object()
@ -55,11 +55,11 @@ class Plugin(object):
def to_dictionary(self): def to_dictionary(self):
"""Convert this plugin to a dictionary.""" """Convert this plugin to a dictionary."""
return { return {
'name': self.name, "name": self.name,
'parameters': self.parameters, "parameters": self.parameters,
'parameter_names': self.parameter_names, "parameter_names": self.parameter_names,
'plugin': self.plugin, "plugin": self.plugin,
'plugin_name': self.plugin_name, "plugin_name": self.plugin_name,
} }
def is_in_a_group(self): def is_in_a_group(self):
@ -75,7 +75,7 @@ class Plugin(object):
def group(self): def group(self):
"""Find and parse the group the plugin is in.""" """Find and parse the group the plugin is in."""
if self._group is None: if self._group is None:
name = self.name.split('.', 1) name = self.name.split(".", 1)
if len(name) > 1: if len(name) > 1:
self._group = name[0] self._group = name[0]
else: else:
@ -132,7 +132,7 @@ class Plugin(object):
@property @property
def off_by_default(self): def off_by_default(self):
"""Return whether the plugin is ignored by default.""" """Return whether the plugin is ignored by default."""
return getattr(self.plugin, 'off_by_default', False) return getattr(self.plugin, "off_by_default", False)
def execute(self, *args, **kwargs): def execute(self, *args, **kwargs):
r"""Call the plugin with \*args and \*\*kwargs.""" r"""Call the plugin with \*args and \*\*kwargs."""
@ -140,22 +140,21 @@ class Plugin(object):
def _load(self, verify_requirements): def _load(self, verify_requirements):
# Avoid relying on hasattr() here. # Avoid relying on hasattr() here.
resolve = getattr(self.entry_point, 'resolve', None) resolve = getattr(self.entry_point, "resolve", None)
require = getattr(self.entry_point, 'require', None) require = getattr(self.entry_point, "require", None)
if resolve and require: if resolve and require:
if verify_requirements: if verify_requirements:
LOG.debug('Verifying plugin "%s"\'s requirements.', LOG.debug('Verifying plugin "%s"\'s requirements.', self.name)
self.name)
require() require()
self._plugin = resolve() self._plugin = resolve()
else: else:
self._plugin = self.entry_point.load( self._plugin = self.entry_point.load(require=verify_requirements)
require=verify_requirements
)
if not callable(self._plugin): if not callable(self._plugin):
msg = ('Plugin %r is not a callable. It might be written for an' msg = (
' older version of flake8 and might not work with this' "Plugin %r is not a callable. It might be written for an"
' version' % self._plugin) " older version of flake8 and might not work with this"
" version" % self._plugin
)
LOG.critical(msg) LOG.critical(msg)
raise TypeError(msg) raise TypeError(msg)
@ -179,8 +178,7 @@ class Plugin(object):
except Exception as load_exception: except Exception as load_exception:
LOG.exception(load_exception) LOG.exception(load_exception)
failed_to_load = exceptions.FailedToLoadPlugin( failed_to_load = exceptions.FailedToLoadPlugin(
plugin=self, plugin=self, exception=load_exception
exception=load_exception,
) )
LOG.critical(str(failed_to_load)) LOG.critical(str(failed_to_load))
raise failed_to_load raise failed_to_load
@ -194,8 +192,11 @@ class Plugin(object):
try: try:
options.ignore.remove(self.name) options.ignore.remove(self.name)
except (ValueError, KeyError): except (ValueError, KeyError):
LOG.debug('Attempted to remove %s from the ignore list but it was ' LOG.debug(
'not a member of the list.', self.name) "Attempted to remove %s from the ignore list but it was "
"not a member of the list.",
self.name,
)
def disable(self, optmanager): def disable(self, optmanager):
"""Add the plugin name to the default ignore list.""" """Add the plugin name to the default ignore list."""
@ -203,7 +204,7 @@ class Plugin(object):
def provide_options(self, optmanager, options, extra_args): def provide_options(self, optmanager, options, extra_args):
"""Pass the parsed options and extra arguments to the plugin.""" """Pass the parsed options and extra arguments to the plugin."""
parse_options = getattr(self.plugin, 'parse_options', None) parse_options = getattr(self.plugin, "parse_options", None)
if parse_options is not None: if parse_options is not None:
LOG.debug('Providing options to plugin "%s".', self.name) LOG.debug('Providing options to plugin "%s".', self.name)
try: try:
@ -224,11 +225,12 @@ class Plugin(object):
:returns: :returns:
Nothing Nothing
""" """
add_options = getattr(self.plugin, 'add_options', None) add_options = getattr(self.plugin, "add_options", None)
if add_options is not None: if add_options is not None:
LOG.debug( LOG.debug(
'Registering options from plugin "%s" on OptionManager %r', 'Registering options from plugin "%s" on OptionManager %r',
self.name, optmanager self.name,
optmanager,
) )
add_options(optmanager) add_options(optmanager)
@ -239,8 +241,9 @@ class Plugin(object):
class PluginManager(object): # pylint: disable=too-few-public-methods class PluginManager(object): # pylint: disable=too-few-public-methods
"""Find and manage plugins consistently.""" """Find and manage plugins consistently."""
def __init__(self, namespace, def __init__(
verify_requirements=False, local_plugins=None): self, namespace, verify_requirements=False, local_plugins=None
):
"""Initialize the manager. """Initialize the manager.
:param str namespace: :param str namespace:
@ -348,7 +351,7 @@ def version_for(plugin):
except ImportError: except ImportError:
return None return None
return getattr(module, '__version__', None) return getattr(module, "__version__", None)
class PluginTypeManager(object): class PluginTypeManager(object):
@ -363,7 +366,8 @@ class PluginTypeManager(object):
Plugins from config file instead of entry-points Plugins from config file instead of entry-points
""" """
self.manager = PluginManager( self.manager = PluginManager(
self.namespace, local_plugins=local_plugins) self.namespace, local_plugins=local_plugins
)
self.plugins_loaded = False self.plugins_loaded = False
def __contains__(self, name): def __contains__(self, name):
@ -406,9 +410,11 @@ class PluginTypeManager(object):
def _generate_call_function(method_name, optmanager, *args, **kwargs): def _generate_call_function(method_name, optmanager, *args, **kwargs):
def generated_function(plugin): # noqa: D105 def generated_function(plugin): # noqa: D105
method = getattr(plugin, method_name, None) method = getattr(plugin, method_name, None)
if (method is not None and if method is not None and isinstance(
isinstance(method, collections.Callable)): method, collections.Callable
):
return method(optmanager, *args, **kwargs) return method(optmanager, *args, **kwargs)
return generated_function return generated_function
def load_plugins(self): def load_plugins(self):
@ -435,7 +441,7 @@ class PluginTypeManager(object):
"""Register all of the checkers' options to the OptionManager.""" """Register all of the checkers' options to the OptionManager."""
self.load_plugins() self.load_plugins()
call_register_options = self._generate_call_function( call_register_options = self._generate_call_function(
'register_options', optmanager, "register_options", optmanager
) )
list(self.manager.map(call_register_options)) list(self.manager.map(call_register_options))
@ -443,7 +449,7 @@ class PluginTypeManager(object):
def provide_options(self, optmanager, options, extra_args): def provide_options(self, optmanager, options, extra_args):
"""Provide parsed options and extra arguments to the plugins.""" """Provide parsed options and extra arguments to the plugins."""
call_provide_options = self._generate_call_function( call_provide_options = self._generate_call_function(
'provide_options', optmanager, options, extra_args, "provide_options", optmanager, options, extra_args
) )
list(self.manager.map(call_provide_options)) list(self.manager.map(call_provide_options))
@ -470,7 +476,7 @@ class NotifierBuilderMixin(object): # pylint: disable=too-few-public-methods
class Checkers(PluginTypeManager): class Checkers(PluginTypeManager):
"""All of the checkers registered through entry-points or config.""" """All of the checkers registered through entry-points or config."""
namespace = 'flake8.extension' namespace = "flake8.extension"
def checks_expecting(self, argument_name): def checks_expecting(self, argument_name):
"""Retrieve checks that expect an argument with the specified name. """Retrieve checks that expect an argument with the specified name.
@ -484,14 +490,15 @@ class Checkers(PluginTypeManager):
def to_dictionary(self): def to_dictionary(self):
"""Return a dictionary of AST and line-based plugins.""" """Return a dictionary of AST and line-based plugins."""
return { return {
'ast_plugins': [ "ast_plugins": [
plugin.to_dictionary() for plugin in self.ast_plugins plugin.to_dictionary() for plugin in self.ast_plugins
], ],
'logical_line_plugins': [ "logical_line_plugins": [
plugin.to_dictionary() for plugin in self.logical_line_plugins plugin.to_dictionary() for plugin in self.logical_line_plugins
], ],
'physical_line_plugins': [ "physical_line_plugins": [
plugin.to_dictionary() for plugin in self.physical_line_plugins plugin.to_dictionary()
for plugin in self.physical_line_plugins
], ],
} }
@ -508,7 +515,7 @@ class Checkers(PluginTypeManager):
# function to map over the plugins. # function to map over the plugins.
self.load_plugins() self.load_plugins()
call_register_options = self._generate_call_function( call_register_options = self._generate_call_function(
'register_options', optmanager, "register_options", optmanager
) )
def register_and_enable(plugin): def register_and_enable(plugin):
@ -521,27 +528,27 @@ class Checkers(PluginTypeManager):
@property @property
def ast_plugins(self): def ast_plugins(self):
"""List of plugins that expect the AST tree.""" """List of plugins that expect the AST tree."""
plugins = getattr(self, '_ast_plugins', []) plugins = getattr(self, "_ast_plugins", [])
if not plugins: if not plugins:
plugins = list(self.checks_expecting('tree')) plugins = list(self.checks_expecting("tree"))
self._ast_plugins = plugins self._ast_plugins = plugins
return plugins return plugins
@property @property
def logical_line_plugins(self): def logical_line_plugins(self):
"""List of plugins that expect the logical lines.""" """List of plugins that expect the logical lines."""
plugins = getattr(self, '_logical_line_plugins', []) plugins = getattr(self, "_logical_line_plugins", [])
if not plugins: if not plugins:
plugins = list(self.checks_expecting('logical_line')) plugins = list(self.checks_expecting("logical_line"))
self._logical_line_plugins = plugins self._logical_line_plugins = plugins
return plugins return plugins
@property @property
def physical_line_plugins(self): def physical_line_plugins(self):
"""List of plugins that expect the physical lines.""" """List of plugins that expect the physical lines."""
plugins = getattr(self, '_physical_line_plugins', []) plugins = getattr(self, "_physical_line_plugins", [])
if not plugins: if not plugins:
plugins = list(self.checks_expecting('physical_line')) plugins = list(self.checks_expecting("physical_line"))
self._physical_line_plugins = plugins self._physical_line_plugins = plugins
return plugins return plugins
@ -549,10 +556,10 @@ class Checkers(PluginTypeManager):
class Listeners(PluginTypeManager, NotifierBuilderMixin): class Listeners(PluginTypeManager, NotifierBuilderMixin):
"""All of the listeners registered through entry-points or config.""" """All of the listeners registered through entry-points or config."""
namespace = 'flake8.listen' namespace = "flake8.listen"
class ReportFormatters(PluginTypeManager): class ReportFormatters(PluginTypeManager):
"""All of the report formatters registered through entry-points/config.""" """All of the report formatters registered through entry-points/config."""
namespace = 'flake8.report' namespace = "flake8.report"

View file

@ -31,7 +31,7 @@ class Notifier(object):
path = error_code path = error_code
while path: while path:
node = self.listeners.find(path) node = self.listeners.find(path)
listeners = getattr(node, 'data', []) listeners = getattr(node, "data", [])
for listener in listeners: for listener in listeners:
yield listener yield listener
path = path[:-1] path = path[:-1]

View file

@ -18,35 +18,35 @@ from flake8 import utils
FLAKE8_PYFLAKES_CODES = { FLAKE8_PYFLAKES_CODES = {
'UnusedImport': 'F401', "UnusedImport": "F401",
'ImportShadowedByLoopVar': 'F402', "ImportShadowedByLoopVar": "F402",
'ImportStarUsed': 'F403', "ImportStarUsed": "F403",
'LateFutureImport': 'F404', "LateFutureImport": "F404",
'ImportStarUsage': 'F405', "ImportStarUsage": "F405",
'ImportStarNotPermitted': 'F406', "ImportStarNotPermitted": "F406",
'FutureFeatureNotDefined': 'F407', "FutureFeatureNotDefined": "F407",
'MultiValueRepeatedKeyLiteral': 'F601', "MultiValueRepeatedKeyLiteral": "F601",
'MultiValueRepeatedKeyVariable': 'F602', "MultiValueRepeatedKeyVariable": "F602",
'TooManyExpressionsInStarredAssignment': 'F621', "TooManyExpressionsInStarredAssignment": "F621",
'TwoStarredExpressions': 'F622', "TwoStarredExpressions": "F622",
'AssertTuple': 'F631', "AssertTuple": "F631",
'BreakOutsideLoop': 'F701', "BreakOutsideLoop": "F701",
'ContinueOutsideLoop': 'F702', "ContinueOutsideLoop": "F702",
'ContinueInFinally': 'F703', "ContinueInFinally": "F703",
'YieldOutsideFunction': 'F704', "YieldOutsideFunction": "F704",
'ReturnWithArgsInsideGenerator': 'F705', "ReturnWithArgsInsideGenerator": "F705",
'ReturnOutsideFunction': 'F706', "ReturnOutsideFunction": "F706",
'DefaultExceptNotLast': 'F707', "DefaultExceptNotLast": "F707",
'DoctestSyntaxError': 'F721', "DoctestSyntaxError": "F721",
'ForwardAnnotationSyntaxError': 'F722', "ForwardAnnotationSyntaxError": "F722",
'RedefinedWhileUnused': 'F811', "RedefinedWhileUnused": "F811",
'RedefinedInListComp': 'F812', "RedefinedInListComp": "F812",
'UndefinedName': 'F821', "UndefinedName": "F821",
'UndefinedExport': 'F822', "UndefinedExport": "F822",
'UndefinedLocal': 'F823', "UndefinedLocal": "F823",
'DuplicateArgument': 'F831', "DuplicateArgument": "F831",
'UnusedVariable': 'F841', "UnusedVariable": "F841",
'RaiseNotImplemented': 'F901', "RaiseNotImplemented": "F901",
} }
@ -54,8 +54,9 @@ def patch_pyflakes():
"""Add error codes to Pyflakes messages.""" """Add error codes to Pyflakes messages."""
for name, obj in vars(pyflakes.messages).items(): for name, obj in vars(pyflakes.messages).items():
if name[0].isupper() and obj.message: if name[0].isupper() and obj.message:
obj.flake8_msg = '%s %s' % ( obj.flake8_msg = "%s %s" % (
FLAKE8_PYFLAKES_CODES.get(name, 'F999'), obj.message FLAKE8_PYFLAKES_CODES.get(name, "F999"),
obj.message,
) )
@ -65,7 +66,7 @@ patch_pyflakes()
class FlakesChecker(pyflakes.checker.Checker): class FlakesChecker(pyflakes.checker.Checker):
"""Subclass the Pyflakes checker to conform with the flake8 API.""" """Subclass the Pyflakes checker to conform with the flake8 API."""
name = 'pyflakes' name = "pyflakes"
version = pyflakes.__version__ version = pyflakes.__version__
with_doctest = False with_doctest = False
include_in_doctest = [] include_in_doctest = []
@ -75,48 +76,65 @@ class FlakesChecker(pyflakes.checker.Checker):
"""Initialize the PyFlakes plugin with an AST tree and filename.""" """Initialize the PyFlakes plugin with an AST tree and filename."""
filename = utils.normalize_paths(filename)[0] filename = utils.normalize_paths(filename)[0]
with_doctest = self.with_doctest with_doctest = self.with_doctest
included_by = [include for include in self.include_in_doctest included_by = [
if include != '' and filename.startswith(include)] include
for include in self.include_in_doctest
if include != "" and filename.startswith(include)
]
if included_by: if included_by:
with_doctest = True with_doctest = True
for exclude in self.exclude_from_doctest: for exclude in self.exclude_from_doctest:
if exclude != '' and filename.startswith(exclude): if exclude != "" and filename.startswith(exclude):
with_doctest = False with_doctest = False
overlaped_by = [include for include in included_by overlaped_by = [
if include.startswith(exclude)] include
for include in included_by
if include.startswith(exclude)
]
if overlaped_by: if overlaped_by:
with_doctest = True with_doctest = True
super(FlakesChecker, self).__init__(tree, filename, super(FlakesChecker, self).__init__(
withDoctest=with_doctest) tree, filename, withDoctest=with_doctest
)
@classmethod @classmethod
def add_options(cls, parser): def add_options(cls, parser):
"""Register options for PyFlakes on the Flake8 OptionManager.""" """Register options for PyFlakes on the Flake8 OptionManager."""
parser.add_option( parser.add_option(
'--builtins', parse_from_config=True, comma_separated_list=True, "--builtins",
parse_from_config=True,
comma_separated_list=True,
help="define more built-ins, comma separated", help="define more built-ins, comma separated",
) )
parser.add_option( parser.add_option(
'--doctests', default=False, action='store_true', "--doctests",
default=False,
action="store_true",
parse_from_config=True, parse_from_config=True,
help="check syntax of the doctests", help="check syntax of the doctests",
) )
parser.add_option( parser.add_option(
'--include-in-doctest', default='', "--include-in-doctest",
dest='include_in_doctest', parse_from_config=True, default="",
comma_separated_list=True, normalize_paths=True, dest="include_in_doctest",
help='Run doctests only on these files', parse_from_config=True,
type='string', comma_separated_list=True,
normalize_paths=True,
help="Run doctests only on these files",
type="string",
) )
parser.add_option( parser.add_option(
'--exclude-from-doctest', default='', "--exclude-from-doctest",
dest='exclude_from_doctest', parse_from_config=True, default="",
comma_separated_list=True, normalize_paths=True, dest="exclude_from_doctest",
help='Skip these files when running doctests', parse_from_config=True,
type='string', comma_separated_list=True,
normalize_paths=True,
help="Skip these files when running doctests",
type="string",
) )
@classmethod @classmethod
@ -128,20 +146,20 @@ class FlakesChecker(pyflakes.checker.Checker):
included_files = [] included_files = []
for included_file in options.include_in_doctest: for included_file in options.include_in_doctest:
if included_file == '': if included_file == "":
continue continue
if not included_file.startswith((os.sep, './', '~/')): if not included_file.startswith((os.sep, "./", "~/")):
included_files.append('./' + included_file) included_files.append("./" + included_file)
else: else:
included_files.append(included_file) included_files.append(included_file)
cls.include_in_doctest = utils.normalize_paths(included_files) cls.include_in_doctest = utils.normalize_paths(included_files)
excluded_files = [] excluded_files = []
for excluded_file in options.exclude_from_doctest: for excluded_file in options.exclude_from_doctest:
if excluded_file == '': if excluded_file == "":
continue continue
if not excluded_file.startswith((os.sep, './', '~/')): if not excluded_file.startswith((os.sep, "./", "~/")):
excluded_files.append('./' + excluded_file) excluded_files.append("./" + excluded_file)
else: else:
excluded_files.append(excluded_file) excluded_files.append(excluded_file)
cls.exclude_from_doctest = utils.normalize_paths(excluded_files) cls.exclude_from_doctest = utils.normalize_paths(excluded_files)
@ -150,16 +168,20 @@ class FlakesChecker(pyflakes.checker.Checker):
cls.exclude_from_doctest cls.exclude_from_doctest
) )
if inc_exc: if inc_exc:
raise ValueError('"%s" was specified in both the ' raise ValueError(
'include-in-doctest and exclude-from-doctest ' '"%s" was specified in both the '
'options. You are not allowed to specify it in ' "include-in-doctest and exclude-from-doctest "
'both for doctesting.' % inc_exc) "options. You are not allowed to specify it in "
"both for doctesting." % inc_exc
)
def run(self): def run(self):
"""Run the plugin.""" """Run the plugin."""
for message in self.messages: for message in self.messages:
col = getattr(message, 'col', 0) col = getattr(message, "col", 0)
yield (message.lineno, yield (
col, message.lineno,
(message.flake8_msg % message.message_args), col,
message.__class__) (message.flake8_msg % message.message_args),
message.__class__,
)

View file

@ -15,10 +15,11 @@ PyCF_ONLY_AST = 1024
NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE])
# Work around Python < 2.6 behaviour, which does not generate NL after # Work around Python < 2.6 behaviour, which does not generate NL after
# a comment which is on a line by itself. # a comment which is on a line by itself.
COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' COMMENT_WITH_NL = tokenize.generate_tokens(["#\n"].pop).send(None)[1] == "#\n"
SKIP_TOKENS = frozenset([tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, SKIP_TOKENS = frozenset(
tokenize.DEDENT]) [tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT]
)
class FileProcessor(object): class FileProcessor(object):
@ -79,7 +80,7 @@ class FileProcessor(object):
#: Line number in the file #: Line number in the file
self.line_number = 0 self.line_number = 0
#: Current logical line #: Current logical line
self.logical_line = '' self.logical_line = ""
#: Maximum line length as configured by the user #: Maximum line length as configured by the user
self.max_line_length = options.max_line_length self.max_line_length = options.max_line_length
#: Whether the current physical line is multiline #: Whether the current physical line is multiline
@ -89,9 +90,9 @@ class FileProcessor(object):
#: Previous level of indentation #: Previous level of indentation
self.previous_indent_level = 0 self.previous_indent_level = 0
#: Previous logical line #: Previous logical line
self.previous_logical = '' self.previous_logical = ""
#: Previous unindented (i.e. top-level) logical line #: Previous unindented (i.e. top-level) logical line
self.previous_unindented_logical_line = '' self.previous_unindented_logical_line = ""
#: Current set of tokens #: Current set of tokens
self.tokens = [] self.tokens = []
#: Total number of lines in the file #: Total number of lines in the file
@ -99,9 +100,7 @@ class FileProcessor(object):
#: Verbosity level of Flake8 #: Verbosity level of Flake8
self.verbose = options.verbose self.verbose = options.verbose
#: Statistics dictionary #: Statistics dictionary
self.statistics = { self.statistics = {"logical lines": 0}
'logical lines': 0,
}
self._file_tokens = None self._file_tokens = None
@property @property
@ -115,9 +114,9 @@ class FileProcessor(object):
if self._file_tokens is None: if self._file_tokens is None:
line_iter = iter(self.lines) line_iter = iter(self.lines)
try: try:
self._file_tokens = list(tokenize.generate_tokens( self._file_tokens = list(
lambda: next(line_iter) tokenize.generate_tokens(lambda: next(line_iter))
)) )
except tokenize.TokenError as exc: except tokenize.TokenError as exc:
raise exceptions.InvalidSyntax(exc.message, exception=exc) raise exceptions.InvalidSyntax(exc.message, exception=exc)
@ -153,9 +152,9 @@ class FileProcessor(object):
def update_checker_state_for(self, plugin): def update_checker_state_for(self, plugin):
"""Update the checker_state attribute for the plugin.""" """Update the checker_state attribute for the plugin."""
if 'checker_state' in plugin['parameters']: if "checker_state" in plugin["parameters"]:
self.checker_state = self._checker_states.setdefault( self.checker_state = self._checker_states.setdefault(
plugin['name'], {} plugin["name"], {}
) )
def next_logical_line(self): def next_logical_line(self):
@ -194,10 +193,10 @@ class FileProcessor(object):
row_index = previous_row - 1 row_index = previous_row - 1
column_index = previous_column - 1 column_index = previous_column - 1
previous_text = self.lines[row_index][column_index] previous_text = self.lines[row_index][column_index]
if (previous_text == ',' or if previous_text == "," or (
(previous_text not in '{[(' and previous_text not in "{[(" and text not in "}])"
text not in '}])')): ):
text = ' ' + text text = " " + text
elif previous_column != start_column: elif previous_column != start_column:
text = line[previous_column:start_column] + text text = line[previous_column:start_column] + text
logical.append(text) logical.append(text)
@ -208,16 +207,16 @@ class FileProcessor(object):
def build_ast(self): def build_ast(self):
"""Build an abstract syntax tree from the list of lines.""" """Build an abstract syntax tree from the list of lines."""
return compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) return compile("".join(self.lines), "", "exec", PyCF_ONLY_AST)
def build_logical_line(self): def build_logical_line(self):
"""Build a logical line from the current tokens list.""" """Build a logical line from the current tokens list."""
comments, logical, mapping_list = self.build_logical_line_tokens() comments, logical, mapping_list = self.build_logical_line_tokens()
joined_comments = ''.join(comments) joined_comments = "".join(comments)
self.logical_line = ''.join(logical) self.logical_line = "".join(logical)
if defaults.NOQA_INLINE_REGEXP.search(joined_comments): if defaults.NOQA_INLINE_REGEXP.search(joined_comments):
self.noqa = True self.noqa = True
self.statistics['logical lines'] += 1 self.statistics["logical lines"] += 1
return joined_comments, self.logical_line, mapping_list return joined_comments, self.logical_line, mapping_list
def split_line(self, token): def split_line(self, token):
@ -225,7 +224,7 @@ class FileProcessor(object):
This also auto-increments the line number for the caller. This also auto-increments the line number for the caller.
""" """
for line in token[1].split('\n')[:-1]: for line in token[1].split("\n")[:-1]:
yield line yield line
self.line_number += 1 self.line_number += 1
@ -243,14 +242,16 @@ class FileProcessor(object):
LOG.exception(exc) LOG.exception(exc)
raise raise
else: else:
LOG.warning('Plugin requested optional parameter "%s" ' LOG.warning(
'but this is not an available parameter.', 'Plugin requested optional parameter "%s" '
param) "but this is not an available parameter.",
param,
)
return arguments return arguments
def check_physical_error(self, error_code, line): def check_physical_error(self, error_code, line):
"""Update attributes based on error code and line.""" """Update attributes based on error code and line."""
if error_code == 'E101': if error_code == "E101":
self.indent_char = line[0] self.indent_char = line[0]
def generate_tokens(self): def generate_tokens(self):
@ -282,7 +283,7 @@ class FileProcessor(object):
def next_line(self): def next_line(self):
"""Get the next line from the list.""" """Get the next line from the list."""
if self.line_number >= self.total_lines: if self.line_number >= self.total_lines:
return '' return ""
line = self.lines[self.line_number] line = self.lines[self.line_number]
self.line_number += 1 self.line_number += 1
if self.indent_char is None and line[:1] in defaults.WHITESPACE: if self.indent_char is None and line[:1] in defaults.WHITESPACE:
@ -292,8 +293,8 @@ class FileProcessor(object):
def read_lines(self): def read_lines(self):
# type: () -> List[str] # type: () -> List[str]
"""Read the lines for this file checker.""" """Read the lines for this file checker."""
if self.filename is None or self.filename == '-': if self.filename is None or self.filename == "-":
self.filename = self.options.stdin_display_name or 'stdin' self.filename = self.options.stdin_display_name or "stdin"
lines = self.read_lines_from_stdin() lines = self.read_lines_from_stdin()
else: else:
lines = self.read_lines_from_filename() lines = self.read_lines_from_filename()
@ -301,21 +302,20 @@ class FileProcessor(object):
def _readlines_py2(self): def _readlines_py2(self):
# type: () -> List[str] # type: () -> List[str]
with open(self.filename, 'rU') as fd: with open(self.filename, "rU") as fd:
return fd.readlines() return fd.readlines()
def _readlines_py3(self): def _readlines_py3(self):
# type: () -> List[str] # type: () -> List[str]
try: try:
with open(self.filename, 'rb') as fd: with open(self.filename, "rb") as fd:
(coding, lines) = tokenize.detect_encoding(fd.readline) (coding, lines) = tokenize.detect_encoding(fd.readline)
textfd = io.TextIOWrapper(fd, coding, line_buffering=True) textfd = io.TextIOWrapper(fd, coding, line_buffering=True)
return ([l.decode(coding) for l in lines] + return [l.decode(coding) for l in lines] + textfd.readlines()
textfd.readlines())
except (LookupError, SyntaxError, UnicodeError): except (LookupError, SyntaxError, UnicodeError):
# If we can't detect the codec with tokenize.detect_encoding, or # If we can't detect the codec with tokenize.detect_encoding, or
# the detected encoding is incorrect, just fallback to latin-1. # the detected encoding is incorrect, just fallback to latin-1.
with open(self.filename, encoding='latin-1') as fd: with open(self.filename, encoding="latin-1") as fd:
return fd.readlines() return fd.readlines()
def read_lines_from_filename(self): def read_lines_from_filename(self):
@ -346,8 +346,8 @@ class FileProcessor(object):
return True return True
elif any(defaults.NOQA_FILE.search(line) for line in self.lines): elif any(defaults.NOQA_FILE.search(line) for line in self.lines):
LOG.warning( LOG.warning(
'Detected `flake8: noqa` on line with code. To ignore an ' "Detected `flake8: noqa` on line with code. To ignore an "
'error on a line use `noqa` instead.', "error on a line use `noqa` instead."
) )
return False return False
else: else:
@ -367,25 +367,27 @@ class FileProcessor(object):
# If the first byte of the file is a UTF-8 BOM, strip it # If the first byte of the file is a UTF-8 BOM, strip it
if first_byte == 0xFEFF: if first_byte == 0xFEFF:
self.lines[0] = self.lines[0][1:] self.lines[0] = self.lines[0][1:]
elif self.lines[0][:3] == '\xEF\xBB\xBF': elif self.lines[0][:3] == "\xEF\xBB\xBF":
self.lines[0] = self.lines[0][3:] self.lines[0] = self.lines[0][3:]
def is_eol_token(token): def is_eol_token(token):
"""Check if the token is an end-of-line token.""" """Check if the token is an end-of-line token."""
return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' return token[0] in NEWLINE or token[4][token[3][1] :].lstrip() == "\\\n"
if COMMENT_WITH_NL: # If on Python 2.6 if COMMENT_WITH_NL: # If on Python 2.6
def is_eol_token(token, _is_eol_token=is_eol_token): def is_eol_token(token, _is_eol_token=is_eol_token):
"""Check if the token is an end-of-line token.""" """Check if the token is an end-of-line token."""
return (_is_eol_token(token) or return _is_eol_token(token) or (
(token[0] == tokenize.COMMENT and token[1] == token[4])) token[0] == tokenize.COMMENT and token[1] == token[4]
)
def is_multiline_string(token): def is_multiline_string(token):
"""Check if this is a multiline string.""" """Check if this is a multiline string."""
return token[0] == tokenize.STRING and '\n' in token[1] return token[0] == tokenize.STRING and "\n" in token[1]
def token_is_newline(token): def token_is_newline(token):
@ -401,9 +403,9 @@ def token_is_comment(token):
def count_parentheses(current_parentheses_count, token_text): def count_parentheses(current_parentheses_count, token_text):
"""Count the number of parentheses.""" """Count the number of parentheses."""
current_parentheses_count = current_parentheses_count or 0 current_parentheses_count = current_parentheses_count or 0
if token_text in '([{': if token_text in "([{":
return current_parentheses_count + 1 return current_parentheses_count + 1
elif token_text in '}])': elif token_text in "}])":
return current_parentheses_count - 1 return current_parentheses_count - 1
return current_parentheses_count return current_parentheses_count
@ -411,12 +413,14 @@ def count_parentheses(current_parentheses_count, token_text):
def log_token(log, token): def log_token(log, token):
"""Log a token to a provided logging object.""" """Log a token to a provided logging object."""
if token[2][0] == token[3][0]: if token[2][0] == token[3][0]:
pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) pos = "[%s:%s]" % (token[2][1] or "", token[3][1])
else: else:
pos = 'l.%s' % token[3][0] pos = "l.%s" % token[3][0]
log.log(flake8._EXTRA_VERBOSE, 'l.%s\t%s\t%s\t%r' % log.log(
(token[2][0], pos, tokenize.tok_name[token[0]], flake8._EXTRA_VERBOSE,
token[1])) "l.%s\t%s\t%s\t%r"
% (token[2][0], pos, tokenize.tok_name[token[0]], token[1]),
)
# NOTE(sigmavirus24): This was taken wholesale from # NOTE(sigmavirus24): This was taken wholesale from
@ -435,13 +439,13 @@ def expand_indent(line):
>>> expand_indent(' \t') >>> expand_indent(' \t')
16 16
""" """
if '\t' not in line: if "\t" not in line:
return len(line) - len(line.lstrip()) return len(line) - len(line.lstrip())
result = 0 result = 0
for char in line: for char in line:
if char == '\t': if char == "\t":
result = result // 8 * 8 + 8 result = result // 8 * 8 + 8
elif char == ' ': elif char == " ":
result += 1 result += 1
else: else:
break break
@ -470,4 +474,4 @@ def mutate_string(text):
if text[-3:] in ('"""', "'''"): if text[-3:] in ('"""', "'''"):
start += 2 start += 2
end -= 2 end -= 2
return text[:start] + 'x' * (end - start) + text[end:] return text[:start] + "x" * (end - start) + text[end:]

View file

@ -56,13 +56,14 @@ class Statistics(object):
:returns: :returns:
Generator of instances of :class:`Statistic` Generator of instances of :class:`Statistic`
""" """
matching_errors = sorted(key for key in self._store matching_errors = sorted(
if key.matches(prefix, filename)) key for key in self._store if key.matches(prefix, filename)
)
for error_code in matching_errors: for error_code in matching_errors:
yield self._store[error_code] yield self._store[error_code]
class Key(collections.namedtuple('Key', ['filename', 'code'])): class Key(collections.namedtuple("Key", ["filename", "code"])):
"""Simple key structure for the Statistics dictionary. """Simple key structure for the Statistics dictionary.
To make things clearer, easier to read, and more understandable, we use a To make things clearer, easier to read, and more understandable, we use a
@ -75,10 +76,7 @@ class Key(collections.namedtuple('Key', ['filename', 'code'])):
@classmethod @classmethod
def create_from(cls, error): def create_from(cls, error):
"""Create a Key from :class:`flake8.style_guide.Violation`.""" """Create a Key from :class:`flake8.style_guide.Violation`."""
return cls( return cls(filename=error.filename, code=error.code)
filename=error.filename,
code=error.code,
)
def matches(self, prefix, filename): def matches(self, prefix, filename):
"""Determine if this key matches some constraints. """Determine if this key matches some constraints.
@ -94,9 +92,9 @@ class Key(collections.namedtuple('Key', ['filename', 'code'])):
:rtype: :rtype:
bool bool
""" """
return (self.code.startswith(prefix) and return self.code.startswith(prefix) and (
(filename is None or filename is None or self.filename == filename
self.filename == filename)) )
class Statistic(object): class Statistic(object):

View file

@ -11,9 +11,7 @@ from flake8 import defaults
from flake8 import statistics from flake8 import statistics
from flake8 import utils from flake8 import utils
__all__ = ( __all__ = ("StyleGuide",)
'StyleGuide',
)
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -21,34 +19,32 @@ LOG = logging.getLogger(__name__)
try: try:
lru_cache = functools.lru_cache lru_cache = functools.lru_cache
except AttributeError: except AttributeError:
def lru_cache(maxsize=128, typed=False): def lru_cache(maxsize=128, typed=False):
"""Stub for missing lru_cache.""" """Stub for missing lru_cache."""
def fake_decorator(func): return lambda func: func
return func
return fake_decorator
# TODO(sigmavirus24): Determine if we need to use enum/enum34 # TODO(sigmavirus24): Determine if we need to use enum/enum34
class Selected(enum.Enum): class Selected(enum.Enum):
"""Enum representing an explicitly or implicitly selected code.""" """Enum representing an explicitly or implicitly selected code."""
Explicitly = 'explicitly selected' Explicitly = "explicitly selected"
Implicitly = 'implicitly selected' Implicitly = "implicitly selected"
class Ignored(enum.Enum): class Ignored(enum.Enum):
"""Enum representing an explicitly or implicitly ignored code.""" """Enum representing an explicitly or implicitly ignored code."""
Explicitly = 'explicitly ignored' Explicitly = "explicitly ignored"
Implicitly = 'implicitly ignored' Implicitly = "implicitly ignored"
class Decision(enum.Enum): class Decision(enum.Enum):
"""Enum representing whether a code should be ignored or selected.""" """Enum representing whether a code should be ignored or selected."""
Ignored = 'ignored error' Ignored = "ignored error"
Selected = 'selected error' Selected = "selected error"
@lru_cache(maxsize=512) @lru_cache(maxsize=512)
@ -57,14 +53,14 @@ def find_noqa(physical_line):
_Violation = collections.namedtuple( _Violation = collections.namedtuple(
'Violation', "Violation",
[ [
'code', "code",
'filename', "filename",
'line_number', "line_number",
'column_number', "column_number",
'text', "text",
'physical_line', "physical_line",
], ],
) )
@ -89,26 +85,29 @@ class Violation(_Violation):
return False return False
if physical_line is None: if physical_line is None:
physical_line = linecache.getline(self.filename, physical_line = linecache.getline(self.filename, self.line_number)
self.line_number)
noqa_match = find_noqa(physical_line) noqa_match = find_noqa(physical_line)
if noqa_match is None: if noqa_match is None:
LOG.debug('%r is not inline ignored', self) LOG.debug("%r is not inline ignored", self)
return False return False
codes_str = noqa_match.groupdict()['codes'] codes_str = noqa_match.groupdict()["codes"]
if codes_str is None: if codes_str is None:
LOG.debug('%r is ignored by a blanket ``# noqa``', self) LOG.debug("%r is ignored by a blanket ``# noqa``", self)
return True return True
codes = set(utils.parse_comma_separated_list(codes_str)) codes = set(utils.parse_comma_separated_list(codes_str))
if self.code in codes or self.code.startswith(tuple(codes)): if self.code in codes or self.code.startswith(tuple(codes)):
LOG.debug('%r is ignored specifically inline with ``# noqa: %s``', LOG.debug(
self, codes_str) "%r is ignored specifically inline with ``# noqa: %s``",
self,
codes_str,
)
return True return True
LOG.debug('%r is not ignored inline with ``# noqa: %s``', LOG.debug(
self, codes_str) "%r is not ignored inline with ``# noqa: %s``", self, codes_str
)
return False return False
def is_in(self, diff): def is_in(self, diff):
@ -158,30 +157,29 @@ class DecisionEngine(object):
"""Initialize the engine.""" """Initialize the engine."""
self.cache = {} self.cache = {}
self.selected = tuple(options.select) self.selected = tuple(options.select)
self.extended_selected = tuple(sorted( self.extended_selected = tuple(
options.extended_default_select, sorted(options.extended_default_select, reverse=True)
reverse=True,
))
self.enabled_extensions = tuple(options.enable_extensions)
self.all_selected = tuple(sorted(
self.selected + self.enabled_extensions,
reverse=True,
))
self.ignored = tuple(sorted(
itertools.chain(options.ignore, options.extend_ignore),
reverse=True,
))
self.using_default_ignore = set(self.ignored) == set(defaults.IGNORE)
self.using_default_select = (
set(self.selected) == set(defaults.SELECT)
) )
self.enabled_extensions = tuple(options.enable_extensions)
self.all_selected = tuple(
sorted(self.selected + self.enabled_extensions, reverse=True)
)
self.ignored = tuple(
sorted(
itertools.chain(options.ignore, options.extend_ignore),
reverse=True,
)
)
self.using_default_ignore = set(self.ignored) == set(defaults.IGNORE)
self.using_default_select = set(self.selected) == set(defaults.SELECT)
def _in_all_selected(self, code): def _in_all_selected(self, code):
return self.all_selected and code.startswith(self.all_selected) return self.all_selected and code.startswith(self.all_selected)
def _in_extended_selected(self, code): def _in_extended_selected(self, code):
return (self.extended_selected and return self.extended_selected and code.startswith(
code.startswith(self.extended_selected)) self.extended_selected
)
def was_selected(self, code): def was_selected(self, code):
# type: (str) -> Union[Selected, Ignored] # type: (str) -> Union[Selected, Ignored]
@ -264,11 +262,13 @@ class DecisionEngine(object):
# default select list. In either case, we want the violation to be # default select list. In either case, we want the violation to be
# selected. # selected.
return Decision.Selected return Decision.Selected
if (select is None and if select is None and (
(extra_select is None or not self.using_default_ignore)): extra_select is None or not self.using_default_ignore
):
return Decision.Ignored return Decision.Ignored
if ((select is None and not self.using_default_select) and if (select is None and not self.using_default_select) and (
(ignore is None and self.using_default_ignore)): ignore is None and self.using_default_ignore
):
return Decision.Ignored return Decision.Ignored
return Decision.Selected return Decision.Selected
@ -277,20 +277,24 @@ class DecisionEngine(object):
LOG.debug('Deciding if "%s" should be reported', code) LOG.debug('Deciding if "%s" should be reported', code)
selected = self.was_selected(code) selected = self.was_selected(code)
ignored = self.was_ignored(code) ignored = self.was_ignored(code)
LOG.debug('The user configured "%s" to be "%s", "%s"', LOG.debug(
code, selected, ignored) 'The user configured "%s" to be "%s", "%s"',
code,
selected,
ignored,
)
if ((selected is Selected.Explicitly or if (
selected is Selected.Implicitly) and selected is Selected.Explicitly or selected is Selected.Implicitly
ignored is Selected.Implicitly): ) and ignored is Selected.Implicitly:
decision = Decision.Selected decision = Decision.Selected
elif ((selected is Selected.Explicitly and elif (
ignored is Ignored.Explicitly) or selected is Selected.Explicitly and ignored is Ignored.Explicitly
(selected is Ignored.Implicitly and ) or (
ignored is Selected.Implicitly)): selected is Ignored.Implicitly and ignored is Selected.Implicitly
):
decision = self.more_specific_decision_for(code) decision = self.more_specific_decision_for(code)
elif (selected is Ignored.Implicitly or elif selected is Ignored.Implicitly or ignored is Ignored.Explicitly:
ignored is Ignored.Explicitly):
decision = Decision.Ignored # pylint: disable=R0204 decision = Decision.Ignored # pylint: disable=R0204
return decision return decision
@ -354,8 +358,15 @@ class StyleGuide(object):
""" """
return self.decider.decision_for(code) return self.decider.decision_for(code)
def handle_error(self, code, filename, line_number, column_number, text, def handle_error(
physical_line=None): self,
code,
filename,
line_number,
column_number,
text,
physical_line=None,
):
# type: (str, str, int, int, str) -> int # type: (str, str, int, int, str) -> int
"""Handle an error reported by a check. """Handle an error reported by a check.
@ -385,14 +396,24 @@ class StyleGuide(object):
# caught, column_number may be None. # caught, column_number may be None.
if not column_number: if not column_number:
column_number = 0 column_number = 0
error = Violation(code, filename, line_number, column_number + 1, error = Violation(
text, physical_line) code,
error_is_selected = (self.should_report_error(error.code) is filename,
Decision.Selected) line_number,
column_number + 1,
text,
physical_line,
)
error_is_selected = (
self.should_report_error(error.code) is Decision.Selected
)
is_not_inline_ignored = error.is_inline_ignored(disable_noqa) is False is_not_inline_ignored = error.is_inline_ignored(disable_noqa) is False
is_included_in_diff = error.is_in(self._parsed_diff) is_included_in_diff = error.is_in(self._parsed_diff)
if (error_is_selected and is_not_inline_ignored and if (
is_included_in_diff): error_is_selected
and is_not_inline_ignored
and is_included_in_diff
):
self.formatter.handle(error) self.formatter.handle(error)
self.stats.record(error) self.stats.record(error)
self.listener.notify(error.code, error) self.listener.notify(error.code, error)

View file

@ -9,9 +9,9 @@ import re
import sys import sys
import tokenize import tokenize
DIFF_HUNK_REGEXP = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') DIFF_HUNK_REGEXP = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$")
COMMA_SEPARATED_LIST_RE = re.compile(r'[,\s]') COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]")
LOCAL_PLUGIN_LIST_RE = re.compile(r'[,\t\n\r\f\v]') LOCAL_PLUGIN_LIST_RE = re.compile(r"[,\t\n\r\f\v]")
def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE): def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE):
@ -49,8 +49,9 @@ def normalize_paths(paths, parent=os.curdir):
:rtype: :rtype:
[str] [str]
""" """
return [normalize_path(p, parent) return [
for p in parse_comma_separated_list(paths)] normalize_path(p, parent) for p in parse_comma_separated_list(paths)
]
def normalize_path(path, parent=os.curdir): def normalize_path(path, parent=os.curdir):
@ -67,9 +68,10 @@ def normalize_path(path, parent=os.curdir):
# Unix style paths (/foo/bar). # Unix style paths (/foo/bar).
separator = os.path.sep separator = os.path.sep
# NOTE(sigmavirus24): os.path.altsep may be None # NOTE(sigmavirus24): os.path.altsep may be None
alternate_separator = os.path.altsep or '' alternate_separator = os.path.altsep or ""
if separator in path or (alternate_separator and if separator in path or (
alternate_separator in path): alternate_separator and alternate_separator in path
):
path = os.path.abspath(os.path.join(parent, path)) path = os.path.abspath(os.path.join(parent, path))
return path.rstrip(separator + alternate_separator) return path.rstrip(separator + alternate_separator)
@ -81,13 +83,13 @@ def _stdin_get_value_py3():
(coding, lines) = tokenize.detect_encoding(fd.readline) (coding, lines) = tokenize.detect_encoding(fd.readline)
return io.StringIO(stdin_value.decode(coding)) return io.StringIO(stdin_value.decode(coding))
except (LookupError, SyntaxError, UnicodeError): except (LookupError, SyntaxError, UnicodeError):
return io.StringIO(stdin_value.decode('utf-8')) return io.StringIO(stdin_value.decode("utf-8"))
def stdin_get_value(): def stdin_get_value():
# type: () -> str # type: () -> str
"""Get and cache it so plugins can use it.""" """Get and cache it so plugins can use it."""
cached_value = getattr(stdin_get_value, 'cached_stdin', None) cached_value = getattr(stdin_get_value, "cached_stdin", None)
if cached_value is None: if cached_value is None:
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
stdin_value = io.BytesIO(sys.stdin.read()) stdin_value = io.BytesIO(sys.stdin.read())
@ -118,7 +120,7 @@ def parse_unified_diff(diff=None):
if number_of_rows: if number_of_rows:
# NOTE(sigmavirus24): Below we use a slice because stdin may be # NOTE(sigmavirus24): Below we use a slice because stdin may be
# bytes instead of text on Python 3. # bytes instead of text on Python 3.
if line[:1] != '-': if line[:1] != "-":
number_of_rows -= 1 number_of_rows -= 1
# We're in the part of the diff that has lines starting with +, -, # We're in the part of the diff that has lines starting with +, -,
# and ' ' to show context and the changes made. We skip these # and ' ' to show context and the changes made. We skip these
@ -139,10 +141,10 @@ def parse_unified_diff(diff=None):
# +++ b/file.py\t100644 # +++ b/file.py\t100644
# Which is an example that has the new file permissions/mode. # Which is an example that has the new file permissions/mode.
# In this case we only care about the file name. # In this case we only care about the file name.
if line[:3] == '+++': if line[:3] == "+++":
current_path = line[4:].split('\t', 1)[0] current_path = line[4:].split("\t", 1)[0]
# NOTE(sigmavirus24): This check is for diff output from git. # NOTE(sigmavirus24): This check is for diff output from git.
if current_path[:2] == 'b/': if current_path[:2] == "b/":
current_path = current_path[2:] current_path = current_path[2:]
# We don't need to do anything else. We have set up our local # We don't need to do anything else. We have set up our local
# ``current_path`` variable. We can skip the rest of this loop. # ``current_path`` variable. We can skip the rest of this loop.
@ -179,7 +181,7 @@ def is_windows():
:rtype: :rtype:
bool bool
""" """
return os.name == 'nt' return os.name == "nt"
# NOTE(sigmavirus24): If and when https://bugs.python.org/issue27649 is fixed, # NOTE(sigmavirus24): If and when https://bugs.python.org/issue27649 is fixed,
@ -217,7 +219,7 @@ def is_using_stdin(paths):
:rtype: :rtype:
bool bool
""" """
return '-' in paths return "-" in paths
def _default_predicate(*args): def _default_predicate(*args):
@ -312,19 +314,23 @@ def parameters_for(plugin):
argspec = inspect.getargspec(func) argspec = inspect.getargspec(func)
start_of_optional_args = len(argspec[0]) - len(argspec[-1] or []) start_of_optional_args = len(argspec[0]) - len(argspec[-1] or [])
parameter_names = argspec[0] parameter_names = argspec[0]
parameters = collections.OrderedDict([ parameters = collections.OrderedDict(
(name, position < start_of_optional_args) [
for position, name in enumerate(parameter_names) (name, position < start_of_optional_args)
]) for position, name in enumerate(parameter_names)
]
)
else: else:
parameters = collections.OrderedDict([ parameters = collections.OrderedDict(
(parameter.name, parameter.default is parameter.empty) [
for parameter in inspect.signature(func).parameters.values() (parameter.name, parameter.default is parameter.empty)
if parameter.kind == parameter.POSITIONAL_OR_KEYWORD for parameter in inspect.signature(func).parameters.values()
]) if parameter.kind == parameter.POSITIONAL_OR_KEYWORD
]
)
if is_class: if is_class:
parameters.pop('self', None) parameters.pop("self", None)
return parameters return parameters
@ -341,5 +347,5 @@ def get_python_version():
try: try:
impl = platform.python_implementation() + " " impl = platform.python_implementation() + " "
except AttributeError: # Python 2.5 except AttributeError: # Python 2.5
impl = '' impl = ""
return '%s%s on %s' % (impl, platform.python_version(), platform.system()) return "%s%s on %s" % (impl, platform.python_version(), platform.system())

16
tox.ini
View file

@ -23,12 +23,22 @@ basepython = python2.7
skip_install = true skip_install = true
deps = deps =
wheel wheel
flake8-colors
commands = commands =
python setup.py -qq bdist_wheel python setup.py -qq bdist_wheel
pip install -U --pre --find-links ./dist/ flake8 pip install --force-reinstall -U --pre --find-links ./dist/ flake8
flake8 --version flake8 --version
flake8 src/flake8/ tests/ setup.py flake8 src/flake8/ tests/ setup.py
# Autoformatter
[testenv:black]
basepython = python3
skip_install = true
deps =
black
commands =
black -l 78 -N src/
# Linters # Linters
[testenv:flake8] [testenv:flake8]
basepython = python3 basepython = python3
@ -80,12 +90,14 @@ commands =
basepython = python3 basepython = python3
skip_install = true skip_install = true
deps = deps =
{[testenv:black]deps}
{[testenv:flake8]deps} {[testenv:flake8]deps}
{[testenv:pylint]deps} {[testenv:pylint]deps}
{[testenv:doc8]deps} {[testenv:doc8]deps}
{[testenv:readme]deps} {[testenv:readme]deps}
{[testenv:bandit]deps} {[testenv:bandit]deps}
commands = commands =
{[testenv:black]commands}
{[testenv:flake8]commands} {[testenv:flake8]commands}
{[testenv:pylint]commands} {[testenv:pylint]commands}
{[testenv:doc8]commands} {[testenv:doc8]commands}
@ -143,7 +155,7 @@ commands =
# defaults to selecting all other errors so we do not need select=E,F,W,I,D # defaults to selecting all other errors so we do not need select=E,F,W,I,D
# Once Flake8 3.0 is released and in a good state, we can use both and it will # Once Flake8 3.0 is released and in a good state, we can use both and it will
# work well \o/ # work well \o/
ignore = D203, W504 ignore = D203, W503, E203
exclude = exclude =
.tox, .tox,
.git, .git,