From 25dd8c445e61dfafef9356b4e6afa6a3ef97d336 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 10 Feb 2013 13:55:57 -0500 Subject: [PATCH 01/60] Fix tests --- flake8/tests/test_flakes.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/flake8/tests/test_flakes.py b/flake8/tests/test_flakes.py index 230b62a..b007955 100644 --- a/flake8/tests/test_flakes.py +++ b/flake8/tests/test_flakes.py @@ -1,5 +1,5 @@ from unittest import TestCase -from flakey import check, print_messages +from pyflakes.api import check code = """ @@ -50,15 +50,12 @@ class TestFlake(TestCase): def test_exception(self): for c in (code, code2, code3): warnings = check(code, '(stdin)') - warnings = print_messages(warnings) self.assertEqual(warnings, 0) def test_from_import_exception_in_scope(self): warnings = check(code_from_import_exception, '(stdin)') - warnings = print_messages(warnings) self.assertEqual(warnings, 0) def test_import_exception_in_scope(self): warnings = check(code_import_exception, '(stdin)') - warnings = print_messages(warnings) self.assertEqual(warnings, 0) From b97d869e201e9da48f0cd2e9843297d34109cbae Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 10 Feb 2013 15:10:03 -0500 Subject: [PATCH 02/60] Further simplification thanks to pep8 --- flake8/main.py | 17 +++++++++++++---- flake8/util.py | 42 +----------------------------------------- 2 files changed, 14 insertions(+), 45 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 8222e9c..3a763a9 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -5,11 +5,22 @@ import pyflakes.api import pyflakes.checker import select from flake8 import mccabe -from flake8.util import (_initpep8, skip_file, get_parser, read_config, - Flake8Reporter) +from flake8.util import _initpep8, skip_file, get_parser, Flake8Reporter pep8style = None +if sys.platform.startswith('win'): + pep8.DEFAULT_CONFIG = os.path.expanduser(r'~\.flake8') +else: + pep8.DEFAULT_CONFIG = os.path.join( + os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), + 'flake8' + ) + +PROJECT_CONFIG = ['.flake8'] +PROJECT_CONFIG.extend(pep8.PROJECT_CONFIG) +pep8.PROJECT_CONFIG = tuple(PROJECT_CONFIG) + def main(): global pep8style @@ -25,8 +36,6 @@ def main(): from flake8.hooks import install_hook install_hook() - read_config(opts, parser) - warnings = 0 stdin = None diff --git a/flake8/util.py b/flake8/util.py index 0afdd3c..6083e5c 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -45,50 +45,10 @@ def get_parser(): parser.add_option('-V', '--version', action='callback', callback=version, help='Print the version info for flake8') + parser.prog = os.path.basename(sys.argv[0]) return parser -def read_config(opts, opt_parser): - configs = ('.flake8', '.pep8', 'tox.ini', 'setup.cfg', - os.path.expanduser(r'~\.flake8'), - os.path.join(os.path.expanduser('~/.config'), 'flake8')) - parser = ConfigParser() - files_found = parser.read(configs) - if not (files_found and parser.has_section('flake8')): - return - - if opts.verbose: - print("Found local configuration file(s): {0}".format( - ', '.join(files_found))) - - option_list = dict([(o.dest, o.type or o.action) - for o in opt_parser.option_list]) - - for o in parser.options('flake8'): - v = parser.get('flake8', o) - - if opts.verbose > 1: - print(" {0} = {1}".format(o, v)) - - normed = o.replace('-', '_') - if normed not in option_list: - print("Unknown option: {0}".format(o)) - - opt_type = option_list[normed] - - if opt_type in ('int', 'count'): - v = int(v) - elif opt_type in ('store_true', 'store_false'): - v = True if v == 'True' else False - - setattr(opts, normed, v) - - for attr in ('filename', 'exclude', 'ignore', 'select'): - val = getattr(opts, attr) - if hasattr(val, 'split'): - setattr(opts, attr, val.split(',')) - - def skip_warning(warning, ignore=[]): # XXX quick dirty hack, just need to keep the line in the warning if not hasattr(warning, 'message') or ignore is None: From 10cc0f3391cfa3ef952d79b899bacfbaded6709f Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 11 Feb 2013 11:18:41 -0500 Subject: [PATCH 03/60] Update Buildout docs --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d52b6de..7b415d2 100644 --- a/README.rst +++ b/README.rst @@ -127,7 +127,7 @@ In order to use Flake8 inside a buildout, edit your buildout.cfg and add this:: eggs = flake8 ${buildout:eggs} entry-points = - flake8=flake8.run:main + flake8=flake8.main:main setuptools integration From 064f986d9a42201442e5d9cf2186afdcaa106bbf Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 13 Feb 2013 09:51:32 -0500 Subject: [PATCH 04/60] Fixes #69 Note to self: I also have to make sure the VCS hooks work with the new pyflakes API. --- flake8/main.py | 4 +++- flake8/util.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/flake8/main.py b/flake8/main.py index 3a763a9..66ee41c 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -65,13 +65,14 @@ def main(): if opts.exit_zero: raise SystemExit(0) - raise SystemExit(warnings) + raise SystemExit(warnings > 0) def check_file(path, ignore=(), complexity=-1, reporter=None): if pep8style.excluded(path): return 0 warnings = pyflakes.api.checkPath(path, reporter) + warnings -= reporter.ignored_warnings warnings += pep8style.input_file(path) if complexity > -1: warnings += mccabe.get_module_complexity(path, complexity) @@ -80,6 +81,7 @@ def check_file(path, ignore=(), complexity=-1, reporter=None): def check_code(code, ignore=(), complexity=-1, reporter=None): warnings = pyflakes.api.check(code, '', reporter) + warnings -= reporter.ignored_warnings warnings += pep8style.input_file('-', lines=code.split('\n')) if complexity > -1: warnings += mccabe.get_code_complexity(code, complexity) diff --git a/flake8/util.py b/flake8/util.py index 6083e5c..6412a6d 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -137,12 +137,14 @@ class Flake8Reporter(reporter.Reporter): def __init__(self, ignore=None): super(Flake8Reporter, self).__init__(sys.stdout, sys.stderr) self.ignore = ignore or [] + self.ignored_warnings = 0 def flake(self, message): classes = [error_mapping[i] for i in self.ignore if i in error_mapping] if (any(isinstance(message, c) for c in classes) or skip_warning(message)): + self.ignored_warnings += 1 return m = self.to_str(message) i = m.rfind(':') + 1 From 0b32d3373a3e77af35339fe1f6af1ec09c965207 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 13 Feb 2013 10:08:19 -0500 Subject: [PATCH 05/60] Add a possible file for collecting the ext work --- flake8/extensions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 flake8/extensions.py diff --git a/flake8/extensions.py b/flake8/extensions.py new file mode 100644 index 0000000..e69de29 From 4f57016809acc54e110c34fc2fdcbe6e2106e9c5 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 16:31:52 +0100 Subject: [PATCH 06/60] Unused import --- flake8/util.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/flake8/util.py b/flake8/util.py index 6412a6d..2dd6a80 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -3,18 +3,10 @@ import re import os import sys from io import StringIO -import optparse import pep8 import pyflakes from pyflakes import reporter, messages -try: - # Python 2 - from ConfigParser import ConfigParser -except ImportError: - # Python 3 - from configparser import ConfigParser - pep8style = None @@ -134,6 +126,7 @@ error_mapping = { class Flake8Reporter(reporter.Reporter): """Our own instance of a Reporter so that we can silence some messages.""" class_mapping = dict((k, c) for (c, v) in error_mapping.items() for k in v) + def __init__(self, ignore=None): super(Flake8Reporter, self).__init__(sys.stdout, sys.stderr) self.ignore = ignore or [] From 8a50be88b3846b4ef927206e83cc528b05d7b494 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 16:48:33 +0100 Subject: [PATCH 07/60] Prepare moving Pyflakes as an extension: setuptools becomes mandatory --- setup.py | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/setup.py b/setup.py index 29c3e1a..ad954df 100644 --- a/setup.py +++ b/setup.py @@ -1,33 +1,8 @@ -import sys -import os - -ispy3 = sys.version_info[0] == 3 -iswin = os.name == 'nt' - -kwargs = {} -scripts = ["flake8/flake8"] -if ispy3: - from distutils.core import setup # NOQA - if iswin: - scripts.append("scripts/flake8.cmd") -else: - try: - from setuptools import setup # NOQA - kwargs = { - 'entry_points': { - 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], - 'console_scripts': ['flake8 = flake8.main:main'] - }, - 'tests_require': ['nose'], - 'test_suite': 'nose.collector', - } - except ImportError: - from distutils.core import setup # NOQA - if iswin: - scripts.append("scripts/flake8.cmd") +from setuptools import setup from flake8 import __version__ +scripts = ["flake8/flake8"] README = open('README.rst').read() setup( @@ -42,7 +17,11 @@ setup( url="http://bitbucket.org/tarek/flake8", packages=["flake8", "flake8.tests"], scripts=scripts, - install_requires=["pyflakes==0.6.1", "pep8==1.4.2"], + install_requires=[ + "setuptools", + "pyflakes==0.6.1", + "pep8==1.4.2", + ], long_description=README, classifiers=[ "Environment :: Console", @@ -52,5 +31,10 @@ setup( "Topic :: Software Development", "Topic :: Utilities", ], - **kwargs + entry_points={ + 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], + 'console_scripts': ['flake8 = flake8.main:main'] + }, + tests_require=['nose'], + test_suite='nose.collector', ) From 844c95115450d63413aa14a728c94124f778f4c1 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 17:16:28 +0100 Subject: [PATCH 08/60] Make it clear that Flake8Command is currently broken --- flake8/main.py | 5 +++-- flake8/util.py | 16 ---------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 66ee41c..38e712d 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -5,7 +5,7 @@ import pyflakes.api import pyflakes.checker import select from flake8 import mccabe -from flake8.util import _initpep8, skip_file, get_parser, Flake8Reporter +from flake8.util import skip_file, get_parser, Flake8Reporter pep8style = None @@ -123,7 +123,8 @@ else: yield "%s.py" % filename def run(self): - _initpep8() + # XXX the setuptools command is currently broken + # _initpep8() # _get_python_files can produce the same file several # times, if one of its paths is a parent of another. Keep diff --git a/flake8/util.py b/flake8/util.py index 2dd6a80..23ee811 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -91,22 +91,6 @@ def skip_file(path, source=None): return _NOQA.search(content) is not None -def _initpep8(config_file=True): - # default pep8 setup - global pep8style - import pep8 - if pep8style is None: - pep8style = pep8.StyleGuide(config_file=config_file) - pep8style.options.physical_checks = pep8.find_checks('physical_line') - pep8style.options.logical_checks = pep8.find_checks('logical_line') - pep8style.options.counters = dict.fromkeys(pep8.BENCHMARK_KEYS, 0) - pep8style.options.messages = {} - if not pep8style.options.max_line_length: - pep8style.options.max_line_length = 79 - pep8style.args = [] - return pep8style - - error_mapping = { 'W402': (messages.UnusedImport,), 'W403': (messages.ImportShadowedByLoopVar,), From 20b8c5962ece6143c85fe9ba959fb6cd6ae843a5 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:18:49 +0100 Subject: [PATCH 09/60] Refactor to use extensions --- flake8/engine.py | 56 +++++++++++++++++ flake8/main.py | 131 ++++++++++---------------------------- flake8/util.py | 160 ++++++++++++----------------------------------- 3 files changed, 130 insertions(+), 217 deletions(-) create mode 100644 flake8/engine.py diff --git a/flake8/engine.py b/flake8/engine.py new file mode 100644 index 0000000..332c8c8 --- /dev/null +++ b/flake8/engine.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +import pep8 + +from flake8 import __version__ +from flake8.util import OrderedSet + + +def _register_extensions(): + """Register all the extensions.""" + extensions = OrderedSet() + extensions.add(('pep8', pep8.__version__)) + parser_hooks = [] + options_hooks = [] + try: + from pkg_resources import iter_entry_points + except ImportError: + pass + else: + for entry in iter_entry_points('flake8.extension'): + checker = entry.load() + pep8.register_check(checker, codes=[entry.name]) + extensions.add((checker.name, checker.version)) + if hasattr(checker, 'add_options'): + parser_hooks.append(checker.add_options) + if hasattr(checker, 'parse_options'): + options_hooks.append(checker.parse_options) + return extensions, parser_hooks, options_hooks + + +def get_parser(): + (extensions, parser_hooks, options_hooks) = _register_extensions() + details = ', '.join(['%s: %s' % ext for ext in extensions]) + parser = pep8.get_parser('flake8', '%s (%s)' % (__version__, details)) + for opt in ('--repeat', '--testsuite', '--doctest'): + try: + parser.remove_option(opt) + except ValueError: + pass + parser.add_option('--exit-zero', action='store_true', + help="exit with code 0 even if there are errors") + for parser_hook in parser_hooks: + parser_hook(parser) + parser.add_option('--install-hook', default=False, action='store_true', + help='Install the appropriate hook for this ' + 'repository.', dest='install_hook') + return parser, options_hooks + + +def get_style_guide(**kwargs): + """Parse the options and configure the checker.""" + kwargs['parser'], options_hooks = get_parser() + styleguide = pep8.StyleGuide(**kwargs) + options = styleguide.options + for options_hook in options_hooks: + options_hook(options) + return styleguide diff --git a/flake8/main.py b/flake8/main.py index 38e712d..4f8e120 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -1,91 +1,58 @@ import os import sys -import pep8 -import pyflakes.api -import pyflakes.checker import select -from flake8 import mccabe -from flake8.util import skip_file, get_parser, Flake8Reporter + +from flake8.engine import get_style_guide pep8style = None if sys.platform.startswith('win'): - pep8.DEFAULT_CONFIG = os.path.expanduser(r'~\.flake8') + DEFAULT_CONFIG = os.path.expanduser(r'~\.flake8') else: - pep8.DEFAULT_CONFIG = os.path.join( + DEFAULT_CONFIG = os.path.join( os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 'flake8' ) -PROJECT_CONFIG = ['.flake8'] -PROJECT_CONFIG.extend(pep8.PROJECT_CONFIG) -pep8.PROJECT_CONFIG = tuple(PROJECT_CONFIG) - def main(): - global pep8style - # parse out our flags so pep8 doesn't get confused - parser = get_parser() + """Parse options and run checks on Python source.""" + # Prepare + flake8_style = get_style_guide(parse_argv=True, config_file=DEFAULT_CONFIG) + options = flake8_style.options - # We then have to re-parse argv to make sure pep8 is properly initialized - pep8style = pep8.StyleGuide(parse_argv=True, config_file=True, - parser=parser) - opts = pep8style.options - - if opts.install_hook: + if options.install_hook: from flake8.hooks import install_hook install_hook() - warnings = 0 - stdin = None + # Run the checkers + report = flake8_style.check_files() - complexity = opts.max_complexity - builtins = set(opts.builtins.split(',')) - if builtins: - orig_builtins = set(pyflakes.checker._MAGIC_GLOBALS) - pyflakes.checker._MAGIC_GLOBALS = orig_builtins | builtins - - # This is needed so we can ignore some items - pyflakes_reporter = Flake8Reporter(opts.ignore) - - if pep8style.paths and opts.filename is not None: - for path in _get_python_files(pep8style.paths): - if path == '-': - if stdin is None: - stdin = read_stdin() - warnings += check_code(stdin, opts.ignore, complexity) - else: - warnings += check_file(path, opts.ignore, complexity, - pyflakes_reporter) - else: - stdin = read_stdin() - warnings += check_code(stdin, opts.ignore, complexity, - pyflakes_reporter) - - if opts.exit_zero: - raise SystemExit(0) - - raise SystemExit(warnings > 0) + # Print the final report + options = flake8_style.options + if options.statistics: + report.print_statistics() + if options.benchmark: + report.print_benchmark() + if report.total_errors: + if options.count: + sys.stderr.write(str(report.total_errors) + '\n') + if not options.exit_zero: + raise SystemExit(1) def check_file(path, ignore=(), complexity=-1, reporter=None): - if pep8style.excluded(path): - return 0 - warnings = pyflakes.api.checkPath(path, reporter) - warnings -= reporter.ignored_warnings - warnings += pep8style.input_file(path) - if complexity > -1: - warnings += mccabe.get_module_complexity(path, complexity) - return warnings + flake8_style = get_style_guide( + config_file=DEFAULT_CONFIG, + ignore=ignore, max_complexity=complexity, reporter=reporter) + return flake8_style.input_file(path) def check_code(code, ignore=(), complexity=-1, reporter=None): - warnings = pyflakes.api.check(code, '', reporter) - warnings -= reporter.ignored_warnings - warnings += pep8style.input_file('-', lines=code.split('\n')) - if complexity > -1: - warnings += mccabe.get_code_complexity(code, complexity) - return warnings + flake8_style = get_style_guide( + config_file=DEFAULT_CONFIG, + ignore=ignore, max_complexity=complexity, reporter=reporter) + return flake8_style.input_file('-', lines=code.split('\n')) def read_stdin(): @@ -123,37 +90,7 @@ else: yield "%s.py" % filename def run(self): - # XXX the setuptools command is currently broken - # _initpep8() - - # _get_python_files can produce the same file several - # times, if one of its paths is a parent of another. Keep - # a set of checked files to de-duplicate. - checked = set() - - warnings = 0 - for path in _get_python_files(self.distribution_files()): - if path not in checked: - warnings += check_file(path) - checked.add(path) - - raise SystemExit(warnings > 0) - - -def _get_python_files(paths): - for path in paths: - if os.path.isdir(path): - for dirpath, dirnames, filenames in os.walk(path): - if pep8style.excluded(dirpath): - dirnames[:] = [] - continue - for filename in filenames: - if not filename.endswith('.py'): - continue - fullpath = os.path.join(dirpath, filename) - if not skip_file(fullpath) or pep8style.excluded(fullpath): - yield fullpath - - else: - if not skip_file(path) or pep8style.excluded(path): - yield path + flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) + paths = self.distribution_files() + report = flake8_style.check_files(paths) + raise SystemExit(report.get_file_results() > 0) diff --git a/flake8/util.py b/flake8/util.py index 23ee811..3354274 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -1,73 +1,45 @@ +# -*- coding: utf-8 -*- from __future__ import with_statement +import os.path import re -import os -import sys -from io import StringIO -import pep8 -import pyflakes -from pyflakes import reporter, messages -pep8style = None +__all__ = ['ast', 'iter_child_nodes', 'OrderedSet', 'skip_file'] + +try: + import ast + iter_child_nodes = ast.iter_child_nodes +except ImportError: # Python 2.5 + import _ast as ast + + if 'decorator_list' not in ast.ClassDef._fields: + # Patch the missing attribute 'decorator_list' + ast.ClassDef.decorator_list = () + ast.FunctionDef.decorator_list = property(lambda s: s.decorators) + + def iter_child_nodes(node): + """ + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. + """ + if not node._fields: + return + for name in node._fields: + field = getattr(node, name, None) + if isinstance(field, ast.AST): + yield field + elif isinstance(field, list): + for item in field: + if isinstance(item, ast.AST): + yield item -def get_parser(): - """Create a custom OptionParser""" - from flake8 import __version__ - parser = pep8.get_parser() - - def version(option, opt, value, parser): - parser.print_usage() - parser.print_version() - sys.exit(0) - - parser.version = '{0} (pep8: {1}, pyflakes: {2})'.format( - __version__, pep8.__version__, pyflakes.__version__) - parser.remove_option('--version') - parser.add_option('--builtins', default='', dest='builtins', - help="append builtin functions to pyflakes' " - "_MAGIC_BUILTINS") - parser.add_option('--exit-zero', action='store_true', default=False, - help='Exit with status 0 even if there are errors') - parser.add_option('--max-complexity', default=-1, action='store', - type='int', help='McCabe complexity threshold') - parser.add_option('--install-hook', default=False, action='store_true', - help='Install the appropriate hook for this ' - 'repository.', dest='install_hook') - # don't overlap with pep8's verbose option - parser.add_option('-V', '--version', action='callback', - callback=version, - help='Print the version info for flake8') - parser.prog = os.path.basename(sys.argv[0]) - return parser - - -def skip_warning(warning, ignore=[]): - # XXX quick dirty hack, just need to keep the line in the warning - if not hasattr(warning, 'message') or ignore is None: - # McCabe's warnings cannot be skipped afaik, and they're all strings. - # And we'll get a TypeError otherwise - return False - if warning.message.split()[0] in ignore: - return True - if not os.path.isfile(warning.filename): - return False - - # XXX should cache the file in memory - with open(warning.filename) as f: - line = f.readlines()[warning.lineno - 1] - - return skip_line(line) - - -def skip_line(line): - def _noqa(line): - return line.strip().lower().endswith('# noqa') - skip = _noqa(line) - if not skip: - i = line.rfind(' #') - skip = _noqa(line[:i]) if i > 0 else False - return skip +class OrderedSet(list): + """List without duplicates.""" + __slots__ = () + def add(self, value): + if value not in self: + self.append(value) _NOQA = re.compile(r'flake8[:=]\s*noqa', re.I | re.M) @@ -78,61 +50,9 @@ def skip_file(path, source=None): # flake8: noqa """ if os.path.isfile(path): - f = open(path) - elif source: - f = StringIO(source) - else: + with open(path) as f: + source = f.read() + elif not source: return False - try: - content = f.read() - finally: - f.close() - return _NOQA.search(content) is not None - - -error_mapping = { - 'W402': (messages.UnusedImport,), - 'W403': (messages.ImportShadowedByLoopVar,), - 'W404': (messages.ImportStarUsed,), - 'W405': (messages.LateFutureImport,), - 'W801': (messages.RedefinedWhileUnused, - messages.RedefinedInListComp,), - 'W802': (messages.UndefinedName,), - 'W803': (messages.UndefinedExport,), - 'W804': (messages.UndefinedLocal, - messages.UnusedVariable,), - 'W805': (messages.DuplicateArgument,), - 'W806': (messages.Redefined,), -} - - -class Flake8Reporter(reporter.Reporter): - """Our own instance of a Reporter so that we can silence some messages.""" - class_mapping = dict((k, c) for (c, v) in error_mapping.items() for k in v) - - def __init__(self, ignore=None): - super(Flake8Reporter, self).__init__(sys.stdout, sys.stderr) - self.ignore = ignore or [] - self.ignored_warnings = 0 - - def flake(self, message): - classes = [error_mapping[i] for i in self.ignore if i in error_mapping] - - if (any(isinstance(message, c) for c in classes) or - skip_warning(message)): - self.ignored_warnings += 1 - return - m = self.to_str(message) - i = m.rfind(':') + 1 - message = '{0} {1}{2}'.format( - m[:i], self.class_mapping[message.__class__], m[i:] - ) - - super(Flake8Reporter, self).flake(message) - - def to_str(self, message): - try: - return unicode(message) - except NameError: - return str(message) + return _NOQA.search(source) is not None From 3ee195a8baf68f716df73ae6523bb490256101ae Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:19:30 +0100 Subject: [PATCH 10/60] Fix imports in flake8.run --- flake8/run.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/flake8/run.py b/flake8/run.py index e574c39..aca929e 100644 --- a/flake8/run.py +++ b/flake8/run.py @@ -2,10 +2,9 @@ """ Implementation of the command-line I{flake8} tool. """ -from flake8.hooks import git_hook, hg_hook # noqa +from flake8.hooks import git_hook, hg_hook # noqa +from flake8.main import check_code, check_file, Flake8Command # noqa from flake8.main import main -from flake8.main import (check_code, check_file, get_parser, # noqa - Flake8Command) # noqa if __name__ == '__main__': From 701ec32e3fcaf2766e44a2eae7c193a7300fd01e Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:21:37 +0100 Subject: [PATCH 11/60] Fix the commit hooks, hopefully --- flake8/hooks.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/flake8/hooks.py b/flake8/hooks.py index a99bae3..cd3e126 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -1,52 +1,52 @@ +from __future__ import with_statement import os import sys -from flake8.util import (_initpep8, pep8style, skip_file, get_parser, - ConfigParser) from subprocess import Popen, PIPE +try: + from configparser import ConfigParser +except ImportError: # Python 2 + from ConfigParser import ConfigParser + +from flake8.engine import get_parser, get_style_guide +from flake8.main import DEFAULT_CONFIG +from flake8.util import skip_file def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): - from flake8.main import check_file - _initpep8() - if ignore: - pep8style.options.ignore = ignore - - warnings = 0 - gitcmd = "git diff-index --cached --name-only HEAD" if lazy: gitcmd = gitcmd.replace('--cached ', '') _, files_modified, _ = run(gitcmd) - for filename in files_modified: - ext = os.path.splitext(filename)[-1] - if ext != '.py': - continue - if not os.path.exists(filename): - continue - warnings += check_file(path=filename, ignore=ignore, - complexity=complexity) + + flake8_style = get_style_guide( + config_file=DEFAULT_CONFIG, + ignore=ignore, max_complexity=complexity) + report = flake8_style.check_files(files_modified) if strict: - return warnings + return report.get_file_results() return 0 def hg_hook(ui, repo, **kwargs): - from flake8.main import check_file complexity = ui.config('flake8', 'complexity', default=-1) config = ui.config('flake8', 'config', default=True) - _initpep8(config_file=config) - warnings = 0 + if config is True: + config = DEFAULT_CONFIG - for file_ in _get_files(repo, **kwargs): - warnings += check_file(file_, complexity) + flake8_style = get_style_guide( + config_file=config, + max_complexity=complexity) + + paths = _get_files(repo, **kwargs) + report = flake8_style.check_files(paths) strict = ui.configbool('flake8', 'strict', default=True) if strict: - return warnings + return report.get_file_results() return 0 From 212364d89c1c936c41a596fd1d571c70d20839fe Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:22:34 +0100 Subject: [PATCH 12/60] Create and enable the Pyflakes extension --- flake8/_pyflakes.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ setup.py | 5 ++++- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 flake8/_pyflakes.py diff --git a/flake8/_pyflakes.py b/flake8/_pyflakes.py new file mode 100644 index 0000000..41189e0 --- /dev/null +++ b/flake8/_pyflakes.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +import pyflakes +import pyflakes.checker + + +def patch_pyflakes(): + """Add error codes to Pyflakes messages.""" + codes = dict([line.split()[::-1] for line in ( + 'F401 UnusedImport', + 'F402 ImportShadowedByLoopVar', + 'F403 ImportStarUsed', + 'F404 LateFutureImport', + 'F810 Redefined', # XXX Obsolete? + 'F811 RedefinedWhileUnused', + 'F812 RedefinedInListComp', + 'F821 UndefinedName', + 'F822 UndefinedExport', + 'F823 UndefinedLocal', + 'F831 DuplicateArgument', + 'F841 UnusedVariable', + )]) + + for name, obj in vars(pyflakes.messages).items(): + if name[0].isupper() and obj.message: + obj.flint_msg = '%s %s' % (codes.get(name, 'F999'), obj.message) +patch_pyflakes() + + +class FlakesChecker(pyflakes.checker.Checker): + """Subclass the Pyflakes checker to conform with the flint API.""" + name = 'pyflakes' + version = pyflakes.__version__ + + @classmethod + def add_options(cls, parser): + parser.add_option('--builtins', + help="define more built-ins, comma separated") + parser.config_options.append('builtins') + + @classmethod + def parse_options(cls, options): + if options.builtins: + cls.builtIns = cls.builtIns.union(options.builtins.split(',')) + + def run(self): + for m in self.messages: + yield m.lineno, 0, (m.flint_msg % m.message_args), m.__class__ diff --git a/setup.py b/setup.py index ad954df..8db2373 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,10 @@ setup( ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], - 'console_scripts': ['flake8 = flake8.main:main'] + 'console_scripts': ['flake8 = flake8.main:main'], + 'flake8.extension': [ + 'F = flake8._pyflakes:FlakesChecker', + ], }, tests_require=['nose'], test_suite='nose.collector', From 16b9fe108c852ad50f88c766989a8104fd4adead Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:27:43 +0100 Subject: [PATCH 13/60] Restore the ConfigParser import, used by the flake8.hooks --- flake8/util.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flake8/util.py b/flake8/util.py index 2dd6a80..0702b3e 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -7,6 +7,13 @@ import pep8 import pyflakes from pyflakes import reporter, messages +try: + # Python 2 + from ConfigParser import ConfigParser +except ImportError: + # Python 3 + from configparser import ConfigParser + pep8style = None From 740e8b9ad6fb9740750026d8e13bcd48bf803b22 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:30:04 +0100 Subject: [PATCH 14/60] Remove empty module --- flake8/extensions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 flake8/extensions.py diff --git a/flake8/extensions.py b/flake8/extensions.py deleted file mode 100644 index e69de29..0000000 From 1c6ed0526ab304aa72de0c2072f13fbe32f9770e Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:41:23 +0100 Subject: [PATCH 15/60] Upgrade McCabe checker as an extension; now it uses the AST --- flake8/mccabe.py | 197 +++++++++++++++++++++++++---------------------- setup.py | 1 + 2 files changed, 105 insertions(+), 93 deletions(-) diff --git a/flake8/mccabe.py b/flake8/mccabe.py index e93c87f..b6a5857 100644 --- a/flake8/mccabe.py +++ b/flake8/mccabe.py @@ -3,35 +3,26 @@ http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html MIT License. """ -try: - from compiler import parse # NOQA - iter_child_nodes = None # NOQA -except ImportError: - from ast import parse, iter_child_nodes # NOQA +from __future__ import with_statement import optparse import sys -from flake8.util import skip_warning from collections import defaultdict -WARNING_CODE = "W901" +from flake8.util import ast, iter_child_nodes + +version = 0.1 -class ASTVisitor: - - VERBOSE = 0 +class ASTVisitor(object): + """Performs a depth-first walk of the AST.""" def __init__(self): self.node = None self._cache = {} def default(self, node, *args): - if hasattr(node, 'getChildNodes'): - children = node.getChildNodes() - else: - children = iter_child_nodes(node) - - for child in children: + for child in iter_child_nodes(node): self.dispatch(child, *args) def dispatch(self, node, *args): @@ -42,7 +33,6 @@ class ASTVisitor: className = klass.__name__ meth = getattr(self.visitor, 'visit' + className, self.default) self._cache[klass] = meth - return meth(node, *args) def preorder(self, tree, visitor, *args): @@ -52,7 +42,7 @@ class ASTVisitor: self.dispatch(tree, *args) # XXX *args make sense? -class PathNode: +class PathNode(object): def __init__(self, name, look="circle"): self.name = name self.look = look @@ -65,7 +55,7 @@ class PathNode: return id(self) -class PathGraph: +class PathGraph(object): def __init__(self, name, entity, lineno): self.name = name self.entity = entity @@ -99,7 +89,7 @@ class PathGraphingAstVisitor(ASTVisitor): """ def __init__(self): - ASTVisitor.__init__(self) + super(PathGraphingAstVisitor, self).__init__() self.classname = "" self.graphs = {} self.reset() @@ -108,7 +98,11 @@ class PathGraphingAstVisitor(ASTVisitor): self.graph = None self.tail = None - def visitFunction(self, node): + def dispatch_list(self, node_list): + for node in node_list: + self.dispatch(node) + + def visitFunctionDef(self, node): if self.classname: entity = '%s%s' % (self.classname, node.name) @@ -121,7 +115,7 @@ class PathGraphingAstVisitor(ASTVisitor): # closure pathnode = self.appendPathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) bottom = PathNode("", look='point') self.graph.connect(self.tail, bottom) self.graph.connect(pathnode, bottom) @@ -130,16 +124,14 @@ class PathGraphingAstVisitor(ASTVisitor): self.graph = PathGraph(name, entity, node.lineno) pathnode = PathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) self.graphs["%s%s" % (self.classname, node.name)] = self.graph self.reset() - visitFunctionDef = visitFunction - - def visitClass(self, node): + def visitClassDef(self, node): old_classname = self.classname self.classname += node.name + "." - self.default(node) + self.dispatch_list(node.body) self.classname = old_classname def appendPathNode(self, name): @@ -158,9 +150,9 @@ class PathGraphingAstVisitor(ASTVisitor): name = "Stmt %d" % lineno self.appendPathNode(name) - visitAssert = visitAssign = visitAssTuple = visitPrint = \ - visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ - visitPass = visitDiscard = visitGlobal = visitReturn = \ + visitAssert = visitAssign = visitAugAssign = visitDelete = visitPrint = \ + visitRaise = visitYield = visitImport = visitCall = visitSubscript = \ + visitPass = visitContinue = visitBreak = visitGlobal = visitReturn = \ visitSimpleStatement def visitLoop(self, node): @@ -171,101 +163,119 @@ class PathGraphingAstVisitor(ASTVisitor): self.graph = PathGraph(name, name, node.lineno) pathnode = PathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) self.graphs["%s%s" % (self.classname, name)] = self.graph self.reset() else: pathnode = self.appendPathNode(name) self.tail = pathnode - self.default(node.body) + self.dispatch_list(node.body) bottom = PathNode("", look='point') self.graph.connect(self.tail, bottom) self.graph.connect(pathnode, bottom) self.tail = bottom - # TODO: else clause in node.else_ + # TODO: else clause in node.orelse visitFor = visitWhile = visitLoop def visitIf(self, node): name = "If %d" % node.lineno pathnode = self.appendPathNode(name) - if not pathnode: - return # TODO: figure out what to do with if's outside def's. loose_ends = [] - for t, n in node.tests: + self.dispatch_list(node.body) + loose_ends.append(self.tail) + if node.orelse: self.tail = pathnode - self.default(n) - loose_ends.append(self.tail) - if node.else_: - self.tail = pathnode - self.default(node.else_) + self.dispatch_list(node.orelse) loose_ends.append(self.tail) else: loose_ends.append(pathnode) - bottom = PathNode("", look='point') - for le in loose_ends: - self.graph.connect(le, bottom) - self.tail = bottom + if pathnode: + bottom = PathNode("", look='point') + for le in loose_ends: + self.graph.connect(le, bottom) + self.tail = bottom - # TODO: visitTryExcept - # TODO: visitTryFinally - # TODO: visitWith + def visitTryExcept(self, node): + name = "TryExcept %d" % node.lineno + pathnode = self.appendPathNode(name) + loose_ends = [] + self.dispatch_list(node.body) + loose_ends.append(self.tail) + for handler in node.handlers: + self.tail = pathnode + self.dispatch_list(handler.body) + loose_ends.append(self.tail) + if pathnode: + bottom = PathNode("", look='point') + for le in loose_ends: + self.graph.connect(le, bottom) + self.tail = bottom - # XXX todo: determine which ones can add to the complexity - # py2 - # TODO: visitStmt - # TODO: visitAssName - # TODO: visitCallFunc - # TODO: visitConst - - # py3 - # TODO: visitStore - # TODO: visitCall - # TODO: visitLoad - # TODO: visitNum - # TODO: visitarguments - # TODO: visitExpr + def visitWith(self, node): + name = "With %d" % node.lineno + self.appendPathNode(name) + self.dispatch_list(node.body) -def get_code_complexity(code, min=7, filename='stdin'): - complex = [] +class McCabeChecker(object): + """McCabe cyclomatic complexity checker.""" + name = 'mccabe' + version = version + _code = 'C901' + _error_tmpl = "C901 %r is too complex (%d)" + max_complexity = 0 + + def __init__(self, tree, filename): + self.tree = tree + + @classmethod + def add_options(cls, parser): + parser.add_option('--max-complexity', default=-1, action='store', + type='int', help="McCabe complexity threshold") + parser.config_options.append('max-complexity') + + @classmethod + def parse_options(cls, options): + cls.max_complexity = options.max_complexity + + def run(self): + if self.max_complexity < 0: + return + visitor = PathGraphingAstVisitor() + visitor.preorder(self.tree, visitor) + for graph in visitor.graphs.values(): + graph_complexity = graph.complexity() + if graph_complexity >= self.max_complexity: + text = self._error_tmpl % (graph.entity, graph_complexity) + yield graph.lineno, 0, text, type(self) + + +def get_code_complexity(code, min_complexity=7, filename='stdin'): try: - ast = parse(code) - except (AttributeError, SyntaxError): + tree = compile(code, filename, "exec", ast.PyCF_ONLY_AST) + except SyntaxError: e = sys.exc_info()[1] sys.stderr.write("Unable to parse %s: %s\n" % (filename, e)) return 0 - visitor = PathGraphingAstVisitor() - visitor.preorder(ast, visitor) - for graph in visitor.graphs.values(): - if graph is None: - # ? - continue - if graph.complexity() >= min: - graph.filename = filename - if not skip_warning(graph): - msg = '%s:%d:1: %s %r is too complex (%d)' % ( - filename, - graph.lineno, - WARNING_CODE, - graph.entity, - graph.complexity(), - ) - complex.append(msg) + complx = [] + McCabeChecker.max_complexity = min_complexity + for lineno, offset, text, check in McCabeChecker(tree, filename): + complx.append('%s:%d:1: %s' % (filename, lineno, text)) - if len(complex) == 0: + if len(complx) == 0: return 0 - - print('\n'.join(complex)) - return len(complex) + print('\n'.join(complx)) + return len(complx) -def get_module_complexity(module_path, min=7): +def get_module_complexity(module_path, min_complexity=7): """Returns the complexity of a module""" - code = open(module_path, "rU").read() + '\n\n' - return get_code_complexity(code, min, filename=module_path) + with open(module_path, "rU") as mod: + code = mod.read() + return get_code_complexity(code, min_complexity, filename=module_path) def main(argv): @@ -278,10 +288,11 @@ def main(argv): options, args = opar.parse_args(argv) - text = open(args[0], "rU").read() + '\n\n' - ast = parse(text) + with open(args[0], "rU") as mod: + code = mod.read() + tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST) visitor = PathGraphingAstVisitor() - visitor.preorder(ast, visitor) + visitor.preorder(tree, visitor) if options.dot: print('graph {') diff --git a/setup.py b/setup.py index 8db2373..aab1ebf 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ setup( 'console_scripts': ['flake8 = flake8.main:main'], 'flake8.extension': [ 'F = flake8._pyflakes:FlakesChecker', + 'C90 = flake8.mccabe:McCabeChecker', ], }, tests_require=['nose'], From 321bbce17fc56e5f3e5418a1670e8b515f5f91bb Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:53:01 +0100 Subject: [PATCH 16/60] Fix the get_*_complexity functions --- flake8/mccabe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake8/mccabe.py b/flake8/mccabe.py index b6a5857..8b7ac4b 100644 --- a/flake8/mccabe.py +++ b/flake8/mccabe.py @@ -262,7 +262,7 @@ def get_code_complexity(code, min_complexity=7, filename='stdin'): complx = [] McCabeChecker.max_complexity = min_complexity - for lineno, offset, text, check in McCabeChecker(tree, filename): + for lineno, offset, text, check in McCabeChecker(tree, filename).run(): complx.append('%s:%d:1: %s' % (filename, lineno, text)) if len(complx) == 0: From df6375c952f0681a3f66596ca77701db8a193b6b Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 19:53:56 +0100 Subject: [PATCH 17/60] Fix the codes in the tests --- flake8/tests/test_mccabe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake8/tests/test_mccabe.py b/flake8/tests/test_mccabe.py index 887599f..1814699 100644 --- a/flake8/tests/test_mccabe.py +++ b/flake8/tests/test_mccabe.py @@ -36,6 +36,6 @@ class McCabeTest(unittest.TestCase): self.assertEqual(get_code_complexity(_GLOBAL, 1), 2) self.out.seek(0) res = self.out.read().strip().split('\n') - wanted = ["stdin:5:1: W901 'a' is too complex (4)", - "stdin:2:1: W901 'Loop 2' is too complex (2)"] + wanted = ["stdin:5:1: C901 'a' is too complex (4)", + "stdin:2:1: C901 'Loop 2' is too complex (2)"] self.assertEqual(res, wanted) From 015da9fabbfa1eb4cad723099ff61f734e3e0b8b Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 20:23:58 +0100 Subject: [PATCH 18/60] Remove unused global --- flake8/main.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 4f8e120..e5129f0 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -4,8 +4,6 @@ import select from flake8.engine import get_style_guide -pep8style = None - if sys.platform.startswith('win'): DEFAULT_CONFIG = os.path.expanduser(r'~\.flake8') else: From a13bdad3d8f01bf4057c896e5171f0cc8ea06b57 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 21:07:31 +0100 Subject: [PATCH 19/60] Wrong exit code returned by the setuptools Flake8Command --- flake8/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake8/main.py b/flake8/main.py index e5129f0..79c20e2 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -91,4 +91,4 @@ else: flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) paths = self.distribution_files() report = flake8_style.check_files(paths) - raise SystemExit(report.get_file_results() > 0) + raise SystemExit(report.total_errors > 0) From 29b74f9b37a6efd61800487f2ef1c5432e0264a4 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 21:26:28 +0100 Subject: [PATCH 20/60] Wrong exit code for the VCS hooks --- flake8/hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake8/hooks.py b/flake8/hooks.py index cd3e126..68df056 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -25,7 +25,7 @@ def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): report = flake8_style.check_files(files_modified) if strict: - return report.get_file_results() + return report.total_errors return 0 @@ -46,7 +46,7 @@ def hg_hook(ui, repo, **kwargs): strict = ui.configbool('flake8', 'strict', default=True) if strict: - return report.get_file_results() + return report.total_errors return 0 From b721a38b3930a90cdb0b526dfc9b94c7b0f18cd9 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 13 Feb 2013 22:03:56 +0100 Subject: [PATCH 21/60] Fix Pyflakes tests for Python 2.5, and add a reporter --- flake8/tests/test_flakes.py | 43 ++++++++++++++++++++++++++++++++----- flake8/tests/test_mccabe.py | 1 + 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/flake8/tests/test_flakes.py b/flake8/tests/test_flakes.py index b007955..79ac253 100644 --- a/flake8/tests/test_flakes.py +++ b/flake8/tests/test_flakes.py @@ -1,8 +1,30 @@ +# -*- coding: utf-8 -*- +import sys + from unittest import TestCase from pyflakes.api import check -code = """ +class FlakesTestReporter(object): + def __init__(self): + self.messages = [] + self.flakes = self.messages.append + + def unexpectedError(self, filename, msg): + self.flakes('[unexpectedError] %s: %s' % (filename, msg)) + + def syntaxError(self, filename, msg, lineno, offset, text): + self.flakes('[syntaxError] %s:%d: %s' % (filename, lineno, msg)) + + +code0 = """ +try: + pass +except ValueError, err: + print(err) +""" + +code1 = """ try: pass except ValueError as err: @@ -48,14 +70,25 @@ except foo.SomeException: class TestFlake(TestCase): def test_exception(self): - for c in (code, code2, code3): - warnings = check(code, '(stdin)') + codes = [code1, code2, code3] + if sys.version_info < (2, 6): + codes[0] = code0 + elif sys.version_info < (3,): + codes.insert(0, code0) + for code in codes: + reporter = FlakesTestReporter() + warnings = check(code, '(stdin)', reporter) + self.assertFalse(reporter.messages) self.assertEqual(warnings, 0) def test_from_import_exception_in_scope(self): - warnings = check(code_from_import_exception, '(stdin)') + reporter = FlakesTestReporter() + warnings = check(code_from_import_exception, '(stdin)', reporter) + self.assertFalse(reporter.messages) self.assertEqual(warnings, 0) def test_import_exception_in_scope(self): - warnings = check(code_import_exception, '(stdin)') + reporter = FlakesTestReporter() + warnings = check(code_import_exception, '(stdin)', reporter) + self.assertFalse(reporter.messages) self.assertEqual(warnings, 0) diff --git a/flake8/tests/test_mccabe.py b/flake8/tests/test_mccabe.py index 1814699..a220c83 100644 --- a/flake8/tests/test_mccabe.py +++ b/flake8/tests/test_mccabe.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import unittest import sys try: From 3a99cd18b6600ede5589e0e93a8600c2f6bf02de Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 14 Feb 2013 16:16:16 +0100 Subject: [PATCH 22/60] Turn it to an alpha version --- flake8/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake8/__init__.py b/flake8/__init__.py index a3332a5..1db7ef8 100644 --- a/flake8/__init__.py +++ b/flake8/__init__.py @@ -1,2 +1,2 @@ -__version__ = '2.0' +__version__ = '2.0a1' From 6055fcea2c7eb680f3f675a5db1576f5a04e5896 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 14 Feb 2013 17:21:34 +0100 Subject: [PATCH 23/60] Remove leftovers of the Flint merge --- flake8/_pyflakes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake8/_pyflakes.py b/flake8/_pyflakes.py index 41189e0..86d28ed 100644 --- a/flake8/_pyflakes.py +++ b/flake8/_pyflakes.py @@ -22,12 +22,12 @@ def patch_pyflakes(): for name, obj in vars(pyflakes.messages).items(): if name[0].isupper() and obj.message: - obj.flint_msg = '%s %s' % (codes.get(name, 'F999'), obj.message) + obj.flake8_msg = '%s %s' % (codes.get(name, 'F999'), obj.message) patch_pyflakes() class FlakesChecker(pyflakes.checker.Checker): - """Subclass the Pyflakes checker to conform with the flint API.""" + """Subclass the Pyflakes checker to conform with the flake8 API.""" name = 'pyflakes' version = pyflakes.__version__ @@ -44,4 +44,4 @@ class FlakesChecker(pyflakes.checker.Checker): def run(self): for m in self.messages: - yield m.lineno, 0, (m.flint_msg % m.message_args), m.__class__ + yield m.lineno, 0, (m.flake8_msg % m.message_args), m.__class__ From d242899fb54f42cef90378fdf2436be65968531b Mon Sep 17 00:00:00 2001 From: Marc Schlaich Date: Wed, 20 Feb 2013 11:36:30 +0100 Subject: [PATCH 24/60] Added some messages for pep8 --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 7b415d2..79ae5bd 100644 --- a/README.rst +++ b/README.rst @@ -177,6 +177,8 @@ pep8: - E111: indentation is not a multiple of four - E112: expected an indented block - E113: unexpected indentation +- E127: continuation line over-indented for visual indent +- E128: continuation line under-indented for visual indent - E201: whitespace after char - E202: whitespace before char - E203: whitespace before char From a6977c19161bbfcb2664cad7a2f35f5d3428fd93 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:39:54 +0100 Subject: [PATCH 25/60] Add Ian and me as contributors --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 3200c2f..6e7c642 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -14,6 +14,8 @@ Contributors (by order of appearance) : - Miki Tebeka - David Cramer - Peter Teichman +- Ian Cordasco - Oleg Broytman - Marc Labbé - Bruno Miguel Custódio +- Florent Xicluna From e4afd25312f2661b9b4cf4c8d75ee605f04b9701 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:41:29 +0100 Subject: [PATCH 26/60] Small refactoring in the hooks module --- flake8/hooks.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/flake8/hooks.py b/flake8/hooks.py index 68df056..efbc063 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import with_statement import os import sys @@ -20,8 +21,7 @@ def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): _, files_modified, _ = run(gitcmd) flake8_style = get_style_guide( - config_file=DEFAULT_CONFIG, - ignore=ignore, max_complexity=complexity) + config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) report = flake8_style.check_files(files_modified) if strict: @@ -32,18 +32,16 @@ def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): def hg_hook(ui, repo, **kwargs): complexity = ui.config('flake8', 'complexity', default=-1) + strict = ui.configbool('flake8', 'strict', default=True) config = ui.config('flake8', 'config', default=True) if config is True: config = DEFAULT_CONFIG - flake8_style = get_style_guide( - config_file=config, - max_complexity=complexity) - paths = _get_files(repo, **kwargs) - report = flake8_style.check_files(paths) - strict = ui.configbool('flake8', 'strict', default=True) + flake8_style = get_style_guide( + config_file=config, max_complexity=complexity) + report = flake8_style.check_files(paths) if strict: return report.total_errors @@ -53,9 +51,9 @@ def hg_hook(ui, repo, **kwargs): def run(command): p = Popen(command.split(), stdout=PIPE, stderr=PIPE) - p.wait() - return (p.returncode, [line.strip() for line in p.stdout.readlines()], - [line.strip() for line in p.stderr.readlines()]) + (stdout, stderr) = p.communicate() + return (p.returncode, [line.strip() for line in stdout.splitlines()], + [line.strip() for line in stderr.splitlines()]) def _get_files(repo, **kwargs): @@ -66,11 +64,8 @@ def _get_files(repo, **kwargs): if file_ in seen or not os.path.exists(file_): continue seen.add(file_) - if not file_.endswith('.py'): - continue - if skip_file(file_): - continue - yield file_ + if file_.endswith('.py') and not skip_file(file_): + yield file_ def find_vcs(): From be9f2a5ffbe134c3dd88dbe582e6aa5e27d4f913 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:42:31 +0100 Subject: [PATCH 27/60] Remove unused read_stdin, now ported to pep8 --- flake8/main.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 79c20e2..a52b536 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -1,6 +1,6 @@ +# -*- coding: utf-8 -*- import os import sys -import select from flake8.engine import get_style_guide @@ -53,16 +53,6 @@ def check_code(code, ignore=(), complexity=-1, reporter=None): return flake8_style.input_file('-', lines=code.split('\n')) -def read_stdin(): - # wait for 1 second on the stdin fd - reads, __, __ = select.select([sys.stdin], [], [], 1.) - if reads == []: - print('input not specified') - raise SystemExit(1) - - return sys.stdin.read() - - try: from setuptools import Command except ImportError: From 1496579fb2fd6db4833e2fc2c9995717d57706bf Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:44:17 +0100 Subject: [PATCH 28/60] Take into account that setuptools is no longer optional --- flake8/main.py | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index a52b536..72878c7 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -2,6 +2,8 @@ import os import sys +import setuptools + from flake8.engine import get_style_guide if sys.platform.startswith('win'): @@ -53,32 +55,27 @@ def check_code(code, ignore=(), complexity=-1, reporter=None): return flake8_style.input_file('-', lines=code.split('\n')) -try: - from setuptools import Command -except ImportError: - Flake8Command = None -else: - class Flake8Command(Command): - description = "Run flake8 on modules registered in setuptools" - user_options = [] +class Flake8Command(setuptools.Command): + description = "Run flake8 on modules registered in setuptools" + user_options = [] - def initialize_options(self): - pass + def initialize_options(self): + pass - def finalize_options(self): - pass + def finalize_options(self): + pass - def distribution_files(self): - if self.distribution.packages: - for package in self.distribution.packages: - yield package.replace(".", os.path.sep) + def distribution_files(self): + if self.distribution.packages: + for package in self.distribution.packages: + yield package.replace(".", os.path.sep) - if self.distribution.py_modules: - for filename in self.distribution.py_modules: - yield "%s.py" % filename + if self.distribution.py_modules: + for filename in self.distribution.py_modules: + yield "%s.py" % filename - def run(self): - flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) - paths = self.distribution_files() - report = flake8_style.check_files(paths) - raise SystemExit(report.total_errors > 0) + def run(self): + flake8_style = get_style_guide(config_file=DEFAULT_CONFIG) + paths = self.distribution_files() + report = flake8_style.check_files(paths) + raise SystemExit(report.total_errors > 0) From fa092c0294bdd0a57f1031b479c32342272b8139 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 18:45:48 +0100 Subject: [PATCH 29/60] Remove the obsolete argument 'reporter' of check_code and check_file --- flake8/main.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/flake8/main.py b/flake8/main.py index 72878c7..cbcaeef 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -41,18 +41,16 @@ def main(): raise SystemExit(1) -def check_file(path, ignore=(), complexity=-1, reporter=None): +def check_file(path, ignore=(), complexity=-1): flake8_style = get_style_guide( - config_file=DEFAULT_CONFIG, - ignore=ignore, max_complexity=complexity, reporter=reporter) + config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) return flake8_style.input_file(path) -def check_code(code, ignore=(), complexity=-1, reporter=None): +def check_code(code, ignore=(), complexity=-1): flake8_style = get_style_guide( - config_file=DEFAULT_CONFIG, - ignore=ignore, max_complexity=complexity, reporter=reporter) - return flake8_style.input_file('-', lines=code.split('\n')) + config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) + return flake8_style.input_file('-', lines=code.splitlines()) class Flake8Command(setuptools.Command): From 7362d57c24a3e06bf89561ae4a2b54366d5b3a94 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 21:14:13 +0100 Subject: [PATCH 30/60] Restore the whole-file-skip feature --- flake8/engine.py | 22 +++++++++++++++++++++- flake8/hooks.py | 3 +-- flake8/util.py | 20 +------------------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/flake8/engine.py b/flake8/engine.py index 332c8c8..bcbad54 100644 --- a/flake8/engine.py +++ b/flake8/engine.py @@ -1,9 +1,13 @@ # -*- coding: utf-8 -*- +import re + import pep8 from flake8 import __version__ from flake8.util import OrderedSet +_flake8_noqa = re.compile(r'flake8[:=]\s*noqa', re.I).search + def _register_extensions(): """Register all the extensions.""" @@ -46,10 +50,26 @@ def get_parser(): return parser, options_hooks +class StyleGuide(pep8.StyleGuide): + # Backward compatibility pep8 <= 1.4.2 + checker_class = pep8.Checker + + def input_file(self, filename, lines=None, expected=None, line_offset=0): + """Run all checks on a Python source file.""" + if self.options.verbose: + print('checking %s' % filename) + fchecker = self.checker_class( + filename, lines=lines, options=self.options) + # Any "# flake8: noqa" line? + if any(_flake8_noqa(line) for line in fchecker.lines): + return 0 + return fchecker.check_all(expected=expected, line_offset=line_offset) + + def get_style_guide(**kwargs): """Parse the options and configure the checker.""" kwargs['parser'], options_hooks = get_parser() - styleguide = pep8.StyleGuide(**kwargs) + styleguide = StyleGuide(**kwargs) options = styleguide.options for options_hook in options_hooks: options_hook(options) diff --git a/flake8/hooks.py b/flake8/hooks.py index efbc063..37c1b60 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -10,7 +10,6 @@ except ImportError: # Python 2 from flake8.engine import get_parser, get_style_guide from flake8.main import DEFAULT_CONFIG -from flake8.util import skip_file def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): @@ -64,7 +63,7 @@ def _get_files(repo, **kwargs): if file_ in seen or not os.path.exists(file_): continue seen.add(file_) - if file_.endswith('.py') and not skip_file(file_): + if file_.endswith('.py'): yield file_ diff --git a/flake8/util.py b/flake8/util.py index 3354274..771cca0 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import with_statement -import os.path -import re -__all__ = ['ast', 'iter_child_nodes', 'OrderedSet', 'skip_file'] +__all__ = ['ast', 'iter_child_nodes', 'OrderedSet'] try: import ast @@ -40,19 +38,3 @@ class OrderedSet(list): def add(self, value): if value not in self: self.append(value) - -_NOQA = re.compile(r'flake8[:=]\s*noqa', re.I | re.M) - - -def skip_file(path, source=None): - """Returns True if this header is found in path - - # flake8: noqa - """ - if os.path.isfile(path): - with open(path) as f: - source = f.read() - elif not source: - return False - - return _NOQA.search(source) is not None From 20b49ee2578f338e441e9d9227cb740ff6d4b8db Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:30:24 +0100 Subject: [PATCH 31/60] setup.py: reorder entries --- setup.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index aab1ebf..4e162f9 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ setup( license="MIT", version=__version__, description="code checking using pep8 and pyflakes", + long_description=README, author="Tarek Ziade", author_email="tarek@ziade.org", maintainer="Ian Cordasco", @@ -22,15 +23,6 @@ setup( "pyflakes==0.6.1", "pep8==1.4.2", ], - long_description=README, - classifiers=[ - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Topic :: Software Development", - "Topic :: Utilities", - ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], 'console_scripts': ['flake8 = flake8.main:main'], @@ -39,6 +31,14 @@ setup( 'C90 = flake8.mccabe:McCabeChecker', ], }, + classifiers=[ + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Utilities", + ], tests_require=['nose'], test_suite='nose.collector', ) From 3e1452d10eb0c1f1c3c40bead1646085a1ad1588 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:32:04 +0100 Subject: [PATCH 32/60] setup.py: remove 'scripts' which is redundant with setuptools entry points; issue #70 --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 4e162f9..c7101a3 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,6 @@ from setuptools import setup from flake8 import __version__ -scripts = ["flake8/flake8"] README = open('README.rst').read() setup( @@ -17,7 +16,6 @@ setup( maintainer_email="graffatcolmingov@gmail.com", url="http://bitbucket.org/tarek/flake8", packages=["flake8", "flake8.tests"], - scripts=scripts, install_requires=[ "setuptools", "pyflakes==0.6.1", From bcbf1b8eae5ba8340401fe072c12fbc71609a970 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:47:54 +0100 Subject: [PATCH 33/60] setup.py: allow upgrade of dependencies --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c7101a3..f3131cc 100644 --- a/setup.py +++ b/setup.py @@ -18,8 +18,8 @@ setup( packages=["flake8", "flake8.tests"], install_requires=[ "setuptools", - "pyflakes==0.6.1", - "pep8==1.4.2", + "pyflakes >= 0.6.1", + "pep8 >= 1.4.2", ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], From b8aab10d5f7c6d07390f8e89e70e76135f40978d Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 34/60] setup.py: add classifiers for Python 2/3 compatibility --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index f3131cc..82877e1 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,8 @@ setup( "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", "Topic :: Software Development", "Topic :: Utilities", ], From 2e76b4191ab20e376c4fccf1bb6b02f5c19386e9 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 35/60] setup.py: new topic classifiers --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 82877e1..4e9cf4f 100644 --- a/setup.py +++ b/setup.py @@ -36,8 +36,8 @@ setup( "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", - "Topic :: Software Development", - "Topic :: Utilities", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Quality Assurance", ], tests_require=['nose'], test_suite='nose.collector', From 83571710cb23bc4b53e39bc8b02e5ee98587e9fe Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 36/60] setup.py: update description --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e9cf4f..d19e3a2 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="flake8", license="MIT", version=__version__, - description="code checking using pep8 and pyflakes", + description="the modular source code checker: pep8, pyflakes and co", long_description=README, author="Tarek Ziade", author_email="tarek@ziade.org", From 9a8d85d22f3a0eb49d75b259ca7fcf332fbef7ac Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 37/60] Move the changelog to CHANGES.rst --- CHANGES.rst | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.rst | 135 ---------------------------------------------------- setup.py | 13 ++++- 3 files changed, 143 insertions(+), 137 deletions(-) create mode 100644 CHANGES.rst diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..fcddf7b --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,132 @@ +CHANGES +======= + +2.0.0 - 2013-01-xx +------------------ + +- Fixes #13: pep8 and pyflakes are now external dependencies +- Split run.py into main.py and hooks.py for better logic +- Expose our parser for our users +- New feature: Install git and hg hooks automagically +- By relying on pyflakes (0.6.1), we also fixed #45 and #35 + +1.7.0 - 2012-12-21 +------------------ + +- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker + for python 3.3 +- Support stdin +- Incorporate @phd's builtins pull request +- Fix the git hook +- Update pep8.py to the latest version + +1.6.2 - 2012-11-25 +------------------ + +- fixed the NameError: global name 'message' is not defined (#46) + + +1.6.1 - 2012-11-24 +------------------ + +- fixed the mercurial hook, a change from a previous patch was not properly + applied +- fixed an assumption about warnings/error messages that caused an exception + to be thrown when McCabe is used + +1.6 - 2012-11-16 +---------------- + +- changed the signatures of the ``check_file`` function in flake8/run.py, + ``skip_warning`` in flake8/util.py and the ``check``, ``checkPath`` + functions in flake8/pyflakes.py. +- fix ``--exclude`` and ``--ignore`` command flags (#14, #19) +- fix the git hook that wasn't catching files not already added to the index + (#29) +- pre-emptively includes the addition to pep8 to ignore certain lines. Add ``# + nopep8`` to the end of a line to ignore it. (#37) +- ``check_file`` can now be used without any special prior setup (#21) +- unpacking exceptions will no longer cause an exception (#20) +- fixed crash on non-existant file (#38) + + + +1.5 - 2012-10-13 +---------------- + +- fixed the stdin +- make sure mccabe catches the syntax errors as warnings +- pep8 upgrade +- added max_line_length default value +- added Flake8Command and entry points is setuptools is around +- using the setuptools console wrapper when available + + +1.4 - 2012-07-12 +---------------- + +- git_hook: Only check staged changes for compliance +- use pep8 1.2 + + +1.3.1 - 2012-05-19 +------------------ + +- fixed support for Python 2.5 + + +1.3 - 2012-03-12 +---------------- + +- fixed false W402 warning on exception blocks. + + +1.2 - 2012-02-12 +---------------- + +- added a git hook +- now python 3 compatible +- mccabe and pyflakes have warning codes like pep8 now + + +1.1 - 2012-02-14 +---------------- + +- fixed the value returned by --version +- allow the flake8: header to be more generic +- fixed the "hg hook raises 'physical lines'" bug +- allow three argument form of raise +- now uses setuptools if available, for 'develop' command + +1.0 - 2011-11-29 +---------------- + +- Deactivates by default the complexity checker +- Introduces the complexity option in the HG hook and the command line. + + +0.9 - 2011-11-09 +---------------- + +- update pep8 version to 0.6.1 +- mccabe check: gracefully handle compile failure + +0.8 - 2011-02-27 +---------------- + +- fixed hg hook +- discard unexisting files on hook check + + +0.7 - 2010-02-18 +---------------- + +- Fix pep8 intialization when run through Hg +- Make pep8 short options work when run throug the command line +- skip duplicates when controlling files via Hg + + +0.6 - 2010-02-15 +---------------- + +- Fix the McCabe metric on some loops diff --git a/README.rst b/README.rst index 7b415d2..f6b5cc4 100644 --- a/README.rst +++ b/README.rst @@ -225,138 +225,3 @@ flakey: McCabe: - W901: '' is too complex ('') - -CHANGES -======= - -2.0.0 - 2013-01-xx ------------------- - -- Fixes #13: pep8 and pyflakes are now external dependencies -- Split run.py into main.py and hooks.py for better logic -- Expose our parser for our users -- New feature: Install git and hg hooks automagically -- By relying on pyflakes (0.6.1), we also fixed #45 and #35 - -1.7.0 - 2012-12-21 ------------------- - -- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker - for python 3.3 -- Support stdin -- Incorporate @phd's builtins pull request -- Fix the git hook -- Update pep8.py to the latest version - -1.6.2 - 2012-11-25 ------------------- - -- fixed the NameError: global name 'message' is not defined (#46) - - -1.6.1 - 2012-11-24 ------------------- - -- fixed the mercurial hook, a change from a previous patch was not properly - applied -- fixed an assumption about warnings/error messages that caused an exception - to be thrown when McCabe is used - -1.6 - 2012-11-16 ----------------- - -- changed the signatures of the ``check_file`` function in flake8/run.py, - ``skip_warning`` in flake8/util.py and the ``check``, ``checkPath`` - functions in flake8/pyflakes.py. -- fix ``--exclude`` and ``--ignore`` command flags (#14, #19) -- fix the git hook that wasn't catching files not already added to the index - (#29) -- pre-emptively includes the addition to pep8 to ignore certain lines. Add ``# - nopep8`` to the end of a line to ignore it. (#37) -- ``check_file`` can now be used without any special prior setup (#21) -- unpacking exceptions will no longer cause an exception (#20) -- fixed crash on non-existant file (#38) - - - -1.5 - 2012-10-13 ----------------- - -- fixed the stdin -- make sure mccabe catches the syntax errors as warnings -- pep8 upgrade -- added max_line_length default value -- added Flake8Command and entry points is setuptools is around -- using the setuptools console wrapper when available - - -1.4 - 2012-07-12 ----------------- - -- git_hook: Only check staged changes for compliance -- use pep8 1.2 - - -1.3.1 - 2012-05-19 ------------------- - -- fixed support for Python 2.5 - - -1.3 - 2012-03-12 ----------------- - -- fixed false W402 warning on exception blocks. - - -1.2 - 2012-02-12 ----------------- - -- added a git hook -- now python 3 compatible -- mccabe and pyflakes have warning codes like pep8 now - - -1.1 - 2012-02-14 ----------------- - -- fixed the value returned by --version -- allow the flake8: header to be more generic -- fixed the "hg hook raises 'physical lines'" bug -- allow three argument form of raise -- now uses setuptools if available, for 'develop' command - -1.0 - 2011-11-29 ----------------- - -- Deactivates by default the complexity checker -- Introduces the complexity option in the HG hook and the command line. - - -0.9 - 2011-11-09 ----------------- - -- update pep8 version to 0.6.1 -- mccabe check: gracefully handle compile failure - -0.8 - 2011-02-27 ----------------- - -- fixed hg hook -- discard unexisting files on hook check - - -0.7 - 2010-02-18 ----------------- - -- Fix pep8 intialization when run through Hg -- Make pep8 short options work when run throug the command line -- skip duplicates when controlling files via Hg - - -0.6 - 2010-02-15 ----------------- - -- Fix the McCabe metric on some loops - - diff --git a/setup.py b/setup.py index d19e3a2..054073b 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import with_statement from setuptools import setup from flake8 import __version__ -README = open('README.rst').read() + +def get_long_description(): + descr = [] + for fname in ('README.rst', 'CHANGES.rst'): + with open(fname) as f: + descr.append(f.read()) + return '\n\n'.join(descr) + setup( name="flake8", license="MIT", version=__version__, description="the modular source code checker: pep8, pyflakes and co", - long_description=README, + long_description=get_long_description(), author="Tarek Ziade", author_email="tarek@ziade.org", maintainer="Ian Cordasco", From 472b186eb3032137d117789a5f52ff1dba1cd04d Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 38/60] setup.py: do not import the module to read the __version__ --- setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 054073b..70693e8 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,12 @@ from __future__ import with_statement from setuptools import setup -from flake8 import __version__ + +def get_version(fname='flake8/__init__.py'): + with open(fname) as f: + for line in f: + if line.startswith('__version__'): + return eval(line.split('=')[-1]) def get_long_description(): @@ -16,7 +21,7 @@ def get_long_description(): setup( name="flake8", license="MIT", - version=__version__, + version=get_version(), description="the modular source code checker: pep8, pyflakes and co", long_description=get_long_description(), author="Tarek Ziade", From 8af0f1ed98f8ce8555538747d4fdb60c1d2e45c4 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Wed, 20 Feb 2013 22:50:12 +0100 Subject: [PATCH 39/60] Add information to the README.rst --- README.rst | 183 +++++++++++++++++++++++++++++------------------------ 1 file changed, 101 insertions(+), 82 deletions(-) diff --git a/README.rst b/README.rst index f6b5cc4..912ba17 100644 --- a/README.rst +++ b/README.rst @@ -8,22 +8,20 @@ Flake8 is a wrapper around these tools: - pep8 - Ned's McCabe script -Flake8 runs all tools by launching the single 'flake8' script, but ignores pep8 -and PyFlakes extended options and just uses defaults. It displays the warnings -in a per-file, merged output. +Flake8 runs all the tools by launching the single 'flake8' script. +It displays the warnings in a per-file, merged output. It also adds a few features: -- files that contains with this header are skipped:: +- files that contain this line are skipped:: # flake8: noqa -- lines that contain a "# NOQA" comment at the end will not issue pyflakes - warnings. -- lines that contain a "# NOPEP8" comment at the end will not issue pep8 - warnings. -- a Mercurial hook. +- lines that contain a "# noqa" comment at the end will not issue warnings. +- a Git and a Mercurial hook. - a McCabe complexity checker. +- extendable through ``flake8.extension`` entry points + QuickStart ========== @@ -37,13 +35,14 @@ To run flake8 just invoke it against any directory or Python module:: coolproject/mod.py:1028: local variable 'errors' is assigned to but never used coolproject/mod.py:625:17: E225 missing whitespace around operato -The output of PyFlakes *and* pep8 is merged and returned. +The output of PyFlakes *and* pep8 (and the optional plugins) are merged +and returned. -flake8 offers an extra option: --max-complexity, which will emit a warning if the -McCabe complexityu of a function is higher that the value. By default it's +flake8 offers an extra option: --max-complexity, which will emit a warning if +the McCabe complexity of a function is higher than the value. By default it's deactivated:: - $ bin/flake8 --max-complexity 12 flake8 + $ flake8 --max-complexity 12 flake8 coolproject/mod.py:97: 'shutil' imported but unused coolproject/mod.py:729: redefinition of function 'readlines' from line 723 coolproject/mod.py:1028: local variable 'errors' is assigned to but never used @@ -52,16 +51,37 @@ deactivated:: coolproject/mod.py:939:1: 'Checker.check_all' is too complex (12) coolproject/mod.py:1204:1: 'selftest' is too complex (14) -This feature is quite useful to detect over-complex code. According to McCabe, anything -that goes beyond 10 is too complex. +This feature is quite useful to detect over-complex code. According to McCabe, +anything that goes beyond 10 is too complex. See https://en.wikipedia.org/wiki/Cyclomatic_complexity. +Configuration +------------- + +The behaviour may be configured at two levels. + +The user settings are read from the ``~/.config/flake8`` file. +Example:: + + [flake8] + ignore = E226,E302,E41 + max-line-length = 160 + +At the project level, a ``tox.ini`` file or a ``setup.cfg`` file is read +if present. Only the first file is considered. If this file does not +have a ``[flake8]`` section, no project specific configuration is loaded. + +If the ``ignore`` option is not in the configuration and not in the arguments, +only the error codes ``E226`` and ``E241/E242`` are ignored +(see codes in the table below). + + Mercurial hook ============== -To use the Mercurial hook on any *commit* or *qrefresh*, change your .hg/rc file -like this:: +To use the Mercurial hook on any *commit* or *qrefresh*, change your .hg/hgrc +file like this:: [hooks] commit = python:flake8.run.hg_hook @@ -73,11 +93,12 @@ like this:: If *strict* option is set to **1**, any warning will block the commit. When -*strict* is set to **0**, warnings are just displayed in the standard output. +*strict* is set to **0**, warnings are just printed to the standard output. *complexity* defines the maximum McCabe complexity allowed before a warning -is emited. If you don't specify it it's just ignored. If specified, must -be a positive value. 12 is usually a good value. +is emitted. If you don't specify it, it's just ignored. If specified, it must +be a positive value. 12 is usually a good value. + Git hook ======== @@ -97,18 +118,18 @@ To use the Git hook on any *commit*, add a **pre-commit** file in the If *strict* option is set to **True**, any warning will block the commit. When -*strict* is set to **False** or omited, warnings are just displayed in the +*strict* is set to **False** or omitted, warnings are just printed to the standard output. *complexity* defines the maximum McCabe complexity allowed before a warning -is emited. If you don't specify it or set it to **-1**, it's just ignored. -If specified, it must be a positive value. 12 is usually a good value. +is emitted. If you don't specify it or set it to **-1**, it's just ignored. +If specified, it must be a positive value. 12 is usually a good value. -*lazy* when set to ``True`` will also take into account files not added to the +*lazy* when set to ``True`` will also take into account files not added to the index. Also, make sure the file is executable and adapt the shebang line so it -point to your python interpreter. +points to your Python interpreter. Buildout integration @@ -134,7 +155,7 @@ setuptools integration ====================== If setuptools is available, Flake8 provides a command that checks the -Python files declared by your project. To use it, add flake8 to your +Python files declared by your project. To use it, add flake8 to your setup_requires:: setup( @@ -147,8 +168,8 @@ setup_requires:: ) Running ``python setup.py flake8`` on the command line will check the -files listed in your ``py_modules`` and ``packages``. If any warnings -are found, the command will exit with an error code:: +files listed in your ``py_modules`` and ``packages``. If any warning +is found, the command will exit with an error code:: $ python setup.py flake8 @@ -160,68 +181,66 @@ Original projects Flake8 is just a glue project, all the merits go to the creators of the original projects: -- pep8: https://github.com/jcrocholl/pep8/ -- PyFlakes: http://divmod.org/trac/wiki/DivmodPyflakes -- flakey: https://bitbucket.org/icordasc/flakey +- pep8: https://github.com/jcrocholl/pep8 +- PyFlakes: https://launchpad.net/pyflakes - McCabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html + Warning / Error codes ===================== -Below are lists of all warning and error codes flake8 will generate, broken -out by component. +The convention of Flake8 is to assign a code to each error or warning, like +the ``pep8`` tool. These codes are used to configure the list of errors +which are selected or ignored. -pep8: +Each code consists of an upper case ASCII letter followed by three digits. +The recommendation is to use a different prefix for each plugin. -- E101: indentation contains mixed spaces and tabs -- E111: indentation is not a multiple of four -- E112: expected an indented block -- E113: unexpected indentation -- E201: whitespace after char -- E202: whitespace before char -- E203: whitespace before char -- E211: whitespace before text -- E223: tab / multiple spaces before operator -- E224: tab / multiple spaces after operator -- E225: missing whitespace around operator -- E225: missing whitespace around operator -- E231: missing whitespace after char -- E241: multiple spaces after separator -- E242: tab after separator -- E251: no spaces around keyword / parameter equals -- E262: inline comment should start with '# ' -- E301: expected 1 blank line, found 0 -- E302: expected 2 blank lines, found -- E303: too many blank lines () -- E304: blank lines found after function decorator -- E401: multiple imports on one line -- E501: line too long ( characters) -- E701: multiple statements on one line (colon) -- E702: multiple statements on one line (semicolon) -- W191: indentation contains tabs -- W291: trailing whitespace -- W292: no newline at end of file -- W293: blank line contains whitespace -- W391: blank line at end of file -- W601: .has_key() is deprecated, use 'in' -- W602: deprecated form of raising exception -- W603: '<>' is deprecated, use '!=' -- W604: backticks are deprecated, use 'repr()' +A list of the known prefixes is published below: -flakey: +- ``E***``/``W***``: `pep8 errors and warnings + `_ +- ``F***``: PyFlakes codes (see below) +- ``C9**``: McCabe complexity plugin `mccabe + `_ +- ``N8**``: Naming Conventions plugin `flint-naming + `_ -- W402: imported but unused -- W403: import from line shadowed by loop variable -- W404: 'from import ``*``' used; unable to detect undefined names -- W405: future import(s) after other statements -- W801: redefinition of unused from line -- W802: undefined name -- W803: undefined name in __all__ -- W804: local variable (defined in enclosing scope on line ) referenced before assignment -- W805: duplicate argument in function definition -- W806: redefinition of function from line -- W806: local variable is assigned to but never used -McCabe: +The original PyFlakes does not provide error codes. Flake8 patches the +PyFlakes messages to add the following codes: -- W901: '' is too complex ('') ++------+--------------------------------------------------------------------+ +| code | sample message | ++======+====================================================================+ +| F401 | ``module`` imported but unused | ++------+--------------------------------------------------------------------+ +| F402 | import ``module`` from line ``N`` shadowed by loop variable | ++------+--------------------------------------------------------------------+ +| F403 | 'from ``module`` import \*' used; unable to detect undefined names | ++------+--------------------------------------------------------------------+ +| F404 | future import(s) ``name`` after other statements | ++------+--------------------------------------------------------------------+ ++------+--------------------------------------------------------------------+ +| F811 | redefinition of unused ``name`` from line ``N`` | ++------+--------------------------------------------------------------------+ +| F812 | list comprehension redefines ``name`` from line ``N`` | ++------+--------------------------------------------------------------------+ +| F821 | undefined name ``name`` | ++------+--------------------------------------------------------------------+ +| F822 | undefined name ``name`` in __all__ | ++------+--------------------------------------------------------------------+ +| F823 | local variable ``name`` ... referenced before assignment | ++------+--------------------------------------------------------------------+ +| F831 | duplicate argument ``name`` in function definition | ++------+--------------------------------------------------------------------+ +| F841 | local variable ``name`` is assigned to but never used | ++------+--------------------------------------------------------------------+ + + +Links +----- + +* `pep8 documentation `_ + +* `flake8 documentation `_ From a593dd01a462eae39ec5786f13ff2c97622ac581 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 21 Feb 2013 00:00:54 +0100 Subject: [PATCH 40/60] Install mccabe as a dependency --- flake8/mccabe.py | 310 ------------------------------------ flake8/tests/test_mccabe.py | 42 ----- setup.py | 2 +- 3 files changed, 1 insertion(+), 353 deletions(-) delete mode 100644 flake8/mccabe.py delete mode 100644 flake8/tests/test_mccabe.py diff --git a/flake8/mccabe.py b/flake8/mccabe.py deleted file mode 100644 index 8b7ac4b..0000000 --- a/flake8/mccabe.py +++ /dev/null @@ -1,310 +0,0 @@ -""" Meager code path measurement tool. - Ned Batchelder - http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html - MIT License. -""" -from __future__ import with_statement - -import optparse -import sys -from collections import defaultdict - -from flake8.util import ast, iter_child_nodes - -version = 0.1 - - -class ASTVisitor(object): - """Performs a depth-first walk of the AST.""" - - def __init__(self): - self.node = None - self._cache = {} - - def default(self, node, *args): - for child in iter_child_nodes(node): - self.dispatch(child, *args) - - def dispatch(self, node, *args): - self.node = node - klass = node.__class__ - meth = self._cache.get(klass) - if meth is None: - className = klass.__name__ - meth = getattr(self.visitor, 'visit' + className, self.default) - self._cache[klass] = meth - return meth(node, *args) - - def preorder(self, tree, visitor, *args): - """Do preorder walk of tree using visitor""" - self.visitor = visitor - visitor.visit = self.dispatch - self.dispatch(tree, *args) # XXX *args make sense? - - -class PathNode(object): - def __init__(self, name, look="circle"): - self.name = name - self.look = look - - def to_dot(self): - print('node [shape=%s,label="%s"] %d;' % ( - self.look, self.name, self.dot_id())) - - def dot_id(self): - return id(self) - - -class PathGraph(object): - def __init__(self, name, entity, lineno): - self.name = name - self.entity = entity - self.lineno = lineno - self.nodes = defaultdict(list) - - def connect(self, n1, n2): - self.nodes[n1].append(n2) - - def to_dot(self): - print('subgraph {') - for node in self.nodes: - node.to_dot() - for node, nexts in self.nodes.items(): - for next in nexts: - print('%s -- %s;' % (node.dot_id(), next.dot_id())) - print('}') - - def complexity(self): - """ Return the McCabe complexity for the graph. - V-E+2 - """ - num_edges = sum([len(n) for n in self.nodes.values()]) - num_nodes = len(self.nodes) - return num_edges - num_nodes + 2 - - -class PathGraphingAstVisitor(ASTVisitor): - """ A visitor for a parsed Abstract Syntax Tree which finds executable - statements. - """ - - def __init__(self): - super(PathGraphingAstVisitor, self).__init__() - self.classname = "" - self.graphs = {} - self.reset() - - def reset(self): - self.graph = None - self.tail = None - - def dispatch_list(self, node_list): - for node in node_list: - self.dispatch(node) - - def visitFunctionDef(self, node): - - if self.classname: - entity = '%s%s' % (self.classname, node.name) - else: - entity = node.name - - name = '%d:1: %r' % (node.lineno, entity) - - if self.graph is not None: - # closure - pathnode = self.appendPathNode(name) - self.tail = pathnode - self.dispatch_list(node.body) - bottom = PathNode("", look='point') - self.graph.connect(self.tail, bottom) - self.graph.connect(pathnode, bottom) - self.tail = bottom - else: - self.graph = PathGraph(name, entity, node.lineno) - pathnode = PathNode(name) - self.tail = pathnode - self.dispatch_list(node.body) - self.graphs["%s%s" % (self.classname, node.name)] = self.graph - self.reset() - - def visitClassDef(self, node): - old_classname = self.classname - self.classname += node.name + "." - self.dispatch_list(node.body) - self.classname = old_classname - - def appendPathNode(self, name): - if not self.tail: - return - pathnode = PathNode(name) - self.graph.connect(self.tail, pathnode) - self.tail = pathnode - return pathnode - - def visitSimpleStatement(self, node): - if node.lineno is None: - lineno = 0 - else: - lineno = node.lineno - name = "Stmt %d" % lineno - self.appendPathNode(name) - - visitAssert = visitAssign = visitAugAssign = visitDelete = visitPrint = \ - visitRaise = visitYield = visitImport = visitCall = visitSubscript = \ - visitPass = visitContinue = visitBreak = visitGlobal = visitReturn = \ - visitSimpleStatement - - def visitLoop(self, node): - name = "Loop %d" % node.lineno - - if self.graph is None: - # global loop - self.graph = PathGraph(name, name, node.lineno) - pathnode = PathNode(name) - self.tail = pathnode - self.dispatch_list(node.body) - self.graphs["%s%s" % (self.classname, name)] = self.graph - self.reset() - else: - pathnode = self.appendPathNode(name) - self.tail = pathnode - self.dispatch_list(node.body) - bottom = PathNode("", look='point') - self.graph.connect(self.tail, bottom) - self.graph.connect(pathnode, bottom) - self.tail = bottom - - # TODO: else clause in node.orelse - - visitFor = visitWhile = visitLoop - - def visitIf(self, node): - name = "If %d" % node.lineno - pathnode = self.appendPathNode(name) - loose_ends = [] - self.dispatch_list(node.body) - loose_ends.append(self.tail) - if node.orelse: - self.tail = pathnode - self.dispatch_list(node.orelse) - loose_ends.append(self.tail) - else: - loose_ends.append(pathnode) - if pathnode: - bottom = PathNode("", look='point') - for le in loose_ends: - self.graph.connect(le, bottom) - self.tail = bottom - - def visitTryExcept(self, node): - name = "TryExcept %d" % node.lineno - pathnode = self.appendPathNode(name) - loose_ends = [] - self.dispatch_list(node.body) - loose_ends.append(self.tail) - for handler in node.handlers: - self.tail = pathnode - self.dispatch_list(handler.body) - loose_ends.append(self.tail) - if pathnode: - bottom = PathNode("", look='point') - for le in loose_ends: - self.graph.connect(le, bottom) - self.tail = bottom - - def visitWith(self, node): - name = "With %d" % node.lineno - self.appendPathNode(name) - self.dispatch_list(node.body) - - -class McCabeChecker(object): - """McCabe cyclomatic complexity checker.""" - name = 'mccabe' - version = version - _code = 'C901' - _error_tmpl = "C901 %r is too complex (%d)" - max_complexity = 0 - - def __init__(self, tree, filename): - self.tree = tree - - @classmethod - def add_options(cls, parser): - parser.add_option('--max-complexity', default=-1, action='store', - type='int', help="McCabe complexity threshold") - parser.config_options.append('max-complexity') - - @classmethod - def parse_options(cls, options): - cls.max_complexity = options.max_complexity - - def run(self): - if self.max_complexity < 0: - return - visitor = PathGraphingAstVisitor() - visitor.preorder(self.tree, visitor) - for graph in visitor.graphs.values(): - graph_complexity = graph.complexity() - if graph_complexity >= self.max_complexity: - text = self._error_tmpl % (graph.entity, graph_complexity) - yield graph.lineno, 0, text, type(self) - - -def get_code_complexity(code, min_complexity=7, filename='stdin'): - try: - tree = compile(code, filename, "exec", ast.PyCF_ONLY_AST) - except SyntaxError: - e = sys.exc_info()[1] - sys.stderr.write("Unable to parse %s: %s\n" % (filename, e)) - return 0 - - complx = [] - McCabeChecker.max_complexity = min_complexity - for lineno, offset, text, check in McCabeChecker(tree, filename).run(): - complx.append('%s:%d:1: %s' % (filename, lineno, text)) - - if len(complx) == 0: - return 0 - print('\n'.join(complx)) - return len(complx) - - -def get_module_complexity(module_path, min_complexity=7): - """Returns the complexity of a module""" - with open(module_path, "rU") as mod: - code = mod.read() - return get_code_complexity(code, min_complexity, filename=module_path) - - -def main(argv): - opar = optparse.OptionParser() - opar.add_option("-d", "--dot", dest="dot", - help="output a graphviz dot file", action="store_true") - opar.add_option("-m", "--min", dest="min", - help="minimum complexity for output", type="int", - default=2) - - options, args = opar.parse_args(argv) - - with open(args[0], "rU") as mod: - code = mod.read() - tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST) - visitor = PathGraphingAstVisitor() - visitor.preorder(tree, visitor) - - if options.dot: - print('graph {') - for graph in visitor.graphs.values(): - if graph.complexity() >= options.min: - graph.to_dot() - print('}') - else: - for graph in visitor.graphs.values(): - if graph.complexity() >= options.min: - print(graph.name, graph.complexity()) - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/flake8/tests/test_mccabe.py b/flake8/tests/test_mccabe.py deleted file mode 100644 index a220c83..0000000 --- a/flake8/tests/test_mccabe.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -import unittest -import sys -try: - from StringIO import StringIO -except ImportError: - from io import StringIO # NOQA - -from flake8.mccabe import get_code_complexity - - -_GLOBAL = """\ - -for i in range(10): - pass - -def a(): - def b(): - def c(): - pass - c() - b() - -""" - - -class McCabeTest(unittest.TestCase): - - def setUp(self): - self.old = sys.stdout - self.out = sys.stdout = StringIO() - - def tearDown(self): - sys.sdtout = self.old - - def test_sample(self): - self.assertEqual(get_code_complexity(_GLOBAL, 1), 2) - self.out.seek(0) - res = self.out.read().strip().split('\n') - wanted = ["stdin:5:1: C901 'a' is too complex (4)", - "stdin:2:1: C901 'Loop 2' is too complex (2)"] - self.assertEqual(res, wanted) diff --git a/setup.py b/setup.py index 70693e8..803c132 100644 --- a/setup.py +++ b/setup.py @@ -34,13 +34,13 @@ setup( "setuptools", "pyflakes >= 0.6.1", "pep8 >= 1.4.2", + "mccabe >= 0.2a0", ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], 'console_scripts': ['flake8 = flake8.main:main'], 'flake8.extension': [ 'F = flake8._pyflakes:FlakesChecker', - 'C90 = flake8.mccabe:McCabeChecker', ], }, classifiers=[ From 6bf49f848f351b0e18fae312551e282de24325b6 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 21 Feb 2013 23:56:35 +0100 Subject: [PATCH 41/60] Do not copy the licenses for the dependencies --- LICENSE | 72 --------------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/LICENSE b/LICENSE index 7d4564e..c8c1f45 100644 --- a/LICENSE +++ b/LICENSE @@ -19,75 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - - -== McCabe License (MIT) == - -Copyright (C) Ned Batchelder - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -== PyFlakes license (MIT) == - -Copyright (c) 2005 Divmod, Inc., http://www.divmod.com/ - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -== pep8 license (expat) == - -Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - From 11b52513630aea52e8229ea95e6271acd9ffd1eb Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 21 Feb 2013 23:59:41 +0100 Subject: [PATCH 42/60] Include the *.rst files in MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 68c63de..9b622ad 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ +include *.rst include CONTRIBUTORS.txt -include README include LICENSE recursive-include scripts flake8.* From 467b9f5c2923eeaae32358d423983bdf21d586b2 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 08:30:37 +0100 Subject: [PATCH 43/60] Upgrade requirements to pep8 1.4.3 and mccabe 0.2 --- CHANGES.rst | 8 ++++++-- setup.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fcddf7b..473c9a7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ CHANGES ======= -2.0.0 - 2013-01-xx +2.0.0 - 2013-02-xx ------------------ - Fixes #13: pep8 and pyflakes are now external dependencies @@ -10,6 +10,7 @@ CHANGES - New feature: Install git and hg hooks automagically - By relying on pyflakes (0.6.1), we also fixed #45 and #35 + 1.7.0 - 2012-12-21 ------------------ @@ -20,6 +21,7 @@ CHANGES - Fix the git hook - Update pep8.py to the latest version + 1.6.2 - 2012-11-25 ------------------ @@ -34,6 +36,7 @@ CHANGES - fixed an assumption about warnings/error messages that caused an exception to be thrown when McCabe is used + 1.6 - 2012-11-16 ---------------- @@ -50,7 +53,6 @@ CHANGES - fixed crash on non-existant file (#38) - 1.5 - 2012-10-13 ---------------- @@ -98,6 +100,7 @@ CHANGES - allow three argument form of raise - now uses setuptools if available, for 'develop' command + 1.0 - 2011-11-29 ---------------- @@ -111,6 +114,7 @@ CHANGES - update pep8 version to 0.6.1 - mccabe check: gracefully handle compile failure + 0.8 - 2011-02-27 ---------------- diff --git a/setup.py b/setup.py index 803c132..3c774da 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,8 @@ setup( install_requires=[ "setuptools", "pyflakes >= 0.6.1", - "pep8 >= 1.4.2", - "mccabe >= 0.2a0", + "pep8 >= 1.4.3", + "mccabe >= 0.2", ], entry_points={ 'distutils.commands': ['flake8 = flake8.main:Flake8Command'], From 22040891211d55f2a315dc0886ddf73962b2a556 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 10:22:10 +0100 Subject: [PATCH 44/60] Obsolete __future__ import --- flake8/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flake8/util.py b/flake8/util.py index 771cca0..7e6b37b 100644 --- a/flake8/util.py +++ b/flake8/util.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import with_statement __all__ = ['ast', 'iter_child_nodes', 'OrderedSet'] From 47bf8887816bbef6ac04ff4b95829b8c0bfaac64 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 10:35:11 +0100 Subject: [PATCH 45/60] Review the content of the README --- README.rst | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 912ba17..21267f4 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ It also adds a few features: - lines that contain a "# noqa" comment at the end will not issue warnings. - a Git and a Mercurial hook. - a McCabe complexity checker. -- extendable through ``flake8.extension`` entry points +- extendable through ``flake8.extension`` entry points. QuickStart @@ -29,27 +29,25 @@ QuickStart To run flake8 just invoke it against any directory or Python module:: $ flake8 coolproject - coolproject/mod.py:1027: local variable 'errors' is assigned to but never used - coolproject/mod.py:97: 'shutil' imported but unused - coolproject/mod.py:729: redefinition of function 'readlines' from line 723 - coolproject/mod.py:1028: local variable 'errors' is assigned to but never used + coolproject/mod.py:97:1: F401 'shutil' imported but unused coolproject/mod.py:625:17: E225 missing whitespace around operato + coolproject/mod.py:729: F811 redefinition of function 'readlines' from line 723 + coolproject/mod.py:1028: F841 local variable 'errors' is assigned to but never used -The output of PyFlakes *and* pep8 (and the optional plugins) are merged +The outputs of PyFlakes *and* pep8 (and the optional plugins) are merged and returned. flake8 offers an extra option: --max-complexity, which will emit a warning if the McCabe complexity of a function is higher than the value. By default it's deactivated:: - $ flake8 --max-complexity 12 flake8 - coolproject/mod.py:97: 'shutil' imported but unused - coolproject/mod.py:729: redefinition of function 'readlines' from line 723 - coolproject/mod.py:1028: local variable 'errors' is assigned to but never used + $ flake8 --max-complexity 12 coolproject + coolproject/mod.py:97:1: F401 'shutil' imported but unused coolproject/mod.py:625:17: E225 missing whitespace around operator - coolproject/mod.py:452:1: 'missing_whitespace_around_operator' is too complex (18) - coolproject/mod.py:939:1: 'Checker.check_all' is too complex (12) - coolproject/mod.py:1204:1: 'selftest' is too complex (14) + coolproject/mod.py:729:1: F811 redefinition of unused 'readlines' from line 723 + coolproject/mod.py:939:1: C901 'Checker.check_all' is too complex (12) + coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used + coolproject/mod.py:1204:1: C901 'selftest' is too complex (14) This feature is quite useful to detect over-complex code. According to McCabe, anything that goes beyond 10 is too complex. @@ -202,9 +200,9 @@ A list of the known prefixes is published below: `_ - ``F***``: PyFlakes codes (see below) - ``C9**``: McCabe complexity plugin `mccabe - `_ -- ``N8**``: Naming Conventions plugin `flint-naming - `_ + `_ +- ``N8**``: Naming Conventions plugin `pep8-naming + `_ (planned) The original PyFlakes does not provide error codes. Flake8 patches the From b13ec0acef4f3eb905ad1901469710cbfd87b6ab Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 10:36:51 +0100 Subject: [PATCH 46/60] Typo in the sample output --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 21267f4..206bc50 100644 --- a/README.rst +++ b/README.rst @@ -31,8 +31,8 @@ To run flake8 just invoke it against any directory or Python module:: $ flake8 coolproject coolproject/mod.py:97:1: F401 'shutil' imported but unused coolproject/mod.py:625:17: E225 missing whitespace around operato - coolproject/mod.py:729: F811 redefinition of function 'readlines' from line 723 - coolproject/mod.py:1028: F841 local variable 'errors' is assigned to but never used + coolproject/mod.py:729:1: F811 redefinition of function 'readlines' from line 723 + coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used The outputs of PyFlakes *and* pep8 (and the optional plugins) are merged and returned. From 6a984fdfdd31410fb3b9818a752a9b10f4c35818 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 12:57:05 +0100 Subject: [PATCH 47/60] Plugin pep8-naming is released --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 206bc50..cd5387f 100644 --- a/README.rst +++ b/README.rst @@ -202,7 +202,7 @@ A list of the known prefixes is published below: - ``C9**``: McCabe complexity plugin `mccabe `_ - ``N8**``: Naming Conventions plugin `pep8-naming - `_ (planned) + `_ The original PyFlakes does not provide error codes. Flake8 patches the From 2db51c22a7095c3046dd2ba5c76911d5efc29e2f Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Fri, 22 Feb 2013 14:41:32 +0100 Subject: [PATCH 48/60] Typos in the changelog --- CHANGES.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 473c9a7..c144f88 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,8 +14,8 @@ CHANGES 1.7.0 - 2012-12-21 ------------------ -- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker - for python 3.3 +- Fixes part of #35: Exception for no WITHITEM being an attribute of Checker + for Python 3.3 - Support stdin - Incorporate @phd's builtins pull request - Fix the git hook @@ -31,26 +31,26 @@ CHANGES 1.6.1 - 2012-11-24 ------------------ -- fixed the mercurial hook, a change from a previous patch was not properly +- fixed the mercurial hook, a change from a previous patch was not properly applied -- fixed an assumption about warnings/error messages that caused an exception +- fixed an assumption about warnings/error messages that caused an exception to be thrown when McCabe is used 1.6 - 2012-11-16 ---------------- -- changed the signatures of the ``check_file`` function in flake8/run.py, +- changed the signatures of the ``check_file`` function in flake8/run.py, ``skip_warning`` in flake8/util.py and the ``check``, ``checkPath`` functions in flake8/pyflakes.py. - fix ``--exclude`` and ``--ignore`` command flags (#14, #19) -- fix the git hook that wasn't catching files not already added to the index +- fix the git hook that wasn't catching files not already added to the index (#29) -- pre-emptively includes the addition to pep8 to ignore certain lines. Add ``# - nopep8`` to the end of a line to ignore it. (#37) +- pre-emptively includes the addition to pep8 to ignore certain lines. + Add ``# nopep8`` to the end of a line to ignore it. (#37) - ``check_file`` can now be used without any special prior setup (#21) - unpacking exceptions will no longer cause an exception (#20) -- fixed crash on non-existant file (#38) +- fixed crash on non-existent file (#38) 1.5 - 2012-10-13 @@ -87,7 +87,7 @@ CHANGES ---------------- - added a git hook -- now python 3 compatible +- now Python 3 compatible - mccabe and pyflakes have warning codes like pep8 now @@ -125,9 +125,9 @@ CHANGES 0.7 - 2010-02-18 ---------------- -- Fix pep8 intialization when run through Hg -- Make pep8 short options work when run throug the command line -- skip duplicates when controlling files via Hg +- Fix pep8 initialization when run through Hg +- Make pep8 short options work when run through the command line +- Skip duplicates when controlling files via Hg 0.6 - 2010-02-15 From 257eae684eff8933f6d8fea15fe270ee5f34111d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 22 Feb 2013 23:18:15 -0500 Subject: [PATCH 49/60] Add docs --- docs/Makefile | 130 ++++++++++++++++++++++++ docs/api.rst | 34 +++++++ docs/buildout.rst | 17 ++++ docs/conf.py | 238 ++++++++++++++++++++++++++++++++++++++++++++ docs/config.rst | 28 ++++++ docs/index.rst | 79 +++++++++++++++ docs/setuptools.rst | 21 ++++ docs/vcs.rst | 56 +++++++++++ docs/warnings.rst | 49 +++++++++ flake8/engine.py | 6 +- flake8/hooks.py | 17 ++++ flake8/main.py | 17 ++++ 12 files changed, 691 insertions(+), 1 deletion(-) create mode 100644 docs/Makefile create mode 100644 docs/api.rst create mode 100644 docs/buildout.rst create mode 100644 docs/conf.py create mode 100644 docs/config.rst create mode 100644 docs/index.rst create mode 100644 docs/setuptools.rst create mode 100644 docs/vcs.rst create mode 100644 docs/warnings.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..bf49b54 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Raclette.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Raclette.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Raclette" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Raclette" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..e1b9cb3 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,34 @@ +.. module:: flake8 + +flake8.engine +============= + +.. autofunction:: flake8.engine.get_parser + +.. autofunction:: flake8.engine.get_style_guide + +flake8.hooks +============ + +.. autofunction:: flake8.hooks.git_hook + +.. autofunction:: flake8.hooks.hg_hook + +flake8.main +=========== + +.. autofunction:: flake8.main.main + +.. autofunction:: flake8.main.check_file + +.. autofunction:: flake8.main.check_code + +.. autoclass:: flake8.main.Flake8Command + +flake8.util +=========== + +For AST checkers, this module has the ``iter_child_nodes`` function and +handles compatibility for all versions of Python between 2.5 and 3.3. The +function was added to the ``ast`` module in Python 2.6 but is redefined in the +case where the user is running Python 2.5 diff --git a/docs/buildout.rst b/docs/buildout.rst new file mode 100644 index 0000000..da9c58a --- /dev/null +++ b/docs/buildout.rst @@ -0,0 +1,17 @@ +Buildout integration +===================== + +In order to use Flake8 inside a buildout, edit your buildout.cfg and add this:: + + [buildout] + + parts += + ... + flake8 + + [flake8] + recipe = zc.recipe.egg + eggs = flake8 + ${buildout:eggs} + entry-points = + flake8=flake8.main:main diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..6c13f4b --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- +# +# This file is execfile()d with the current directory set to its containing +# dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# This environment variable makes decorators not decorate functions, so their +# signatures in the generated documentation are still correct +os.environ['GENERATING_DOCUMENTATION'] = "flake8" + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('..')) +import flake8 + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'flake8' +copyright = u'2012-2013 - Tarek Ziade, Ian Cordasco, Florent Xicluna' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = flake8.__version__ +# The full version, including alpha/beta/rc tags. +release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +# pygments_style = 'flask_theme_support.FlaskyStyle' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'nature' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'flake8_doc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'flake8.tex', u'flake8 Documentation', + u'Tarek Ziade', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'flake8', u'flake8 Documentation', + [u'Tarek Ziade', u'Ian Cordasco', u'Florent Xicluna'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'flake8', u'flake8 Documentation', u'Tarek Ziade', + 'flake8', 'Code checking using pep8, pyflakes and mccabe', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +texinfo_appendices = [] diff --git a/docs/config.rst b/docs/config.rst new file mode 100644 index 0000000..4fdb7fa --- /dev/null +++ b/docs/config.rst @@ -0,0 +1,28 @@ +Configuration +============= + +The behaviour may be configured at two levels. + +Global +------ + +The user settings are read from the ``~/.config/flake8`` file. +Example:: + + [flake8] + ignore = E226,E302,E41 + max-line-length = 160 + +Per-Project +----------- + +At the project level, a ``tox.ini`` file or a ``setup.cfg`` file is read +if present. Only the first file is considered. If this file does not +have a ``[flake8]`` section, no project specific configuration is loaded. + +Default +------- + +If the ``ignore`` option is not in the configuration and not in the arguments, +only the error codes ``E226`` and ``E241/E242`` are ignored +(see codes in the table below). diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..4244958 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,79 @@ +====== +Flake8 +====== + +Flake8 is a wrapper around these tools: + +- PyFlakes +- pep8 +- Ned Batchelder's McCabe script + +Flake8 runs all the tools by launching the single ``flake8`` script. +It displays the warnings in a per-file, merged output. + +It also adds a few features: + +- files that contain this line are skipped:: + + # flake8: noqa + +- lines that contain a ``# noqa`` comment at the end will not issue warnings. +- a Git and a Mercurial hook. +- a McCabe complexity checker. +- extendable through ``flake8.extension`` entry points. + +QuickStart +========== + +:: + + pip install flake8 + +To run flake8 just invoke it against any directory or Python module:: + + $ flake8 coolproject + coolproject/mod.py:97:1: F401 'shutil' imported but unused + coolproject/mod.py:625:17: E225 missing whitespace around operato + coolproject/mod.py:729:1: F811 redefinition of function 'readlines' from line 723 + coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used + +The outputs of PyFlakes *and* pep8 (and the optional plugins) are merged +and returned. + +flake8 offers an extra option: --max-complexity, which will emit a warning if +the McCabe complexity of a function is higher than the value. By default it's +deactivated:: + + $ flake8 --max-complexity 12 coolproject + coolproject/mod.py:97:1: F401 'shutil' imported but unused + coolproject/mod.py:625:17: E225 missing whitespace around operator + coolproject/mod.py:729:1: F811 redefinition of unused 'readlines' from line 723 + coolproject/mod.py:939:1: C901 'Checker.check_all' is too complex (12) + coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used + coolproject/mod.py:1204:1: C901 'selftest' is too complex (14) + +This feature is quite useful to detect over-complex code. According to McCabe, +anything that goes beyond 10 is too complex. +See https://en.wikipedia.org/wiki/Cyclomatic_complexity. + +Documentation +============= + +.. toctree:: + + api + config + vcs + buildout + setuptools + warnings + +Original Projects +================= + +Flake8 is just a glue project, all the merits go to the creators of the original +projects: + +- pep8: https://github.com/jcrocholl/pep8 +- PyFlakes: https://launchpad.net/pyflakes +- McCabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html diff --git a/docs/setuptools.rst b/docs/setuptools.rst new file mode 100644 index 0000000..a91accf --- /dev/null +++ b/docs/setuptools.rst @@ -0,0 +1,21 @@ +setuptools integration +====================== + +If setuptools is available, Flake8 provides a command that checks the +Python files declared by your project. To use it, add flake8 to your +setup_requires:: + + setup( + name="project", + packages=["project"], + + setup_requires=[ + "flake8" + ] + ) + +Running ``python setup.py flake8`` on the command line will check the +files listed in your ``py_modules`` and ``packages``. If any warning +is found, the command will exit with an error code:: + + $ python setup.py flake8 diff --git a/docs/vcs.rst b/docs/vcs.rst new file mode 100644 index 0000000..5b938f6 --- /dev/null +++ b/docs/vcs.rst @@ -0,0 +1,56 @@ +VCS Hooks +========= + +Mercurial hook +-------------- + +To use the Mercurial hook on any *commit* or *qrefresh*, change your .hg/hgrc +file like this:: + + [hooks] + commit = python:flake8.run.hg_hook + qrefresh = python:flake8.run.hg_hook + + [flake8] + strict = 0 + complexity = 12 + + +If *strict* option is set to **1**, any warning will block the commit. When +*strict* is set to **0**, warnings are just printed to the standard output. + +*complexity* defines the maximum McCabe complexity allowed before a warning +is emitted. If you don't specify it, it's just ignored. If specified, it must +be a positive value. 12 is usually a good value. + + +Git hook +-------- + +To use the Git hook on any *commit*, add a **pre-commit** file in the +*.git/hooks* directory containing:: + + #!/usr/bin/python + import sys + from flake8.run import git_hook + + COMPLEXITY = 10 + STRICT = False + + if __name__ == '__main__': + sys.exit(git_hook(complexity=COMPLEXITY, strict=STRICT, ignore='E501')) + + +If *strict* option is set to **True**, any warning will block the commit. When +*strict* is set to **False** or omitted, warnings are just printed to the +standard output. + +*complexity* defines the maximum McCabe complexity allowed before a warning +is emitted. If you don't specify it or set it to **-1**, it's just ignored. +If specified, it must be a positive value. 12 is usually a good value. + +*lazy* when set to ``True`` will also take into account files not added to the +index. + +Also, make sure the file is executable and adapt the shebang line so it +points to your Python interpreter. diff --git a/docs/warnings.rst b/docs/warnings.rst new file mode 100644 index 0000000..eaf8606 --- /dev/null +++ b/docs/warnings.rst @@ -0,0 +1,49 @@ +Warning / Error codes +===================== + +The convention of Flake8 is to assign a code to each error or warning, like +the ``pep8`` tool. These codes are used to configure the list of errors +which are selected or ignored. + +Each code consists of an upper case ASCII letter followed by three digits. +The recommendation is to use a different prefix for each plugin. A list of the +known prefixes is published below: + +- ``E***``/``W***``: `pep8 errors and warnings + `_ +- ``F***``: PyFlakes codes (see below) +- ``C9**``: McCabe complexity plugin `mccabe + `_ +- ``N8**``: Naming Conventions plugin `pep8-naming + `_ + + +The original PyFlakes does not provide error codes. Flake8 patches the +PyFlakes messages to add the following codes: + ++------+--------------------------------------------------------------------+ +| code | sample message | ++======+====================================================================+ +| F401 | ``module`` imported but unused | ++------+--------------------------------------------------------------------+ +| F402 | import ``module`` from line ``N`` shadowed by loop variable | ++------+--------------------------------------------------------------------+ +| F403 | 'from ``module`` import \*' used; unable to detect undefined names | ++------+--------------------------------------------------------------------+ +| F404 | future import(s) ``name`` after other statements | ++------+--------------------------------------------------------------------+ ++------+--------------------------------------------------------------------+ +| F811 | redefinition of unused ``name`` from line ``N`` | ++------+--------------------------------------------------------------------+ +| F812 | list comprehension redefines ``name`` from line ``N`` | ++------+--------------------------------------------------------------------+ +| F821 | undefined name ``name`` | ++------+--------------------------------------------------------------------+ +| F822 | undefined name ``name`` in __all__ | ++------+--------------------------------------------------------------------+ +| F823 | local variable ``name`` ... referenced before assignment | ++------+--------------------------------------------------------------------+ +| F831 | duplicate argument ``name`` in function definition | ++------+--------------------------------------------------------------------+ +| F841 | local variable ``name`` is assigned to but never used | ++------+--------------------------------------------------------------------+ diff --git a/flake8/engine.py b/flake8/engine.py index bcbad54..ca20f88 100644 --- a/flake8/engine.py +++ b/flake8/engine.py @@ -32,6 +32,9 @@ def _register_extensions(): def get_parser(): + """This returns an instance of optparse.OptionParser with all the + extensions registered and options set. This wraps ``pep8.get_parser``. + """ (extensions, parser_hooks, options_hooks) = _register_extensions() details = ', '.join(['%s: %s' % ext for ext in extensions]) parser = pep8.get_parser('flake8', '%s (%s)' % (__version__, details)) @@ -67,7 +70,8 @@ class StyleGuide(pep8.StyleGuide): def get_style_guide(**kwargs): - """Parse the options and configure the checker.""" + """Parse the options and configure the checker. This returns a sub-class + of ``pep8.StyleGuide``.""" kwargs['parser'], options_hooks = get_parser() styleguide = StyleGuide(**kwargs) options = styleguide.options diff --git a/flake8/hooks.py b/flake8/hooks.py index 37c1b60..bd6ba33 100644 --- a/flake8/hooks.py +++ b/flake8/hooks.py @@ -13,6 +13,18 @@ from flake8.main import DEFAULT_CONFIG def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): + """This is the function used by the git hook. + + :param int complexity: (optional), any value > 0 enables complexity + checking with mccabe + :param bool strict: (optional), if True, this returns the total number of + errors which will cause the hook to fail + :param str ignore: (optional), a comma-separated list of errors and + warnings to ignore + :param bool lazy: (optional), allows for the instances where you don't add + the files to the index before running a commit, e.g., git commit -a + :returns: total number of errors if strict is True, otherwise 0 + """ gitcmd = "git diff-index --cached --name-only HEAD" if lazy: gitcmd = gitcmd.replace('--cached ', '') @@ -30,6 +42,11 @@ def git_hook(complexity=-1, strict=False, ignore=None, lazy=False): def hg_hook(ui, repo, **kwargs): + """This is the function executed directly by Mercurial as part of the + hook. This is never called directly by the user, so the parameters are + undocumented. If you would like to learn more about them, please feel free + to read the official Mercurial documentation. + """ complexity = ui.config('flake8', 'complexity', default=-1) strict = ui.configbool('flake8', 'strict', default=True) config = ui.config('flake8', 'config', default=True) diff --git a/flake8/main.py b/flake8/main.py index cbcaeef..692e9f4 100644 --- a/flake8/main.py +++ b/flake8/main.py @@ -42,18 +42,35 @@ def main(): def check_file(path, ignore=(), complexity=-1): + """Checks a file using pep8 and pyflakes by default and mccabe + optionally. + + :param str path: path to the file to be checked + :param tuple ignore: (optional), error and warning codes to be ignored + :param int complexity: (optional), enables the mccabe check for values > 0 + """ flake8_style = get_style_guide( config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) return flake8_style.input_file(path) def check_code(code, ignore=(), complexity=-1): + """Checks code using pep8 and pyflakes by default and mccabe optionally. + + :param str code: code to be checked + :param tuple ignore: (optional), error and warning codes to be ignored + :param int complexity: (optional), enables the mccabe check for values > 0 + """ flake8_style = get_style_guide( config_file=DEFAULT_CONFIG, ignore=ignore, max_complexity=complexity) return flake8_style.input_file('-', lines=code.splitlines()) class Flake8Command(setuptools.Command): + """The :class:`Flake8Command` class is used by setuptools to perform + checks on registered modules. + """ + description = "Run flake8 on modules registered in setuptools" user_options = [] From e3480904316f7ebfe47841dd44a030490e3bf641 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 22 Feb 2013 23:29:08 -0500 Subject: [PATCH 50/60] Fix the heading for one part of the docs --- docs/api.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index e1b9cb3..c823d52 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,3 +1,7 @@ +========== +Flake8 API +========== + .. module:: flake8 flake8.engine From 2298968eb683817992687cf78e3a74baaf5b0fc5 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 22 Feb 2013 23:33:36 -0500 Subject: [PATCH 51/60] Get ready for 2.0 --- flake8/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake8/__init__.py b/flake8/__init__.py index 1db7ef8..a3332a5 100644 --- a/flake8/__init__.py +++ b/flake8/__init__.py @@ -1,2 +1,2 @@ -__version__ = '2.0a1' +__version__ = '2.0' From 855d7f5f5fcf11c709552052aacc9ddfb78981cb Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Sat, 23 Feb 2013 13:14:17 +0100 Subject: [PATCH 52/60] docs/conf.py cleanup --- docs/conf.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6c13f4b..d8fd835 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# This file is execfile()d with the current directory set to its containing -# dir. +# This file is execfile()d with the current directory set to its +# containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -9,7 +9,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys +import os # This environment variable makes decorators not decorate functions, so their # signatures in the generated documentation are still correct @@ -21,13 +22,13 @@ os.environ['GENERATING_DOCUMENTATION'] = "flake8" sys.path.insert(0, os.path.abspath('..')) import flake8 -# -- General configuration ----------------------------------------------------- +# -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. @@ -69,7 +70,8 @@ release = version # directories to ignore when looking for source files. exclude_patterns = ['_build'] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) to use for +# all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. @@ -90,7 +92,7 @@ exclude_patterns = ['_build'] #modindex_common_prefix = [] -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -115,7 +117,6 @@ html_theme = 'nature' # of the sidebar. #html_logo = None - # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. @@ -134,7 +135,6 @@ html_theme = 'nature' # typographically correct entities. #html_use_smartypants = True -# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names. #html_sidebars = {} @@ -172,7 +172,7 @@ html_theme = 'nature' htmlhelp_basename = 'flake8_doc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' @@ -181,10 +181,11 @@ htmlhelp_basename = 'flake8_doc' #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). +# (source start file, target name, title, +# author, documentclass [howto/manual]). latex_documents = [ - ('index', 'flake8.tex', u'flake8 Documentation', - u'Tarek Ziade', 'manual'), + ('index', 'flake8.tex', u'flake8 Documentation', + u'Tarek Ziade', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -211,7 +212,7 @@ latex_documents = [ #latex_domain_indices = True -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). @@ -223,15 +224,16 @@ man_pages = [ # If true, show URL addresses after external links. #man_show_urls = False -# -- Options for Texinfo output ------------------------------------------------ + +# -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'flake8', u'flake8 Documentation', u'Tarek Ziade', - 'flake8', 'Code checking using pep8, pyflakes and mccabe', - 'Miscellaneous'), + ('index', 'flake8', u'flake8 Documentation', u'Tarek Ziade', + 'flake8', 'Code checking using pep8, pyflakes and mccabe', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. From e9280978c4cf45c585dbdb375b605adc0a451d74 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Sat, 23 Feb 2013 13:17:00 +0100 Subject: [PATCH 53/60] Doc minor change: leading space and title --- docs/api.rst | 6 +++--- docs/setuptools.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index c823d52..fd746f7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -32,7 +32,7 @@ flake8.main flake8.util =========== -For AST checkers, this module has the ``iter_child_nodes`` function and -handles compatibility for all versions of Python between 2.5 and 3.3. The -function was added to the ``ast`` module in Python 2.6 but is redefined in the +For AST checkers, this module has the ``iter_child_nodes`` function and +handles compatibility for all versions of Python between 2.5 and 3.3. The +function was added to the ``ast`` module in Python 2.6 but is redefined in the case where the user is running Python 2.5 diff --git a/docs/setuptools.rst b/docs/setuptools.rst index a91accf..6890964 100644 --- a/docs/setuptools.rst +++ b/docs/setuptools.rst @@ -1,4 +1,4 @@ -setuptools integration +Setuptools integration ====================== If setuptools is available, Flake8 provides a command that checks the From 8976f69f21cfc27385888c0572558260d91ccc23 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Sat, 23 Feb 2013 13:17:32 +0100 Subject: [PATCH 54/60] Fix the link to the error codes --- docs/config.rst | 2 +- docs/warnings.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 4fdb7fa..042f715 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -25,4 +25,4 @@ Default If the ``ignore`` option is not in the configuration and not in the arguments, only the error codes ``E226`` and ``E241/E242`` are ignored -(see codes in the table below). +(see the :ref:`warning and error codes `). diff --git a/docs/warnings.rst b/docs/warnings.rst index eaf8606..b4b7fb1 100644 --- a/docs/warnings.rst +++ b/docs/warnings.rst @@ -1,3 +1,5 @@ +.. _error-codes: + Warning / Error codes ===================== From f5825f72908a72fec96133c067b0e034c1b41ac9 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Sat, 23 Feb 2013 13:19:12 +0100 Subject: [PATCH 55/60] Re-use the README to avoid duplication with the documentation --- README.rst | 197 +++---------------------------------------------- docs/index.rst | 59 +-------------- 2 files changed, 13 insertions(+), 243 deletions(-) diff --git a/README.rst b/README.rst index f2b8987..6456522 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,9 @@ Flake8 is a wrapper around these tools: - PyFlakes - pep8 -- Ned's McCabe script +- Ned Batchelder's McCabe script -Flake8 runs all the tools by launching the single 'flake8' script. +Flake8 runs all the tools by launching the single ``flake8`` script. It displays the warnings in a per-file, merged output. It also adds a few features: @@ -17,7 +17,7 @@ It also adds a few features: # flake8: noqa -- lines that contain a "# noqa" comment at the end will not issue warnings. +- lines that contain a ``# noqa`` comment at the end will not issue warnings. - a Git and a Mercurial hook. - a McCabe complexity checker. - extendable through ``flake8.extension`` entry points. @@ -26,6 +26,10 @@ It also adds a few features: QuickStart ========== +:: + + pip install flake8 + To run flake8 just invoke it against any directory or Python module:: $ flake8 coolproject @@ -54,190 +58,11 @@ anything that goes beyond 10 is too complex. See https://en.wikipedia.org/wiki/Cyclomatic_complexity. -Configuration -------------- - -The behaviour may be configured at two levels. - -The user settings are read from the ``~/.config/flake8`` file. -Example:: - - [flake8] - ignore = E226,E302,E41 - max-line-length = 160 - -At the project level, a ``tox.ini`` file or a ``setup.cfg`` file is read -if present. Only the first file is considered. If this file does not -have a ``[flake8]`` section, no project specific configuration is loaded. - -If the ``ignore`` option is not in the configuration and not in the arguments, -only the error codes ``E226`` and ``E241/E242`` are ignored -(see codes in the table below). - - -Mercurial hook -============== - -To use the Mercurial hook on any *commit* or *qrefresh*, change your .hg/hgrc -file like this:: - - [hooks] - commit = python:flake8.run.hg_hook - qrefresh = python:flake8.run.hg_hook - - [flake8] - strict = 0 - complexity = 12 - - -If *strict* option is set to **1**, any warning will block the commit. When -*strict* is set to **0**, warnings are just printed to the standard output. - -*complexity* defines the maximum McCabe complexity allowed before a warning -is emitted. If you don't specify it, it's just ignored. If specified, it must -be a positive value. 12 is usually a good value. - - -Git hook -======== - -To use the Git hook on any *commit*, add a **pre-commit** file in the -*.git/hooks* directory containing:: - - #!/usr/bin/python - import sys - from flake8.run import git_hook - - COMPLEXITY = 10 - STRICT = False - - if __name__ == '__main__': - sys.exit(git_hook(complexity=COMPLEXITY, strict=STRICT, ignore='E501')) - - -If *strict* option is set to **True**, any warning will block the commit. When -*strict* is set to **False** or omitted, warnings are just printed to the -standard output. - -*complexity* defines the maximum McCabe complexity allowed before a warning -is emitted. If you don't specify it or set it to **-1**, it's just ignored. -If specified, it must be a positive value. 12 is usually a good value. - -*lazy* when set to ``True`` will also take into account files not added to the -index. - -Also, make sure the file is executable and adapt the shebang line so it -points to your Python interpreter. - - -Buildout integration -===================== - -In order to use Flake8 inside a buildout, edit your buildout.cfg and add this:: - - [buildout] - - parts += - ... - flake8 - - [flake8] - recipe = zc.recipe.egg - eggs = flake8 - ${buildout:eggs} - entry-points = - flake8=flake8.main:main - - -setuptools integration -====================== - -If setuptools is available, Flake8 provides a command that checks the -Python files declared by your project. To use it, add flake8 to your -setup_requires:: - - setup( - name="project", - packages=["project"], - - setup_requires=[ - "flake8" - ] - ) - -Running ``python setup.py flake8`` on the command line will check the -files listed in your ``py_modules`` and ``packages``. If any warning -is found, the command will exit with an error code:: - - $ python setup.py flake8 - - - -Original projects -================= - -Flake8 is just a glue project, all the merits go to the creators of the original -projects: - -- pep8: https://github.com/jcrocholl/pep8 -- PyFlakes: https://launchpad.net/pyflakes -- McCabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html - - -Warning / Error codes -===================== - -The convention of Flake8 is to assign a code to each error or warning, like -the ``pep8`` tool. These codes are used to configure the list of errors -which are selected or ignored. - -Each code consists of an upper case ASCII letter followed by three digits. -The recommendation is to use a different prefix for each plugin. A list of the -known prefixes is published below: - -- ``E***``/``W***``: `pep8 errors and warnings - `_ -- ``F***``: PyFlakes codes (see below) -- ``C9**``: McCabe complexity plugin `mccabe - `_ -- ``N8**``: Naming Conventions plugin `pep8-naming - `_ - - -The original PyFlakes does not provide error codes. Flake8 patches the -PyFlakes messages to add the following codes: - -+------+--------------------------------------------------------------------+ -| code | sample message | -+======+====================================================================+ -| F401 | ``module`` imported but unused | -+------+--------------------------------------------------------------------+ -| F402 | import ``module`` from line ``N`` shadowed by loop variable | -+------+--------------------------------------------------------------------+ -| F403 | 'from ``module`` import \*' used; unable to detect undefined names | -+------+--------------------------------------------------------------------+ -| F404 | future import(s) ``name`` after other statements | -+------+--------------------------------------------------------------------+ -+------+--------------------------------------------------------------------+ -| F811 | redefinition of unused ``name`` from line ``N`` | -+------+--------------------------------------------------------------------+ -| F812 | list comprehension redefines ``name`` from line ``N`` | -+------+--------------------------------------------------------------------+ -| F821 | undefined name ``name`` | -+------+--------------------------------------------------------------------+ -| F822 | undefined name ``name`` in __all__ | -+------+--------------------------------------------------------------------+ -| F823 | local variable ``name`` ... referenced before assignment | -+------+--------------------------------------------------------------------+ -| F831 | duplicate argument ``name`` in function definition | -+------+--------------------------------------------------------------------+ -| F841 | local variable ``name`` is assigned to but never used | -+------+--------------------------------------------------------------------+ - +.. _links: Links ------ +===== -* `pep8 documentation `_ +* `flake8 documentation `_ -* `flake8 documentation `_ +* `pep8 documentation `_ diff --git a/docs/index.rst b/docs/index.rst index 4244958..75b48d2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,60 +1,5 @@ -====== -Flake8 -====== - -Flake8 is a wrapper around these tools: - -- PyFlakes -- pep8 -- Ned Batchelder's McCabe script - -Flake8 runs all the tools by launching the single ``flake8`` script. -It displays the warnings in a per-file, merged output. - -It also adds a few features: - -- files that contain this line are skipped:: - - # flake8: noqa - -- lines that contain a ``# noqa`` comment at the end will not issue warnings. -- a Git and a Mercurial hook. -- a McCabe complexity checker. -- extendable through ``flake8.extension`` entry points. - -QuickStart -========== - -:: - - pip install flake8 - -To run flake8 just invoke it against any directory or Python module:: - - $ flake8 coolproject - coolproject/mod.py:97:1: F401 'shutil' imported but unused - coolproject/mod.py:625:17: E225 missing whitespace around operato - coolproject/mod.py:729:1: F811 redefinition of function 'readlines' from line 723 - coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used - -The outputs of PyFlakes *and* pep8 (and the optional plugins) are merged -and returned. - -flake8 offers an extra option: --max-complexity, which will emit a warning if -the McCabe complexity of a function is higher than the value. By default it's -deactivated:: - - $ flake8 --max-complexity 12 coolproject - coolproject/mod.py:97:1: F401 'shutil' imported but unused - coolproject/mod.py:625:17: E225 missing whitespace around operator - coolproject/mod.py:729:1: F811 redefinition of unused 'readlines' from line 723 - coolproject/mod.py:939:1: C901 'Checker.check_all' is too complex (12) - coolproject/mod.py:1028:1: F841 local variable 'errors' is assigned to but never used - coolproject/mod.py:1204:1: C901 'selftest' is too complex (14) - -This feature is quite useful to detect over-complex code. According to McCabe, -anything that goes beyond 10 is too complex. -See https://en.wikipedia.org/wiki/Cyclomatic_complexity. +.. include:: ../README.txt + :end-before: _links: Documentation ============= From 55659b28ab1bcb510bdaedceb34d7e5a873313fe Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Sat, 23 Feb 2013 13:20:45 +0100 Subject: [PATCH 56/60] Fix typo in the include directive --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 75b48d2..350e04f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,4 @@ -.. include:: ../README.txt +.. include:: ../README.rst :end-before: _links: Documentation From d22068b81c24e7e5673d9301ecc0139818dac685 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 23 Feb 2013 14:33:08 -0500 Subject: [PATCH 57/60] Add extension documentation Add more to the CHANGES list --- CHANGES.rst | 5 ++ docs/extensions.rst | 122 ++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 2 + 3 files changed, 129 insertions(+) create mode 100644 docs/extensions.rst diff --git a/CHANGES.rst b/CHANGES.rst index c144f88..907312b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,11 @@ CHANGES 2.0.0 - 2013-02-xx ------------------ +- Pyflakes errors are now prefixed by an ``F`` instead of an ``E`` +- McCabe complexity warnings are now prefixed by a ``C`` instead of a ``W`` +- Flake8 now supports extensions through entry points +- Due to the above support, we now **require** setuptools +- We now have `documentation `_ - Fixes #13: pep8 and pyflakes are now external dependencies - Split run.py into main.py and hooks.py for better logic - Expose our parser for our users diff --git a/docs/extensions.rst b/docs/extensions.rst new file mode 100644 index 0000000..01b8d62 --- /dev/null +++ b/docs/extensions.rst @@ -0,0 +1,122 @@ +Writing an Extension for Flake8 +=============================== + +If you plan on supporting python 2.5 you need to first do ``from __future__ +import with_statement``. Every version of python greater than 2.5 already has +the with statement built-in. After that, you should only ever need to import +``setup`` from ``setuptools``. We're using entry points for extension +management and this is the most sane way of doing things. This also means that +you should specify that the installation requires (at least) ``setuptools``. +Finally you'll need to specify your entry points, e.g., :: + + setup( + entry_points={ + 'flake8.extension': ['P10 = package.PackageEntryClass'], + } + ) + +Below is an example from mccabe_ for how to write your ``setup.py`` file for +your Flake8 extension. + +.. code-block:: python + + # https://github.com/florentx/mccabe/blob/master/setup.py + # -*- coding: utf-8 -*- + from __future__ import with_statement + from setuptools import setup + + + def get_version(fname='mccabe.py'): + with open(fname) as f: + for line in f: + if line.startswith('__version__'): + return eval(line.split('=')[-1]) + + + def get_long_description(): + descr = [] + for fname in ('README.rst',): + with open(fname) as f: + descr.append(f.read()) + return '\n\n'.join(descr) + + + setup( + name='mccabe', + version=get_version(), + description="McCabe checker, plugin for flake8", + long_description=get_long_description(), + keywords='flake8 mccabe', + author='Tarek Ziade', + author_email='tarek@ziade.org', + maintainer='Florent Xicluna', + maintainer_email='florent.xicluna@gmail.com', + url='https://github.com/florentx/mccabe', + license='Expat license', + py_modules=['mccabe'], + zip_safe=False, + install_requires=[ + 'setuptools', + ], + entry_points={ + 'flake8.extension': [ + 'C90 = mccabe:McCabeChecker', + ], + }, + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Software Development :: Quality Assurance', + ], + ) + +In ``mccabe.py`` you can see that extra options are added to the parser when +flake8 registers the extensions: + +.. code-block:: python + + # https://github.com/florentx/mccabe/blob/e88be51e0c6c2bd1b87d7a44b7e71b78fdc53959/mccabe.py#L225 + class McCabeChecker(object): + """McCabe cyclomatic complexity checker.""" + name = 'mccabe' + version = __version__ + _code = 'C901' + _error_tmpl = "C901 %r is too complex (%d)" + max_complexity = 0 + + def __init__(self, tree, filename): + self.tree = tree + + @classmethod + def add_options(cls, parser): + parser.add_option('--max-complexity', default=-1, action='store', + type='int', help="McCabe complexity threshold") + parser.config_options.append('max-complexity') + + @classmethod + def parse_options(cls, options): + cls.max_complexity = options.max_complexity + + def run(self): + if self.max_complexity < 0: + return + visitor = PathGraphingAstVisitor() + visitor.preorder(self.tree, visitor) + for graph in visitor.graphs.values(): + if graph.complexity() >= self.max_complexity: + text = self._error_tmpl % (graph.entity, graph.complexity()) + yield graph.lineno, 0, text, type(self) + +Since that is the defined entry point in the above ``setup.py``, flake8 finds +it and uses it to register the extension. + +.. links +.. _mccabe: https://github.com/florentx/mccabe +.. _PyPI: https://pypi.python.org/pypi/ diff --git a/docs/index.rst b/docs/index.rst index 350e04f..c95d6fd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,3 +22,5 @@ projects: - pep8: https://github.com/jcrocholl/pep8 - PyFlakes: https://launchpad.net/pyflakes - McCabe: http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html + +.. include:: ../CHANGES.rst From b4c212ba2d51c4cf325141be41cf9a7c39ab2714 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 23 Feb 2013 14:34:56 -0500 Subject: [PATCH 58/60] Fix the RTD URL --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 907312b..0a048b4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,7 +8,7 @@ CHANGES - McCabe complexity warnings are now prefixed by a ``C`` instead of a ``W`` - Flake8 now supports extensions through entry points - Due to the above support, we now **require** setuptools -- We now have `documentation `_ +- We now have `documentation `_ - Fixes #13: pep8 and pyflakes are now external dependencies - Split run.py into main.py and hooks.py for better logic - Expose our parser for our users From 83e53cc5a21b14d9fa4e81fb1ab7181461893299 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 23 Feb 2013 14:36:08 -0500 Subject: [PATCH 59/60] Forgot to include the extension docs in the toctree --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index c95d6fd..39be452 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,6 +7,7 @@ Documentation .. toctree:: api + extensions config vcs buildout From 3dc992c0d07e4e98c56519cae575b8c0e217de1f Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 23 Feb 2013 14:49:53 -0500 Subject: [PATCH 60/60] Huge improvement to the extension docs --- docs/extensions.rst | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/extensions.rst b/docs/extensions.rst index 01b8d62..232fc55 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -1,20 +1,34 @@ Writing an Extension for Flake8 =============================== -If you plan on supporting python 2.5 you need to first do ``from __future__ -import with_statement``. Every version of python greater than 2.5 already has -the with statement built-in. After that, you should only ever need to import -``setup`` from ``setuptools``. We're using entry points for extension -management and this is the most sane way of doing things. This also means that -you should specify that the installation requires (at least) ``setuptools``. -Finally you'll need to specify your entry points, e.g., :: +Since Flake8 is now adding support for extensions, we require ``setuptools`` +so we can manage extensions through entry points. If you are making an +existing tool compatible with Flake8 but do not already require +``setuptools``, you should probably add it to your list of requirements. Next, +you'll need to edit your ``setup.py`` file so that upon installation, your +extension is registered. If you define a class called ``PackageEntryClass`` +then this would look something like the following:: + setup( + # ... entry_points={ 'flake8.extension': ['P10 = package.PackageEntryClass'], } + # ... ) +We used ``P10`` here, but in reality, you should check to prevent as much +future overlap as possible with other extensions. ``W`` and ``E`` followed by +three digits should be considered entirely reserved for pep8. ``F`` should be +considered reserved for Pyflakes and ``C`` for McCabe. Also, in anticipation +of possible pylint integration, ``W`` and ``E`` followed by four digits should +be considered reserved. We have no way of checking for those though, so while +we ask you not use them, we can not (currently) prevent you from doing so. + +A Real Example: McCabe +---------------------- + Below is an example from mccabe_ for how to write your ``setup.py`` file for your Flake8 extension.