From 103221e61a154aba6be0850a5ff142f3fd216b34 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 24 Jul 2014 20:06:44 +0200 Subject: Report W503 for line breaks before binary operators; issue #197. #ep14boat --- pep8.py | 39 +++++++++++++++++++++++++++++++++++++++ testsuite/E12.py | 6 +++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/pep8.py b/pep8.py index b31a978..1dae086 100755 --- a/pep8.py +++ b/pep8.py @@ -922,6 +922,45 @@ def explicit_line_join(logical_line, tokens): parens -= 1 +def break_around_binary_operator(logical_line, tokens): + r""" + Avoid breaks before binary operators. + + The preferred place to break around a binary operator is after the + operator, not before it. + + W503: (width == 0\n + height == 0) + W503: (width == 0\n and height == 0) + + Okay: (width == 0 +\n height == 0) + Okay: foo(\n -x) + Okay: foo(x\n []) + Okay: x = '''\n''' + '' + Okay: foo(x,\n -y) + Okay: foo(x, # comment\n -y) + """ + def is_binary_operator(token_type, text): + # The % character is strictly speaking a binary operator, but the + # common usage seems to be to put it next to the format parameters, + # after a line break. + return ((token_type == tokenize.OP or text == 'and' or text == 'or') + and text not in "()[]{},:.;@=%") + + line_break = False + unary_context = True + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + continue + if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: + line_break = True + else: + if (is_binary_operator(token_type, text) and line_break + and not unary_context): + yield start, "W503 line break before binary operator" + unary_context = text in '([{,;' + line_break = False + + def comparison_to_singleton(logical_line, noqa): r"""Comparison to singletons should use "is" or "is not". diff --git a/testsuite/E12.py b/testsuite/E12.py index 6ebd44e..a995c95 100644 --- a/testsuite/E12.py +++ b/testsuite/E12.py @@ -323,14 +323,14 @@ if line_removed: rv.update(d=('a', 'b', 'c'), e=42) # -#: E127 +#: E127 W503 rv.update(d=('a' + 'b', 'c'), e=42, f=42 + 42) -#: E127 +#: E127 W503 input1 = {'a': {'calc': 1 + 2}, 'b': 1 + 42} -#: E128 +#: E128 W503 rv.update(d=('a' + 'b', 'c'), e=42, f=(42 + 42)) -- cgit v1.2.1 From 7f2a185aba1a0e5ab796b66230c67af2ff2be380 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 24 Jul 2014 20:33:41 +0200 Subject: Fix violations of new rule in pep8.py. #ep14boat --- pep8.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pep8.py b/pep8.py index 1dae086..1764005 100755 --- a/pep8.py +++ b/pep8.py @@ -506,8 +506,9 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, yield start, "%s continuation line %s" % error # look for visual indenting - if (parens[row] and token_type not in (tokenize.NL, tokenize.COMMENT) - and not indent[depth]): + if (parens[row] and + token_type not in (tokenize.NL, tokenize.COMMENT) and + not indent[depth]): indent[depth] = start[1] indent_chances[start[1]] = True if verbose >= 4: @@ -943,8 +944,8 @@ def break_around_binary_operator(logical_line, tokens): # The % character is strictly speaking a binary operator, but the # common usage seems to be to put it next to the format parameters, # after a line break. - return ((token_type == tokenize.OP or text == 'and' or text == 'or') - and text not in "()[]{},:.;@=%") + return ((token_type == tokenize.OP or text in ['and', 'or']) and + text not in "()[]{},:.;@=%") line_break = False unary_context = True @@ -954,8 +955,8 @@ def break_around_binary_operator(logical_line, tokens): if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: line_break = True else: - if (is_binary_operator(token_type, text) and line_break - and not unary_context): + if (is_binary_operator(token_type, text) and line_break and + not unary_context): yield start, "W503 line break before binary operator" unary_context = text in '([{,;' line_break = False @@ -1364,8 +1365,8 @@ class Checker(object): (start_row, start_col) = start if prev_row != start_row: # different row prev_text = self.lines[prev_row - 1][prev_col - 1] - if prev_text == ',' or (prev_text not in '{[(' - and text not in '}])'): + if prev_text == ',' or (prev_text not in '{[(' and + text not in '}])'): text = ' ' + text elif prev_col != start_col: # different column text = line[prev_col:start_col] + text -- cgit v1.2.1 From 69a3a3da9ed280831787bcecd6ee47caa2969571 Mon Sep 17 00:00:00 2001 From: Qiangning Hong Date: Fri, 8 Aug 2014 11:15:24 +0800 Subject: allow use # noqa to disable E721 warning Sometimes `isinstance()` is not suitable for comparing types. For example, someone may want to make sure an object is exact instance of a type, not instance of its subclasses. --- docs/intro.rst | 2 +- pep8.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 5e3af1f..806bbba 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -323,7 +323,7 @@ This is the current list of error and warning codes: +----------+----------------------------------------------------------------------+ | E714 | test for object identity should be 'is not' | +----------+----------------------------------------------------------------------+ -| E721 | do not compare types, use 'isinstance()' | +| E721 (^) | do not compare types, use 'isinstance()' | +----------+----------------------------------------------------------------------+ | E731 | do not assign a lambda expression, use a def | +----------+----------------------------------------------------------------------+ diff --git a/pep8.py b/pep8.py index b31a978..8a83e3e 100755 --- a/pep8.py +++ b/pep8.py @@ -974,7 +974,7 @@ def comparison_negative(logical_line): yield pos, "E714 test for object identity should be 'is not'" -def comparison_type(logical_line): +def comparison_type(logical_line, noqa): r"""Object type comparisons should always use isinstance(). Do not compare types directly. @@ -990,7 +990,7 @@ def comparison_type(logical_line): Okay: if type(a1) is type(b1): """ match = COMPARE_TYPE_REGEX.search(logical_line) - if match: + if match and not noqa: inst = match.group(1) if inst and isidentifier(inst) and inst not in SINGLETONS: return # Allow comparison for types which are not obvious -- cgit v1.2.1 From 974748fd9a0ecdd494cdc51aeb16c54d38256d58 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 17 Dec 2014 23:42:03 -0800 Subject: Update DEFAULT_IGNORE to add E121 & E126 per discussion in issues #256 #316 --- pep8.py | 2 +- testsuite/test_api.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pep8.py b/pep8.py index c8889b5..6c51733 100755 --- a/pep8.py +++ b/pep8.py @@ -65,7 +65,7 @@ except ImportError: from ConfigParser import RawConfigParser DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' -DEFAULT_IGNORE = 'E123,E226,E24,E704' +DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' try: if sys.platform == 'win32': DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') diff --git a/testsuite/test_api.py b/testsuite/test_api.py index de7bc7b..341fb34 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -179,7 +179,10 @@ class APITestCase(unittest.TestCase): options = parse_argv('').options self.assertEqual(options.select, ()) - self.assertEqual(options.ignore, ('E123', 'E226', 'E24', 'E704')) + self.assertEqual( + options.ignore, + ('E121', 'E123', 'E126', 'E226', 'E24', 'E704') + ) options = parse_argv('--doctest').options self.assertEqual(options.select, ()) -- cgit v1.2.1 From fe6a8a813d616c9fa397b2fd5b967bf72378b09a Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 18 Dec 2014 00:00:36 -0800 Subject: Update documentation to reflect changes to default; issues #356 #316 --- docs/intro.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 1bcafe8..63a1fe6 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -228,7 +228,7 @@ This is the current list of error and warning codes: | E116 | unexpected indentation (comment) | +----------+----------------------------------------------------------------------+ +----------+----------------------------------------------------------------------+ -| E121 (^) | continuation line under-indented for hanging indent | +| E121 (*^)| continuation line under-indented for hanging indent | +----------+----------------------------------------------------------------------+ | E122 (^) | continuation line missing indentation or outdented | +----------+----------------------------------------------------------------------+ @@ -238,7 +238,7 @@ This is the current list of error and warning codes: +----------+----------------------------------------------------------------------+ | E125 (^) | continuation line with same indent as next logical line | +----------+----------------------------------------------------------------------+ -| E126 (^) | continuation line over-indented for hanging indent | +| E126 (*^)| continuation line over-indented for hanging indent | +----------+----------------------------------------------------------------------+ | E127 (^) | continuation line over-indented for visual indent | +----------+----------------------------------------------------------------------+ @@ -395,11 +395,11 @@ This is the current list of error and warning codes: +----------+----------------------------------------------------------------------+ -**(*)** In the default configuration, the checks **E123**, **E133**, **E226**, -**E241**, **E242** and **E704** are ignored because they are not rules unanimously -accepted, and `PEP 8`_ does not enforce them. The check **E133** is mutually -exclusive with check **E123**. Use switch ``--hang-closing`` to report **E133** -instead of **E123**. +**(*)** In the default configuration, the checks **E121**, **E123**, **E126**, +**E133**, **E226**, **E241**, **E242** and **E704** are ignored because they +are not rules unanimously accepted, and `PEP 8`_ does not enforce them. The +check **E133** is mutually exclusive with check **E123**. Use switch ``--hang- +closing`` to report **E133** instead of **E123**. **(^)** These checks can be disabled at the line level using the ``# noqa`` special comment. This possibility should be reserved for special cases. @@ -420,7 +420,7 @@ Related tools The `flake8 checker `_ is a wrapper around ``pep8`` and similar tools. It supports plugins. -Other tools which use ``pep8`` are referenced in the Wiki: `list of related tools -`_. +Other tools which use ``pep8`` are referenced in the Wiki: `list of related +tools `_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ -- cgit v1.2.1 From 2e540a1d8c623b99b35c444649848a40c2f2c95c Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 18 Dec 2014 00:29:51 -0800 Subject: Add entry to changelog. --- CHANGES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index c57bfd1..a1aacc9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -30,6 +30,8 @@ Changes: * Add ``.tox/`` to default excludes. (Issue #335) +* Do not report E121 or E126 in the default configuration. (Issues #256 / #316) + Bug fixes: * Don't crash if Checker.build_tokens_line() returns None. (Issue #306) -- cgit v1.2.1 From 497f1db5d142ddb891504d2513d52c5f195c05ac Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Tue, 16 Dec 2014 20:51:07 +0100 Subject: Do not skip physical checks if the newline is escaped; issue #319 --- CHANGES.txt | 2 ++ pep8.py | 12 +++++------- testsuite/E50.py | 14 ++++++++++++++ testsuite/W19.py | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d53fb70..d0ae802 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -43,6 +43,8 @@ Bug fixes: * Fix false positive E711/E712/E713. (Issues #330 and #336) +* Do not skip physical checks if the newline is escaped. (Issue #319) + 1.5.7 (2014-05-29) ------------------ diff --git a/pep8.py b/pep8.py index bb46c22..914d7fd 100755 --- a/pep8.py +++ b/pep8.py @@ -1238,14 +1238,12 @@ def filename_match(filename, patterns, default=True): return any(fnmatch(filename, pattern) for pattern in patterns) +def _is_eol_token(token): + return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' if COMMENT_WITH_NL: - def _is_eol_token(token): - return (token[0] in NEWLINE or - (token[0] == tokenize.COMMENT and token[1] == token[4])) -else: - def _is_eol_token(token): - return token[0] in NEWLINE - + def _is_eol_token(token, _eol_token=_is_eol_token): + return _eol_token(token) or (token[0] == tokenize.COMMENT and + token[1] == token[4]) ############################################################################## # Framework to run all checks diff --git a/testsuite/E50.py b/testsuite/E50.py index 31ad6b9..f60f389 100644 --- a/testsuite/E50.py +++ b/testsuite/E50.py @@ -1,5 +1,19 @@ #: E501 a = '12345678901234567890123456789012345678901234567890123456789012345678901234567890' +#: E501 +a = '1234567890123456789012345678901234567890123456789012345678901234567890' or \ + 6 +#: E501 +a = 7 or \ + '1234567890123456789012345678901234567890123456789012345678901234567890' or \ + 6 +#: E501 E501 +a = 7 or \ + '1234567890123456789012345678901234567890123456789012345678901234567890' or \ + '1234567890123456789012345678901234567890123456789012345678901234567890' or \ + 6 +#: E501 +a = '1234567890123456789012345678901234567890123456789012345678901234567890' # \ #: E502 a = ('123456789012345678901234567890123456789012345678901234567890123456789' \ '01234567890') diff --git a/testsuite/W19.py b/testsuite/W19.py index edbb1f0..afdfb76 100644 --- a/testsuite/W19.py +++ b/testsuite/W19.py @@ -86,7 +86,7 @@ if (a == 2 or b == """abc def ghi jkl mno"""): return True -#: E101 W191 +#: W191:2:1 W191:3:1 E101:3:2 if length > options.max_line_length: return options.max_line_length, \ "E501 line too long (%d characters)" % length -- cgit v1.2.1 From 53d4741dbae96d0435fd8c55176611cc38816369 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 18 Dec 2014 01:20:58 -0800 Subject: Update documentation to match function. --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 1bcafe8..8d56add 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -280,7 +280,7 @@ This is the current list of error and warning codes: | E228 | missing whitespace around modulo operator | +----------+----------------------------------------------------------------------+ +----------+----------------------------------------------------------------------+ -| E231 | missing whitespace after ',' | +| E231 | missing whitespace after ',', ';', or ':' | +----------+----------------------------------------------------------------------+ +----------+----------------------------------------------------------------------+ | E241 (*) | multiple spaces after ',' | -- cgit v1.2.1 From 373e0ac1138f0e24422b5e2e78f02ed0557d5c23 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 15 Dec 2014 19:01:29 -0800 Subject: Allow try/except/else/finally keywords intermixed with imports. #304 * This allows use of conditional imports. * Also move `__version__` definition to conform with this rule. --- pep8.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pep8.py b/pep8.py index 914d7fd..920213a 100755 --- a/pep8.py +++ b/pep8.py @@ -47,8 +47,6 @@ W warnings """ from __future__ import with_statement -__version__ = '1.6.0a0' - import os import sys import re @@ -64,6 +62,8 @@ try: except ImportError: from ConfigParser import RawConfigParser +__version__ = '1.6.0a0' + DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E123,E226,E24,E704' try: @@ -850,7 +850,8 @@ def module_imports_on_top_of_file( Okay: # this is a comment\nimport os Okay: '''this is a module docstring'''\nimport os Okay: r'''this is a module docstring'''\nimport os - Okay: __version__ = "123"\nimport os + Okay: try:\n import x\nexcept:\n pass\nelse:\n pass\nimport y + Okay: try:\n import x\nexcept:\n pass\nfinally:\n pass\nimport y E402: a=1\nimport os E402: 'One string'\n"Two string"\nimport os E402: a=1\nfrom sys import x @@ -864,6 +865,8 @@ def module_imports_on_top_of_file( line = line[1:] return line and (line[0] == '"' or line[0] == "'") + allowed_try_keywords = ('try', 'except', 'else', 'finally') + if indent_level: # Allow imports in conditional statements or functions return if not logical_line: # Allow empty lines or comments @@ -874,9 +877,9 @@ def module_imports_on_top_of_file( if line.startswith('import ') or line.startswith('from '): if checker_state.get('seen_non_imports', False): yield 0, "E402 import not at top of file" - elif line.startswith('__version__ '): - # These lines should be included after the module's docstring, before - # any other code, separated by a blank line above and below. + elif any(line.startswith(kw) for kw in allowed_try_keywords): + # Allow try, except, else, finally keywords intermixed with imports in + # order to support conditional importing return elif is_string_literal(line): # The first literal is a docstring, allow it. Otherwise, report error. -- cgit v1.2.1 From 17e3b14a97c19704bf35221f89ac0567777ed7ad Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 15 Dec 2014 19:49:16 -0800 Subject: Update E402 error message emitted --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 920213a..92a10db 100755 --- a/pep8.py +++ b/pep8.py @@ -876,7 +876,7 @@ def module_imports_on_top_of_file( line = logical_line if line.startswith('import ') or line.startswith('from '): if checker_state.get('seen_non_imports', False): - yield 0, "E402 import not at top of file" + yield 0, "E402 module level import not at top of file" elif any(line.startswith(kw) for kw in allowed_try_keywords): # Allow try, except, else, finally keywords intermixed with imports in # order to support conditional importing -- cgit v1.2.1 From d6da0b82a3c5d13c51d61da01c1a6eb3be16cf9a Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 15 Dec 2014 19:49:34 -0800 Subject: Add more test cases for E402; issue #304 --- testsuite/E40.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/testsuite/E40.py b/testsuite/E40.py index d921c25..1051e32 100644 --- a/testsuite/E40.py +++ b/testsuite/E40.py @@ -11,3 +11,28 @@ from foo.bar.yourclass import YourClass import myclass import foo.bar.yourclass +#: E402 +__all__ = ['abc'] + +import foo +#: Okay +try: + import foo +except: + pass +else: + print('imported foo') +finally: + print('made attempt to import foo') + +import bar +#: E402 +VERSION = '1.2.3' + +import foo +#: E402 +import foo + +a = 1 + +import bar -- cgit v1.2.1 From 40b1861c00ce6d0a18d6705dcc99d990aa0cf3bf Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 20 Dec 2014 19:10:14 -0800 Subject: Dont include html_static_path, suppresses warning when building docs. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index d1dca3c..a77ac83 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -128,7 +128,7 @@ html_theme = 'default' # 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'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -- cgit v1.2.1 From 96ddf8c25f053a9d7ced73b194d8558c90069319 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 22 Dec 2014 21:34:22 -0800 Subject: Specify the default ignore list in the help options. --- pep8.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 92a10db..a2e718b 100755 --- a/pep8.py +++ b/pep8.py @@ -1866,7 +1866,8 @@ def get_parser(prog='pep8', version=__version__): parser.add_option('--select', metavar='errors', default='', help="select errors and warnings (e.g. E,W6)") parser.add_option('--ignore', metavar='errors', default='', - help="skip errors and warnings (e.g. E4,W)") + help="skip errors and warnings (e.g. E4,W) " + "(default: %s)" % DEFAULT_IGNORE) parser.add_option('--show-source', action='store_true', help="show source code for each error") parser.add_option('--show-pep8', action='store_true', -- cgit v1.2.1 From a94f9256b0a43735207ea67a18a21c67d94f2146 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 27 Dec 2014 19:30:24 +0900 Subject: sys.stdout.flush() to avoid broken line. --- pep8.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pep8.py b/pep8.py index 940a272..9d3b091 100755 --- a/pep8.py +++ b/pep8.py @@ -1681,6 +1681,13 @@ class StandardReport(BaseReport): 'row': self.line_offset + line_number, 'col': offset + 1, 'code': code, 'text': text, }) + # stdout is block buffered when not stdout.isatty(). + # When using pep8 in multiprocess, line can be broken where + # buffer boundary since other processes write to same file. + # So flush() after print() to avoid buffer boundary. + # Typical buffer size is 8192. line written safely when + # len(line) < 8192. + sys.stdout.flush() if self._show_source: if line_number > len(self.lines): line = '' @@ -1688,8 +1695,10 @@ class StandardReport(BaseReport): line = self.lines[line_number - 1] print(line.rstrip()) print(re.sub(r'\S', ' ', line[:offset]) + '^') + sys.stdout.flush() if self._show_pep8 and doc: print(' ' + doc.strip()) + sys.stdout.flush() return self.file_errors -- cgit v1.2.1 From eaf09d1ae4a92bd0982b5757f2872d0583f7a3d3 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 18 Dec 2014 01:54:20 -0800 Subject: Allow spaces around equals sign of an annotated function definition parameter; issue #357 --- CHANGES.txt | 2 ++ pep8.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 774a90d..91a2095 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -34,6 +34,8 @@ Changes: * Do not report E121 or E126 in the default configuration. (Issues #256 / #316) +* Allow spaces around the equals sign in an annotated function. (Issue #357) + Bug fixes: * Don't crash if Checker.build_tokens_line() returns None. (Issue #306) diff --git a/pep8.py b/pep8.py index 940a272..1f7358e 100755 --- a/pep8.py +++ b/pep8.py @@ -754,6 +754,8 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): Okay: boolean(a != b) Okay: boolean(a <= b) Okay: boolean(a >= b) + Okay: def foo(arg: int = 42): + Okay: def f(x: int, y=15, z: float = 0.123) -> list: E251: def complex(real, imag = 0.0): E251: return magic(r = real, i = imag) @@ -761,6 +763,8 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): parens = 0 no_space = False prev_end = None + annotated_func_arg = False + in_def = logical_line.startswith('def') message = "E251 unexpected spaces around keyword / parameter equals" for token_type, text, start, end, line in tokens: if token_type == tokenize.NL: @@ -774,7 +778,11 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): parens += 1 elif text == ')': parens -= 1 - elif parens and text == '=': + elif in_def and text == ':': + annotated_func_arg = True + elif parens and text == ',': + annotated_func_arg = False + elif parens and text == '=' and not annotated_func_arg: no_space = True if start != prev_end: yield (prev_end, message) -- cgit v1.2.1 From 4f848e5018e9994a4562b13a0b3ea96b9bade0bf Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 18 Dec 2014 23:34:57 -0800 Subject: Update tests for issue #357 --- pep8.py | 4 +++- testsuite/E25.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 1f7358e..ca57638 100755 --- a/pep8.py +++ b/pep8.py @@ -755,7 +755,6 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): Okay: boolean(a <= b) Okay: boolean(a >= b) Okay: def foo(arg: int = 42): - Okay: def f(x: int, y=15, z: float = 0.123) -> list: E251: def complex(real, imag = 0.0): E251: return magic(r = real, i = imag) @@ -786,6 +785,9 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = True if start != prev_end: yield (prev_end, message) + if not parens: + annotated_func_arg = False + prev_end = end diff --git a/testsuite/E25.py b/testsuite/E25.py index 9b7ff69..ad8db88 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -29,3 +29,8 @@ foo(bar=(1 >= 1)) foo(bar=(1 <= 1)) (options, args) = parser.parse_args() d[type(None)] = _deepcopy_atomic + +# Annotated Function Definitions +#: Okay +def munge(input: AnyStr, sep: AnyStr = None, limit=1000) -> AnyStr: + pass -- cgit v1.2.1 From 219db61762dd442a2a0ec02c2962e2313a0fd128 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 21 Dec 2014 21:22:15 -0800 Subject: Only an annotated function if in the first layer of parens --- pep8.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep8.py b/pep8.py index ca57638..67b32d1 100755 --- a/pep8.py +++ b/pep8.py @@ -777,9 +777,9 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): parens += 1 elif text == ')': parens -= 1 - elif in_def and text == ':': + elif in_def and text == ':' and parens == 1: annotated_func_arg = True - elif parens and text == ',': + elif parens and text == ',' and parens == 1: annotated_func_arg = False elif parens and text == '=' and not annotated_func_arg: no_space = True -- cgit v1.2.1 From 6b9004b55f5026716b739e35f42b5874c16607a2 Mon Sep 17 00:00:00 2001 From: Simon Kennedy Date: Sat, 9 Aug 2014 16:53:41 +0100 Subject: Update documentation to match config file location change in last commit. --- docs/intro.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index b5e3971..a5da86a 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -185,8 +185,17 @@ Configuration The behaviour may be configured at two levels. -The user settings are read from the ``~/.config/pep8`` file and -for Windows from the ``~\.pep8`` file. +The user settings are read from the following locations: + +If on Windows: + ``~\.pep8`` + +Otherwise, if the :envvar:`XDG_CONFIG_HOME` environment variable is defined: + ``XDG_CONFIG_HOME/pep8`` + +Else if :envvar:`XDG_CONFIG_HOME` is not defined: + ``~/.config/pep8`` + Example:: [pep8] -- cgit v1.2.1 From 510003502b5e067b927e9a62b69937cd9b0eff6e Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 29 Dec 2014 13:10:14 -0800 Subject: Update docs for configuration --- docs/intro.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index a5da86a..13a9553 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -183,9 +183,9 @@ Quick help is available on the command line:: Configuration ------------- -The behaviour may be configured at two levels. +The behaviour may be configured at two levels, the user and project levels. -The user settings are read from the following locations: +At the user level, settings are read from the following locations: If on Windows: ``~\.pep8`` @@ -202,7 +202,7 @@ Example:: ignore = E226,E302,E41 max-line-length = 160 -At the project level, a ``tox.ini`` file or a ``setup.cfg`` file is read if +At the project level, a ``setup.cfg`` file or a ``tox.ini`` file is read if present (``.pep8`` file is also supported, but it is deprecated). If none of these files have a ``[pep8]`` section, no project specific configuration is loaded. -- cgit v1.2.1 From cfa24ab02e24914038c55286dbb2f19d1ccf9a31 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 12 Jan 2015 23:45:03 -0800 Subject: Reduce calls to sys.stdout.flush() from 3 to 1; issue #363 --- pep8.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pep8.py b/pep8.py index 9d3b091..77c5df0 100755 --- a/pep8.py +++ b/pep8.py @@ -1681,13 +1681,6 @@ class StandardReport(BaseReport): 'row': self.line_offset + line_number, 'col': offset + 1, 'code': code, 'text': text, }) - # stdout is block buffered when not stdout.isatty(). - # When using pep8 in multiprocess, line can be broken where - # buffer boundary since other processes write to same file. - # So flush() after print() to avoid buffer boundary. - # Typical buffer size is 8192. line written safely when - # len(line) < 8192. - sys.stdout.flush() if self._show_source: if line_number > len(self.lines): line = '' @@ -1695,10 +1688,16 @@ class StandardReport(BaseReport): line = self.lines[line_number - 1] print(line.rstrip()) print(re.sub(r'\S', ' ', line[:offset]) + '^') - sys.stdout.flush() if self._show_pep8 and doc: print(' ' + doc.strip()) - sys.stdout.flush() + + # stdout is block buffered when not stdout.isatty(). + # line can be broken where buffer boundary since other processes + # write to same file. + # flush() after print() to avoid buffer boundary. + # Typical buffer size is 8192. line written safely when + # len(line) < 8192. + sys.stdout.flush() return self.file_errors -- cgit v1.2.1 From 73b1e63e48d46ba39ed13d775e8efdb784198cf3 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 12 Jan 2015 23:51:02 -0800 Subject: Add flush method to test support PseudoFile object; issue #363 --- testsuite/support.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testsuite/support.py b/testsuite/support.py index 5185005..6bc795d 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -16,6 +16,9 @@ class PseudoFile(list): def getvalue(self): return ''.join(self) + def flush(self): + pass + class TestReport(StandardReport): """Collect the results for the tests.""" -- cgit v1.2.1 From 4579585f1441dc4bb4a9b44f58b2fbe2ef3c77dd Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 12 Jan 2015 23:54:55 -0800 Subject: Update changelog for issue #363 --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 774a90d..4e2adc0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -47,6 +47,9 @@ Bug fixes: * Do not skip physical checks if the newline is escaped. (Issue #319) +* Flush sys.stdout to avoid race conditions with printing. See flake8 bug: + https://gitlab.com/pycqa/flake8/issues/17 for more details. (Issue #363) + 1.5.7 (2014-05-29) ------------------ -- cgit v1.2.1 From f7a9648da6245738b72fcd664a20168d54953ee7 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 26 Jan 2015 22:28:08 -0800 Subject: Allow backslash to end a line if within inline comment; issue #374 --- CHANGES.txt | 2 ++ pep8.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 91a2095..96cf062 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -36,6 +36,8 @@ Changes: * Allow spaces around the equals sign in an annotated function. (Issue #357) +* Allow trailing backslash if in an inline comment. (Issue #374) + Bug fixes: * Don't crash if Checker.build_tokens_line() returns None. (Issue #306) diff --git a/pep8.py b/pep8.py index 67b32d1..df39802 100755 --- a/pep8.py +++ b/pep8.py @@ -972,10 +972,14 @@ def explicit_line_join(logical_line, tokens): Okay: aaa = [123,\n 123] Okay: aaa = ("bbb "\n "ccc") Okay: aaa = "bbb " \\n "ccc" + Okay: aaa = 123 # \\ """ prev_start = prev_end = parens = 0 + comment = False for token_type, text, start, end, line in tokens: - if start[0] != prev_start and parens and backslash: + if token_type == tokenize.COMMENT: + comment = True + if start[0] != prev_start and parens and backslash and not comment: yield backslash, "E502 the backslash is redundant between brackets" if end[0] != prev_end: if line.rstrip('\r\n').endswith('\\'): -- cgit v1.2.1 From 57372c68833368ed9a999da3a4f186312e880639 Mon Sep 17 00:00:00 2001 From: Jason Killen Date: Thu, 8 Jan 2015 17:02:16 -0500 Subject: #368 don't search the direcotry tree if config was defined with --config --- pep8.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pep8.py b/pep8.py index 79497e2..91b0e95 100755 --- a/pep8.py +++ b/pep8.py @@ -1934,15 +1934,16 @@ def read_config(options, args, arglist, parser): print('user configuration: %s' % user_conf) config.read(user_conf) - local_dir = os.curdir - parent = tail = args and os.path.abspath(os.path.commonprefix(args)) - while tail: - if config.read([os.path.join(parent, fn) for fn in PROJECT_CONFIG]): - local_dir = parent - if options.verbose: - print('local configuration: in %s' % parent) - break - (parent, tail) = os.path.split(parent) + else: + local_dir = os.curdir + parent = tail = args and os.path.abspath(os.path.commonprefix(args)) + while tail: + if config.read([os.path.join(parent, fn) for fn in PROJECT_CONFIG]): + local_dir = parent + if options.verbose: + print('local configuration: in %s' % parent) + break + (parent, tail) = os.path.split(parent) pep8_section = parser.prog if config.has_section(pep8_section): -- cgit v1.2.1 From ed1e25cc274926e07fc0e3859df8980814a98b2f Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 5 Feb 2015 22:37:05 -0800 Subject: Update the ordering of configs parsed; issue #368 / #369 Remove the default of ~/.config/pep8, and instead parse that explictly. If a command line option is passed using --config, then it is the only config handled. --- pep8.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/pep8.py b/pep8.py index 91b0e95..12f7afd 100755 --- a/pep8.py +++ b/pep8.py @@ -68,12 +68,14 @@ DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' try: if sys.platform == 'win32': - DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') + USER_CONFIG = os.path.expanduser(r'~\.pep8') else: - DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or - os.path.expanduser('~/.config'), 'pep8') + USER_CONFIG = os.path.join( + os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), + 'pep8' + ) except ImportError: - DEFAULT_CONFIG = None + USER_CONFIG = None PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8') TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') @@ -1735,13 +1737,11 @@ class StyleGuide(object): # build options from the command line self.checker_class = kwargs.pop('checker_class', Checker) parse_argv = kwargs.pop('parse_argv', False) - config_file = kwargs.pop('config_file', None) parser = kwargs.pop('parser', None) # build options from dict options_dict = dict(*args, **kwargs) arglist = None if parse_argv else options_dict.get('paths', None) - options, self.paths = process_options( - arglist, parse_argv, config_file, parser) + options, self.paths = process_options(arglist, parse_argv, parser) if options_dict: options.__dict__.update(options_dict) if 'paths' in options_dict: @@ -1928,17 +1928,22 @@ def read_config(options, args, arglist, parser): """Read both user configuration and local configuration.""" config = RawConfigParser() - user_conf = options.config - if user_conf and os.path.isfile(user_conf): - if options.verbose: - print('user configuration: %s' % user_conf) - config.read(user_conf) + cli_conf = options.config + if cli_conf and os.path.isfile(cli_conf): + if options.verbose: + print('cli configuration: %s' % cli_conf) + config.read(cli_conf) else: + if USER_CONFIG and os.path.isfile(USER_CONFIG): + if options.verbose: + print('user configuration: %s' % USER_CONFIG) + config.read(USER_CONFIG) + local_dir = os.curdir parent = tail = args and os.path.abspath(os.path.commonprefix(args)) while tail: - if config.read([os.path.join(parent, fn) for fn in PROJECT_CONFIG]): + if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): local_dir = parent if options.verbose: print('local configuration: in %s' % parent) @@ -1979,21 +1984,18 @@ def read_config(options, args, arglist, parser): return options -def process_options(arglist=None, parse_argv=False, config_file=None, - parser=None): +def process_options(arglist=None, parse_argv=False, parser=None): """Process options passed either via arglist or via command line args.""" if not parser: parser = get_parser() if not parser.has_option('--config'): - if config_file is True: - config_file = DEFAULT_CONFIG group = parser.add_option_group("Configuration", description=( "The project options are read from the [%s] section of the " "tox.ini file or the setup.cfg file located in any parent folder " "of the path(s) being processed. Allowed options are: %s." % (parser.prog, ', '.join(parser.config_options)))) - group.add_option('--config', metavar='path', default=config_file, - help="user config file location (default: %default)") + group.add_option('--config', metavar='path', default=None, + help="user config file location") # Don't read the command line if the module is used as a library. if not arglist and not parse_argv: arglist = [] -- cgit v1.2.1 From 9e396b064011a02c05587d609270a193f3e9beb9 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 5 Feb 2015 22:58:10 -0800 Subject: Update configuration documentation; issue #368 / #369 --- pep8.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 12f7afd..41a277c 100755 --- a/pep8.py +++ b/pep8.py @@ -1925,7 +1925,15 @@ def get_parser(prog='pep8', version=__version__): def read_config(options, args, arglist, parser): - """Read both user configuration and local configuration.""" + """Read and parse configurations + + If a config file is specified on the command line with the "--config" + option, then only it is used for configuration. + + Otherwise, the user configuration (~/.config/pep8) and any local + configurations in the current directory or above will be merged together + (in that order) using the read method of ConfigParser. + """ config = RawConfigParser() cli_conf = options.config -- cgit v1.2.1 From 19d9a82df99f8238bc36d0744a2d77fc22a6cb4d Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 5 Feb 2015 22:58:50 -0800 Subject: Update changelog; issue #368 / #369 --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 1caba39..78d0c49 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -38,6 +38,9 @@ Changes: * Allow trailing backslash if in an inline comment. (Issue #374) +* If ``--config`` is used, only that configuration is processed. Otherwise, + merge the user and local configurations are merged. (Issue #368 / #369) + Bug fixes: * Don't crash if Checker.build_tokens_line() returns None. (Issue #306) -- cgit v1.2.1 From 2d8a9077caa01e7bdd415344aba51cc73a5ac9fe Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 6 Feb 2015 18:49:09 -0800 Subject: Release version 1.6 --- CHANGES.txt | 4 ++-- pep8.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 78d0c49..35c3259 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,8 +2,8 @@ Changelog ========= -1.x (unreleased) ----------------- +1.6.0 (2015-02-06) +------------------ News: diff --git a/pep8.py b/pep8.py index 41a277c..f5efd40 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.0a0' +__version__ = '1.6.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From 30b6be3b06775b5a9229e3a899a9f673a12a595f Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 6 Feb 2015 19:26:29 -0800 Subject: Start work on next version --- CHANGES.txt | 4 ++++ pep8.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 35c3259..6482343 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,10 @@ Changelog ========= +1.6.x (unreleased) +------------------ + + 1.6.0 (2015-02-06) ------------------ diff --git a/pep8.py b/pep8.py index f5efd40..a583836 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.0' +__version__ = '1.6.1a0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From 6086f61c90c38b5ceb0bf1f473a382fb36f664b1 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 7 Feb 2015 12:25:16 -0800 Subject: Fix undefined variable issue; #377 --- CHANGES.txt | 3 +++ pep8.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 6482343..76cf1c8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,9 @@ Changelog 1.6.x (unreleased) ------------------ +Bugs: + +* Exception thrown due to unassigned ``local_dir`` variable. (Issue #377) 1.6.0 (2015-02-06) ------------------ diff --git a/pep8.py b/pep8.py index a583836..e58662f 100755 --- a/pep8.py +++ b/pep8.py @@ -1938,6 +1938,8 @@ def read_config(options, args, arglist, parser): cli_conf = options.config + local_dir = os.curdir + if cli_conf and os.path.isfile(cli_conf): if options.verbose: print('cli configuration: %s' % cli_conf) @@ -1948,7 +1950,6 @@ def read_config(options, args, arglist, parser): print('user configuration: %s' % USER_CONFIG) config.read(USER_CONFIG) - local_dir = os.curdir parent = tail = args and os.path.abspath(os.path.commonprefix(args)) while tail: if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): -- cgit v1.2.1 From c8e21b57496454e0552970f1b2468c2b58c4e718 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 7 Feb 2015 16:53:23 -0800 Subject: Fixed #287 -- 'variable is referenced before assignment' error Thanks to @dreadatour for the original pull request. --- pep8.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pep8.py b/pep8.py index b0a99ce..dad873e 100755 --- a/pep8.py +++ b/pep8.py @@ -435,6 +435,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, indent_chances = {} last_indent = tokens[0][2] visual_indent = None + last_token_multiline = False # for each depth, memorize the visual indent column indent = [last_indent[1]] if verbose >= 3: @@ -978,6 +979,7 @@ def explicit_line_join(logical_line, tokens): """ prev_start = prev_end = parens = 0 comment = False + backslash = None for token_type, text, start, end, line in tokens: if token_type == tokenize.COMMENT: comment = True -- cgit v1.2.1 From 390ac70dfecbec846be10e438d4d6fd3b61efa63 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 7 Feb 2015 16:57:03 -0800 Subject: Updated changelog --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 76cf1c8..5fae005 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,10 @@ Changelog 1.6.x (unreleased) ------------------ +Changes: + +* Assign variables before referenced. (Issue #287) + Bugs: * Exception thrown due to unassigned ``local_dir`` variable. (Issue #377) -- cgit v1.2.1 From 1f4227b3735333d7a2fdb197eb049850961e3977 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 8 Feb 2015 13:31:33 -0800 Subject: Release version 1.6.1 --- CHANGES.txt | 2 +- pep8.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5fae005..cd51ac2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,7 +2,7 @@ Changelog ========= -1.6.x (unreleased) +1.6.1 (2015-02-08) ------------------ Changes: diff --git a/pep8.py b/pep8.py index dad873e..bc6a659 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.1a0' +__version__ = '1.6.1' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From d2ffc129ecb07a6ddba81f0770910cd27b563411 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 8 Feb 2015 15:04:24 -0800 Subject: Update changelog --- CHANGES.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index cd51ac2..0c8fd7e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,14 @@ Changelog ========= +1.6.x (unreleased) +------------------ + +Changes: + +* Added check for breaking around a binary operator. (Issue #197, Pull #305) + + 1.6.1 (2015-02-08) ------------------ @@ -13,6 +21,7 @@ Bugs: * Exception thrown due to unassigned ``local_dir`` variable. (Issue #377) + 1.6.0 (2015-02-06) ------------------ -- cgit v1.2.1 From 8ca030e2d8f6d377631bae69a18307fb2d051049 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 14 Feb 2015 15:00:31 -0800 Subject: Bump version number --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index d80b313..7b524c5 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.1' +__version__ = '1.6.2a0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From f25423363eb83ef69fe61cb48829128a0e0aead5 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 14 Feb 2015 15:03:07 -0800 Subject: Fixed regression which caused pep8 to not be able to pass in config file; issue #380 --- pep8.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pep8.py b/pep8.py index 7b524c5..474feb1 100755 --- a/pep8.py +++ b/pep8.py @@ -1779,11 +1779,13 @@ class StyleGuide(object): # build options from the command line self.checker_class = kwargs.pop('checker_class', Checker) parse_argv = kwargs.pop('parse_argv', False) + config_file = kwargs.pop('config_file', False) parser = kwargs.pop('parser', None) # build options from dict options_dict = dict(*args, **kwargs) arglist = None if parse_argv else options_dict.get('paths', None) - options, self.paths = process_options(arglist, parse_argv, parser) + options, self.paths = process_options( + arglist, parse_argv, config_file, parser) if options_dict: options.__dict__.update(options_dict) if 'paths' in options_dict: @@ -2035,8 +2037,13 @@ def read_config(options, args, arglist, parser): return options -def process_options(arglist=None, parse_argv=False, parser=None): - """Process options passed either via arglist or via command line args.""" +def process_options(arglist=None, parse_argv=False, config_file=None, + parser=None): + """Process options passed either via arglist or via command line args. + + Passing in the ``config_file`` parameter allows other tools, such as flake8 + to specify their own options to be processed in pep8. + """ if not parser: parser = get_parser() if not parser.has_option('--config'): @@ -2045,7 +2052,7 @@ def process_options(arglist=None, parse_argv=False, parser=None): "tox.ini file or the setup.cfg file located in any parent folder " "of the path(s) being processed. Allowed options are: %s." % (parser.prog, ', '.join(parser.config_options)))) - group.add_option('--config', metavar='path', default=None, + group.add_option('--config', metavar='path', default=config_file, help="user config file location") # Don't read the command line if the module is used as a library. if not arglist and not parse_argv: -- cgit v1.2.1 From 3bbaa230c4872029999fde25215c6eb6c038f594 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 15 Feb 2015 13:53:24 -0800 Subject: Fixed tests from previous commit --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 474feb1..3037edd 100755 --- a/pep8.py +++ b/pep8.py @@ -2098,7 +2098,7 @@ def _main(): except AttributeError: pass # not supported on Windows - pep8style = StyleGuide(parse_argv=True, config_file=True) + pep8style = StyleGuide(parse_argv=True) options = pep8style.options if options.doctest or options.testsuite: from testsuite.support import run_tests -- cgit v1.2.1 From 2f07c7b64e6c65b277d200f2d544607e3dd8e0ea Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 15 Feb 2015 14:05:02 -0800 Subject: Update changelog for #380 --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 0c8fd7e..04cd46c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,6 +9,10 @@ Changes: * Added check for breaking around a binary operator. (Issue #197, Pull #305) +Bugs: + +* Restored config_file parameter in process_options(). (Issue #380) + 1.6.1 (2015-02-08) ------------------ -- cgit v1.2.1 From dcfdfda78527392433a78d8ed68b6d5a78cdf6c2 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 15 Feb 2015 14:06:23 -0800 Subject: Release version 1.6.2 --- CHANGES.txt | 2 +- pep8.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 04cd46c..dc22f24 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,7 +2,7 @@ Changelog ========= -1.6.x (unreleased) +1.6.2 (2015-02-15) ------------------ Changes: diff --git a/pep8.py b/pep8.py index 3037edd..d7907e5 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.2a0' +__version__ = '1.6.2' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From 18b12f0eb143b501389e7a65bb2f84ea9bc18067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Czy=C5=BCykowski?= Date: Thu, 19 Feb 2015 15:59:27 +0100 Subject: Fix problem with treating ~ operator as binary. --- pep8.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index d7907e5..bca7985 100755 --- a/pep8.py +++ b/pep8.py @@ -1017,13 +1017,14 @@ def break_around_binary_operator(logical_line, tokens): Okay: x = '''\n''' + '' Okay: foo(x,\n -y) Okay: foo(x, # comment\n -y) + Okay: var = (1 &\n ~2) """ def is_binary_operator(token_type, text): # The % character is strictly speaking a binary operator, but the # common usage seems to be to put it next to the format parameters, # after a line break. return ((token_type == tokenize.OP or text in ['and', 'or']) and - text not in "()[]{},:.;@=%") + text not in "()[]{},:.;@=%~") line_break = False unary_context = True -- cgit v1.2.1 From 6fb3c0c3f70fdedb79b63d6edfef1e6af1ed38aa Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 12 Mar 2015 23:58:15 -0700 Subject: Bump version post release --- CHANGES.txt | 3 +++ pep8.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index dc22f24..85eb043 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,9 @@ Changelog ========= +1.6.x (unreleased) +------------------ + 1.6.2 (2015-02-15) ------------------ diff --git a/pep8.py b/pep8.py index d7907e5..c7f34e4 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.2' +__version__ = '1.6.3a0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From f6fe8ac6e29a0395ce53416f98a10a8720bd43d2 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Fri, 13 Mar 2015 00:13:30 -0700 Subject: Updated licensing dates to 2015 --- LICENSE | 1 + docs/conf.py | 2 +- pep8.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 70242b8..2ec4d39 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ Copyright © 2006-2009 Johann C. Rocholl Copyright © 2009-2014 Florent Xicluna +Copyright © 2014-2015 Ian Lee Licensed under the terms of the Expat License diff --git a/docs/conf.py b/docs/conf.py index a77ac83..78bd344 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,7 +46,7 @@ master_doc = 'index' # General information about the project. project = u'pep8' authors = u'Johann C. Rocholl, Florent Xicluna, Ian Lee' -copyright = u'2006-2014, %s' % (authors) +copyright = u'2006-2015, %s' % (authors) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/pep8.py b/pep8.py index c7f34e4..9f40381 100755 --- a/pep8.py +++ b/pep8.py @@ -2,7 +2,7 @@ # pep8.py - Check Python source code formatting, according to PEP 8 # Copyright (C) 2006-2009 Johann C. Rocholl # Copyright (C) 2009-2014 Florent Xicluna -# Copyright (C) 2014 Ian Lee +# Copyright (C) 2014-2015 Ian Lee # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files -- cgit v1.2.1 From 435d1cbf995a659a82d1d4b42d25e3459556ef21 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 17 Mar 2015 21:52:23 -0700 Subject: Reverted fix for #368 which had unintended repurcussions in flake8 and other places. This fix reverts to the parsing of user config (~/.config/pep8), then local directory config files, and finally overrides with cli options as was the behavior back in 1.5.7 --- CHANGES.txt | 6 ++++++ pep8.py | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 85eb043..5499abd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,12 @@ Changelog 1.6.x (unreleased) ------------------ +Changes: + +* Reverted the fix in #368, "options passed on command line are only ones + accepted" feature. This has many unintended consequences in pep8 and flake8 + and needs to be reworked when I have more time. + 1.6.2 (2015-02-15) ------------------ diff --git a/pep8.py b/pep8.py index 9f40381..4d993da 100755 --- a/pep8.py +++ b/pep8.py @@ -1984,24 +1984,24 @@ def read_config(options, args, arglist, parser): local_dir = os.curdir + if USER_CONFIG and os.path.isfile(USER_CONFIG): + if options.verbose: + print('user configuration: %s' % USER_CONFIG) + config.read(USER_CONFIG) + + parent = tail = args and os.path.abspath(os.path.commonprefix(args)) + while tail: + if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): + local_dir = parent + if options.verbose: + print('local configuration: in %s' % parent) + break + (parent, tail) = os.path.split(parent) + if cli_conf and os.path.isfile(cli_conf): if options.verbose: print('cli configuration: %s' % cli_conf) config.read(cli_conf) - else: - if USER_CONFIG and os.path.isfile(USER_CONFIG): - if options.verbose: - print('user configuration: %s' % USER_CONFIG) - config.read(USER_CONFIG) - - parent = tail = args and os.path.abspath(os.path.commonprefix(args)) - while tail: - if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): - local_dir = parent - if options.verbose: - print('local configuration: in %s' % parent) - break - (parent, tail) = os.path.split(parent) pep8_section = parser.prog if config.has_section(pep8_section): -- cgit v1.2.1 From d06b015ed97aa54190c0f791393b6ddf39f3dea3 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 4 Jul 2015 08:28:32 -0500 Subject: Add W503 to the error code table in the docs Closes #414 --- docs/intro.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 006187b..141463c 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -392,6 +392,11 @@ This is the current list of error and warning codes: | W391 | blank line at end of file | +----------+----------------------------------------------------------------------+ +----------+----------------------------------------------------------------------+ +| **W5** | *Line break warning* | ++----------+----------------------------------------------------------------------+ +| W503 | line break occurred before a binary operator | ++----------+----------------------------------------------------------------------+ ++----------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | +----------+----------------------------------------------------------------------+ | W601 | .has_key() is deprecated, use 'in' | -- cgit v1.2.1 From 189c0ccd6e492eb428173acdb170dbc33c317568 Mon Sep 17 00:00:00 2001 From: Steven Myint Date: Sat, 11 Jul 2015 09:33:14 -0700 Subject: Point to new GitHub URL I guess this repository got moved at some point? --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index d842f05..4204b05 100644 --- a/README.rst +++ b/README.rst @@ -78,8 +78,8 @@ Or you can display how often each error was found:: Links ----- -.. image:: https://api.travis-ci.org/jcrocholl/pep8.png?branch=master - :target: https://travis-ci.org/jcrocholl/pep8 +.. image:: https://api.travis-ci.org/PyCQA/pep8.png?branch=master + :target: https://travis-ci.org/PyCQA/pep8 :alt: Build status .. image:: https://pypip.in/wheel/pep8/badge.png?branch=master @@ -88,4 +88,4 @@ Links * `Read the documentation `_ -* `Fork me on GitHub `_ +* `Fork me on GitHub `_ -- cgit v1.2.1 From 1a90865abc3de27c3a9b8375fd8c73b6d2f07b64 Mon Sep 17 00:00:00 2001 From: Steven Myint Date: Tue, 14 Jul 2015 07:46:40 -0700 Subject: Support Python 3.5 This involves fixing a test and avoiding a function deprecated since Python 3.0. https://docs.python.org/dev/library/inspect.html#inspect.getargspec --- pep8.py | 9 +++++++-- testsuite/test_api.py | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pep8.py b/pep8.py index 4d993da..c67e9cd 100755 --- a/pep8.py +++ b/pep8.py @@ -1328,7 +1328,12 @@ def register_check(check, codes=None): codes = ERRORCODE_REGEX.findall(check.__doc__ or '') _add_check(check, args[0], codes, args) elif inspect.isclass(check): - if inspect.getargspec(check.__init__)[0][:2] == ['self', 'tree']: + if sys.version_info[0] >= 3: + parameters = list(inspect.signature(check.__init__).parameters) + else: + parameters = inspect.getargspec(check.__init__)[0] + + if parameters[:2] == ['self', 'tree']: _add_check(check, 'tree', codes, None) @@ -1504,7 +1509,7 @@ class Checker(object): """Build the file's AST and run all AST checks.""" try: tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) - except (SyntaxError, TypeError): + except (ValueError, SyntaxError, TypeError): return self.report_invalid_syntax() for name, cls, __ in self._ast_checks: checker = cls(tree, self.filename) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 341fb34..1cb0d4b 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -339,6 +339,9 @@ class APITestCase(unittest.TestCase): if 'SyntaxError' in stdout: # PyPy 2.2 returns a SyntaxError expected = "stdin:1:2: E901 SyntaxError" + elif 'ValueError' in stdout: + # Python 3.5. + expected = "stdin:1:1: E901 ValueError" else: expected = "stdin:1:1: E901 TypeError" self.assertTrue(stdout.startswith(expected), -- cgit v1.2.1 From 36b5d1999e141f8562eec35ed4583042515ec580 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 14 Jul 2015 11:45:32 -0500 Subject: Remove pypy* from allowed failures The pypy jobs have not failed in a long time. Keeping them in allowed_failures means that if they start failing, no one will notice them. We also start using "sudo: false" since we are not installing anything with apt and this will speed up build times. --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b188325..4d619e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +sudo: false python: - 2.6 - 2.7 @@ -15,10 +16,6 @@ script: - python pep8.py --statistics pep8.py - python pep8.py --doctest - python setup.py test -matrix: - allow_failures: - - python: pypy - - python: pypy3 notifications: email: -- cgit v1.2.1 From f671be90193ce962065b441179874a3b8e17b3a0 Mon Sep 17 00:00:00 2001 From: Steven Myint Date: Tue, 14 Jul 2015 16:38:19 -0700 Subject: Support older Python 3 --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index c67e9cd..e9bc584 100755 --- a/pep8.py +++ b/pep8.py @@ -1328,7 +1328,7 @@ def register_check(check, codes=None): codes = ERRORCODE_REGEX.findall(check.__doc__ or '') _add_check(check, args[0], codes, args) elif inspect.isclass(check): - if sys.version_info[0] >= 3: + if sys.version_info >= (3, 3): parameters = list(inspect.signature(check.__init__).parameters) else: parameters = inspect.getargspec(check.__init__)[0] -- cgit v1.2.1 From 067a3213dd3533a2b44b4e3bb5d5645e3e5724f8 Mon Sep 17 00:00:00 2001 From: Steven Myint Date: Tue, 14 Jul 2015 16:39:42 -0700 Subject: Test on Python 3.5 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b188325..0e17587 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - 3.2 - 3.3 - 3.4 + - nightly - pypy - pypy3 install: -- cgit v1.2.1 From d48eef071003952ed0f98445dc3dc6bc29618f9c Mon Sep 17 00:00:00 2001 From: Steven Myint Date: Tue, 14 Jul 2015 18:01:03 -0700 Subject: Generalize deprecation avoidance --- pep8.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pep8.py b/pep8.py index e9bc584..34ce07a 100755 --- a/pep8.py +++ b/pep8.py @@ -1314,6 +1314,13 @@ if COMMENT_WITH_NL: _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} +def _get_parameters(function): + if sys.version_info >= (3, 3): + return list(inspect.signature(function).parameters) + else: + return inspect.getargspec(function)[0] + + def register_check(check, codes=None): """Register a new check object.""" def _add_check(check, kind, codes, args): @@ -1322,18 +1329,13 @@ def register_check(check, codes=None): else: _checks[kind][check] = (codes or [''], args) if inspect.isfunction(check): - args = inspect.getargspec(check)[0] + args = _get_parameters(check) if args and args[0] in ('physical_line', 'logical_line'): if codes is None: codes = ERRORCODE_REGEX.findall(check.__doc__ or '') _add_check(check, args[0], codes, args) elif inspect.isclass(check): - if sys.version_info >= (3, 3): - parameters = list(inspect.signature(check.__init__).parameters) - else: - parameters = inspect.getargspec(check.__init__)[0] - - if parameters[:2] == ['self', 'tree']: + if _get_parameters(check.__init__)[:2] == ['self', 'tree']: _add_check(check, 'tree', codes, None) -- cgit v1.2.1 From 8aac6b5cc3fc2fba33c5a9108feae97e7907ce9b Mon Sep 17 00:00:00 2001 From: Kristian Glass Date: Sat, 22 Aug 2015 11:32:27 +0100 Subject: Test behaviour regarding multi-line ignore parsing in config_file At time of writing, these tests fail. If this is later fixed, someone may wish to remove this warning! --- testsuite/test_all.py | 3 ++- testsuite/test_parser.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 testsuite/test_parser.py diff --git a/testsuite/test_all.py b/testsuite/test_all.py index 50e2cb9..bfb61d5 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -46,11 +46,12 @@ class Pep8TestCase(unittest.TestCase): def suite(): - from testsuite import test_api, test_shell, test_util + from testsuite import test_api, test_parser, test_shell, test_util suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(Pep8TestCase)) suite.addTest(unittest.makeSuite(test_api.APITestCase)) + suite.addTest(unittest.makeSuite(test_parser.ParserTestCase)) suite.addTest(unittest.makeSuite(test_shell.ShellTestCase)) suite.addTest(unittest.makeSuite(test_util.UtilTestCase)) return suite diff --git a/testsuite/test_parser.py b/testsuite/test_parser.py new file mode 100644 index 0000000..1d9e1ac --- /dev/null +++ b/testsuite/test_parser.py @@ -0,0 +1,61 @@ +import os +import tempfile +import unittest + +import pep8 + + +def _process_file(contents): + with tempfile.NamedTemporaryFile(delete=False) as f: + f.write(contents) + + options, args = pep8.process_options(config_file=f.name) + os.remove(f.name) + + return options, args + + +class ParserTestCase(unittest.TestCase): + + def test_vanilla_ignore_parsing(self): + contents = b""" +[pep8] +ignore = E226,E24 + """ + options, args = _process_file(contents) + + self.assertEqual(options.ignore, ["E226", "E24"]) + + def test_multiline_ignore_parsing(self): + contents = b""" +[pep8] +ignore = + E226, + E24 + """ + + options, args = _process_file(contents) + + self.assertEqual(options.ignore, ["E226", "E24"]) + + def test_trailing_comma_ignore_parsing(self): + contents = b""" +[pep8] +ignore = E226, + """ + + options, args = _process_file(contents) + + self.assertEqual(options.ignore, ["E226"]) + + def test_multiline_trailing_comma_ignore_parsing(self): + contents = b""" +[pep8] +ignore = + E226, + E24, + """ + + options, args = _process_file(contents) + + self.assertEqual(options.ignore, ["E226", "E24"]) -- cgit v1.2.1 From e7d64d3062421707a1c896b25cc5134922bcd9ee Mon Sep 17 00:00:00 2001 From: Kristian Glass Date: Sat, 22 Aug 2015 17:32:15 +0100 Subject: Improved parsing of multiline option values (including trailing commas) --- pep8.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/pep8.py b/pep8.py index 34ce07a..206e339 100755 --- a/pep8.py +++ b/pep8.py @@ -2081,10 +2081,10 @@ def process_options(arglist=None, parse_argv=False, config_file=None, options = read_config(options, args, arglist, parser) options.reporter = parse_argv and options.quiet == 1 and FileReport - options.filename = options.filename and options.filename.split(',') + options.filename = _parse_multi_options(options.filename.split(',')) options.exclude = normalize_paths(options.exclude) - options.select = options.select and options.select.split(',') - options.ignore = options.ignore and options.ignore.split(',') + options.select = _parse_multi_options(options.select.split(',')) + options.ignore = _parse_multi_options(options.ignore.split(',')) if options.diff: options.reporter = DiffReport @@ -2095,6 +2095,22 @@ def process_options(arglist=None, parse_argv=False, config_file=None, return options, args +def _parse_multi_options(options): + r"""Split and strip and discard empties. + + Turns the following: + + A, + B, + + into ["A", "B"] + """ + if options: + return [o.strip() for o in options if o.strip()] + else: + return options + + def _main(): """Parse options and run checks on Python source.""" import signal -- cgit v1.2.1 From 145e2a87a987178679e39eebf2230e68e200db38 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 13 Jul 2015 15:29:27 -0500 Subject: Update all documentation and links to use PyCQA --- docs/advanced.rst | 2 +- docs/developer.rst | 8 ++++---- docs/index.rst | 2 +- docs/intro.rst | 2 +- pep8.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 2bce2e0..1c3a7e1 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -42,7 +42,7 @@ Skip file header ---------------- Another example is related to the `feature request #143 -`_: skip a number of lines +`_: skip a number of lines at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: diff --git a/docs/developer.rst b/docs/developer.rst index 4725acd..9db13b1 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -11,13 +11,13 @@ Source code The source code is currently `available on GitHub`_ under the terms and conditions of the :ref:`Expat license `. Fork away! -* `Source code `_ and - `issue tracker `_ on GitHub. -* `Continuous tests `_ against Python +* `Source code `_ and + `issue tracker `_ on GitHub. +* `Continuous tests `_ against Python 2.6 through 3.4 and PyPy, on `Travis-CI platform `_. -.. _available on GitHub: https://github.com/jcrocholl/pep8 +.. _available on GitHub: https://github.com/pycqa/pep8 Contribute diff --git a/docs/index.rst b/docs/index.rst index 5500e0d..5e4a4c5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,7 +22,7 @@ Contents: developer * Online documentation: http://pep8.readthedocs.org/ -* Source code and issue tracker: https://github.com/jcrocholl/pep8 +* Source code and issue tracker: https://github.com/pycqa/pep8 Indices and tables diff --git a/docs/intro.rst b/docs/intro.rst index 141463c..fc2e8b5 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -435,6 +435,6 @@ The `flake8 checker `_ is a wrapper around ``pep8`` and similar tools. It supports plugins. Other tools which use ``pep8`` are referenced in the Wiki: `list of related -tools `_. +tools `_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ diff --git a/pep8.py b/pep8.py index 206e339..19c563b 100755 --- a/pep8.py +++ b/pep8.py @@ -31,7 +31,7 @@ For usage and a list of options, try this: $ python pep8.py -h This program and its regression test suite live here: -http://github.com/jcrocholl/pep8 +http://github.com/pycqa/pep8 Groups of errors and warnings: E errors -- cgit v1.2.1 From a9dec314063c5de09758e8c5f124a7b62c639755 Mon Sep 17 00:00:00 2001 From: nixscripter Date: Tue, 23 Jun 2015 23:33:41 -0500 Subject: Reworded --diff flag help string to improve clarity (which created misunderstandings in Issue #407) --- pep8.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep8.py b/pep8.py index 19c563b..fdfb71f 100755 --- a/pep8.py +++ b/pep8.py @@ -1962,8 +1962,8 @@ def get_parser(prog='pep8', version=__version__): parser.add_option('--format', metavar='format', default='default', help="set the error format [default|pylint|]") parser.add_option('--diff', action='store_true', - help="report only lines changed according to the " - "unified diff received on STDIN") + help="analyze changes only within line number ranges of " + "the unified diff received on STDIN") group = parser.add_option_group("Testing Options") if os.path.exists(TESTSUITE_PATH): group.add_option('--testsuite', metavar='dir', -- cgit v1.2.1 From 51b153475e8303410b19c5e064a56b977cdb7200 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 7 Sep 2015 14:15:33 -0500 Subject: Use report instead of analyze in the help text Closes #409 --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index fdfb71f..0bea29a 100755 --- a/pep8.py +++ b/pep8.py @@ -1962,7 +1962,7 @@ def get_parser(prog='pep8', version=__version__): parser.add_option('--format', metavar='format', default='default', help="set the error format [default|pylint|]") parser.add_option('--diff', action='store_true', - help="analyze changes only within line number ranges of " + help="report changes only within line number ranges in " "the unified diff received on STDIN") group = parser.add_option_group("Testing Options") if os.path.exists(TESTSUITE_PATH): -- cgit v1.2.1 From 88f89ad09f096e0cf42c815330481f402cb7a2cc Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 8 Sep 2015 13:59:39 -0700 Subject: Updated default html_theme per Sphinx updates --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 78bd344..d7d9291 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -99,7 +99,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'alabaster' # 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 -- cgit v1.2.1 From 31c0215a03264c8c3f4ae8bd59d46c091b03451f Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 8 Sep 2015 17:53:47 -0700 Subject: Escape inline asterisk --- docs/intro.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index fc2e8b5..f2c12f4 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -237,7 +237,7 @@ This is the current list of error and warning codes: | E116 | unexpected indentation (comment) | +----------+----------------------------------------------------------------------+ +----------+----------------------------------------------------------------------+ -| E121 (*^)| continuation line under-indented for hanging indent | +| E121 (\*^)| continuation line under-indented for hanging indent | +----------+----------------------------------------------------------------------+ | E122 (^) | continuation line missing indentation or outdented | +----------+----------------------------------------------------------------------+ @@ -247,7 +247,7 @@ This is the current list of error and warning codes: +----------+----------------------------------------------------------------------+ | E125 (^) | continuation line with same indent as next logical line | +----------+----------------------------------------------------------------------+ -| E126 (*^)| continuation line over-indented for hanging indent | +| E126 (\*^)| continuation line over-indented for hanging indent | +----------+----------------------------------------------------------------------+ | E127 (^) | continuation line over-indented for visual indent | +----------+----------------------------------------------------------------------+ -- cgit v1.2.1 From 37648d3fb74db1fd952c7cb101eaf7224055ce1b Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 9 Sep 2015 07:52:09 -0700 Subject: Fixed table formatting typo --- docs/intro.rst | 380 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 190 insertions(+), 190 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index f2c12f4..3a605eb 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -217,196 +217,196 @@ Error codes This is the current list of error and warning codes: -+----------+----------------------------------------------------------------------+ -| code | sample message | -+==========+======================================================================+ -| **E1** | *Indentation* | -+----------+----------------------------------------------------------------------+ -| E101 | indentation contains mixed spaces and tabs | -+----------+----------------------------------------------------------------------+ -| E111 | indentation is not a multiple of four | -+----------+----------------------------------------------------------------------+ -| E112 | expected an indented block | -+----------+----------------------------------------------------------------------+ -| E113 | unexpected indentation | -+----------+----------------------------------------------------------------------+ -| E114 | indentation is not a multiple of four (comment) | -+----------+----------------------------------------------------------------------+ -| E115 | expected an indented block (comment) | -+----------+----------------------------------------------------------------------+ -| E116 | unexpected indentation (comment) | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E121 (\*^)| continuation line under-indented for hanging indent | -+----------+----------------------------------------------------------------------+ -| E122 (^) | continuation line missing indentation or outdented | -+----------+----------------------------------------------------------------------+ -| E123 (*) | closing bracket does not match indentation of opening bracket's line | -+----------+----------------------------------------------------------------------+ -| E124 (^) | closing bracket does not match visual indentation | -+----------+----------------------------------------------------------------------+ -| E125 (^) | continuation line with same indent as next logical line | -+----------+----------------------------------------------------------------------+ -| E126 (\*^)| continuation line over-indented for hanging indent | -+----------+----------------------------------------------------------------------+ -| E127 (^) | continuation line over-indented for visual indent | -+----------+----------------------------------------------------------------------+ -| E128 (^) | continuation line under-indented for visual indent | -+----------+----------------------------------------------------------------------+ -| E129 (^) | visually indented line with same indent as next logical line | -+----------+----------------------------------------------------------------------+ -| E131 (^) | continuation line unaligned for hanging indent | -+----------+----------------------------------------------------------------------+ -| E133 (*) | closing bracket is missing indentation | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **E2** | *Whitespace* | -+----------+----------------------------------------------------------------------+ -| E201 | whitespace after '(' | -+----------+----------------------------------------------------------------------+ -| E202 | whitespace before ')' | -+----------+----------------------------------------------------------------------+ -| E203 | whitespace before ':' | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E211 | whitespace before '(' | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E221 | multiple spaces before operator | -+----------+----------------------------------------------------------------------+ -| E222 | multiple spaces after operator | -+----------+----------------------------------------------------------------------+ -| E223 | tab before operator | -+----------+----------------------------------------------------------------------+ -| E224 | tab after operator | -+----------+----------------------------------------------------------------------+ -| E225 | missing whitespace around operator | -+----------+----------------------------------------------------------------------+ -| E226 (*) | missing whitespace around arithmetic operator | -+----------+----------------------------------------------------------------------+ -| E227 | missing whitespace around bitwise or shift operator | -+----------+----------------------------------------------------------------------+ -| E228 | missing whitespace around modulo operator | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E231 | missing whitespace after ',', ';', or ':' | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E241 (*) | multiple spaces after ',' | -+----------+----------------------------------------------------------------------+ -| E242 (*) | tab after ',' | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E251 | unexpected spaces around keyword / parameter equals | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E261 | at least two spaces before inline comment | -+----------+----------------------------------------------------------------------+ -| E262 | inline comment should start with '# ' | -+----------+----------------------------------------------------------------------+ -| E265 | block comment should start with '# ' | -+----------+----------------------------------------------------------------------+ -| E266 | too many leading '#' for block comment | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| E271 | multiple spaces after keyword | -+----------+----------------------------------------------------------------------+ -| E272 | multiple spaces before keyword | -+----------+----------------------------------------------------------------------+ -| E273 | tab after keyword | -+----------+----------------------------------------------------------------------+ -| E274 | tab before keyword | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **E3** | *Blank line* | -+----------+----------------------------------------------------------------------+ -| E301 | expected 1 blank line, found 0 | -+----------+----------------------------------------------------------------------+ -| E302 | expected 2 blank lines, found 0 | -+----------+----------------------------------------------------------------------+ -| E303 | too many blank lines (3) | -+----------+----------------------------------------------------------------------+ -| E304 | blank lines found after function decorator | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **E4** | *Import* | -+----------+----------------------------------------------------------------------+ -| E401 | multiple imports on one line | -+----------+----------------------------------------------------------------------+ -| E402 | module level import not at top of file | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **E5** | *Line length* | -+----------+----------------------------------------------------------------------+ -| E501 (^) | line too long (82 > 79 characters) | -+----------+----------------------------------------------------------------------+ -| E502 | the backslash is redundant between brackets | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **E7** | *Statement* | -+----------+----------------------------------------------------------------------+ -| E701 | multiple statements on one line (colon) | -+----------+----------------------------------------------------------------------+ -| E702 | multiple statements on one line (semicolon) | -+----------+----------------------------------------------------------------------+ -| E703 | statement ends with a semicolon | -+----------+----------------------------------------------------------------------+ -| E704 (*) | multiple statements on one line (def) | -+----------+----------------------------------------------------------------------+ -| E711 (^) | comparison to None should be 'if cond is None:' | -+----------+----------------------------------------------------------------------+ -| E712 (^) | comparison to True should be 'if cond is True:' or 'if cond:' | -+----------+----------------------------------------------------------------------+ -| E713 | test for membership should be 'not in' | -+----------+----------------------------------------------------------------------+ -| E714 | test for object identity should be 'is not' | -+----------+----------------------------------------------------------------------+ -| E721 (^) | do not compare types, use 'isinstance()' | -+----------+----------------------------------------------------------------------+ -| E731 | do not assign a lambda expression, use a def | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **E9** | *Runtime* | -+----------+----------------------------------------------------------------------+ -| E901 | SyntaxError or IndentationError | -+----------+----------------------------------------------------------------------+ -| E902 | IOError | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **W1** | *Indentation warning* | -+----------+----------------------------------------------------------------------+ -| W191 | indentation contains tabs | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **W2** | *Whitespace warning* | -+----------+----------------------------------------------------------------------+ -| W291 | trailing whitespace | -+----------+----------------------------------------------------------------------+ -| W292 | no newline at end of file | -+----------+----------------------------------------------------------------------+ -| W293 | blank line contains whitespace | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **W3** | *Blank line warning* | -+----------+----------------------------------------------------------------------+ -| W391 | blank line at end of file | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **W5** | *Line break warning* | -+----------+----------------------------------------------------------------------+ -| W503 | line break occurred before a binary operator | -+----------+----------------------------------------------------------------------+ -+----------+----------------------------------------------------------------------+ -| **W6** | *Deprecation warning* | -+----------+----------------------------------------------------------------------+ -| W601 | .has_key() is deprecated, use 'in' | -+----------+----------------------------------------------------------------------+ -| W602 | deprecated form of raising exception | -+----------+----------------------------------------------------------------------+ -| W603 | '<>' is deprecated, use '!=' | -+----------+----------------------------------------------------------------------+ -| W604 | backticks are deprecated, use 'repr()' | -+----------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| code | sample message | ++============+======================================================================+ +| **E1** | *Indentation* | ++------------+----------------------------------------------------------------------+ +| E101 | indentation contains mixed spaces and tabs | ++------------+----------------------------------------------------------------------+ +| E111 | indentation is not a multiple of four | ++------------+----------------------------------------------------------------------+ +| E112 | expected an indented block | ++------------+----------------------------------------------------------------------+ +| E113 | unexpected indentation | ++------------+----------------------------------------------------------------------+ +| E114 | indentation is not a multiple of four (comment) | ++------------+----------------------------------------------------------------------+ +| E115 | expected an indented block (comment) | ++------------+----------------------------------------------------------------------+ +| E116 | unexpected indentation (comment) | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E121 (\*^) | continuation line under-indented for hanging indent | ++------------+----------------------------------------------------------------------+ +| E122 (^) | continuation line missing indentation or outdented | ++------------+----------------------------------------------------------------------+ +| E123 (*) | closing bracket does not match indentation of opening bracket's line | ++------------+----------------------------------------------------------------------+ +| E124 (^) | closing bracket does not match visual indentation | ++------------+----------------------------------------------------------------------+ +| E125 (^) | continuation line with same indent as next logical line | ++------------+----------------------------------------------------------------------+ +| E126 (\*^) | continuation line over-indented for hanging indent | ++------------+----------------------------------------------------------------------+ +| E127 (^) | continuation line over-indented for visual indent | ++------------+----------------------------------------------------------------------+ +| E128 (^) | continuation line under-indented for visual indent | ++------------+----------------------------------------------------------------------+ +| E129 (^) | visually indented line with same indent as next logical line | ++------------+----------------------------------------------------------------------+ +| E131 (^) | continuation line unaligned for hanging indent | ++------------+----------------------------------------------------------------------+ +| E133 (*) | closing bracket is missing indentation | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **E2** | *Whitespace* | ++------------+----------------------------------------------------------------------+ +| E201 | whitespace after '(' | ++------------+----------------------------------------------------------------------+ +| E202 | whitespace before ')' | ++------------+----------------------------------------------------------------------+ +| E203 | whitespace before ':' | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E211 | whitespace before '(' | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E221 | multiple spaces before operator | ++------------+----------------------------------------------------------------------+ +| E222 | multiple spaces after operator | ++------------+----------------------------------------------------------------------+ +| E223 | tab before operator | ++------------+----------------------------------------------------------------------+ +| E224 | tab after operator | ++------------+----------------------------------------------------------------------+ +| E225 | missing whitespace around operator | ++------------+----------------------------------------------------------------------+ +| E226 (*) | missing whitespace around arithmetic operator | ++------------+----------------------------------------------------------------------+ +| E227 | missing whitespace around bitwise or shift operator | ++------------+----------------------------------------------------------------------+ +| E228 | missing whitespace around modulo operator | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E231 | missing whitespace after ',', ';', or ':' | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E241 (*) | multiple spaces after ',' | ++------------+----------------------------------------------------------------------+ +| E242 (*) | tab after ',' | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E251 | unexpected spaces around keyword / parameter equals | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E261 | at least two spaces before inline comment | ++------------+----------------------------------------------------------------------+ +| E262 | inline comment should start with '# ' | ++------------+----------------------------------------------------------------------+ +| E265 | block comment should start with '# ' | ++------------+----------------------------------------------------------------------+ +| E266 | too many leading '#' for block comment | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| E271 | multiple spaces after keyword | ++------------+----------------------------------------------------------------------+ +| E272 | multiple spaces before keyword | ++------------+----------------------------------------------------------------------+ +| E273 | tab after keyword | ++------------+----------------------------------------------------------------------+ +| E274 | tab before keyword | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **E3** | *Blank line* | ++------------+----------------------------------------------------------------------+ +| E301 | expected 1 blank line, found 0 | ++------------+----------------------------------------------------------------------+ +| E302 | expected 2 blank lines, found 0 | ++------------+----------------------------------------------------------------------+ +| E303 | too many blank lines (3) | ++------------+----------------------------------------------------------------------+ +| E304 | blank lines found after function decorator | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **E4** | *Import* | ++------------+----------------------------------------------------------------------+ +| E401 | multiple imports on one line | ++------------+----------------------------------------------------------------------+ +| E402 | module level import not at top of file | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **E5** | *Line length* | ++------------+----------------------------------------------------------------------+ +| E501 (^) | line too long (82 > 79 characters) | ++------------+----------------------------------------------------------------------+ +| E502 | the backslash is redundant between brackets | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **E7** | *Statement* | ++------------+----------------------------------------------------------------------+ +| E701 | multiple statements on one line (colon) | ++------------+----------------------------------------------------------------------+ +| E702 | multiple statements on one line (semicolon) | ++------------+----------------------------------------------------------------------+ +| E703 | statement ends with a semicolon | ++------------+----------------------------------------------------------------------+ +| E704 (*) | multiple statements on one line (def) | ++------------+----------------------------------------------------------------------+ +| E711 (^) | comparison to None should be 'if cond is None:' | ++------------+----------------------------------------------------------------------+ +| E712 (^) | comparison to True should be 'if cond is True:' or 'if cond:' | ++------------+----------------------------------------------------------------------+ +| E713 | test for membership should be 'not in' | ++------------+----------------------------------------------------------------------+ +| E714 | test for object identity should be 'is not' | ++------------+----------------------------------------------------------------------+ +| E721 (^) | do not compare types, use 'isinstance()' | ++------------+----------------------------------------------------------------------+ +| E731 | do not assign a lambda expression, use a def | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **E9** | *Runtime* | ++------------+----------------------------------------------------------------------+ +| E901 | SyntaxError or IndentationError | ++------------+----------------------------------------------------------------------+ +| E902 | IOError | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **W1** | *Indentation warning* | ++------------+----------------------------------------------------------------------+ +| W191 | indentation contains tabs | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **W2** | *Whitespace warning* | ++------------+----------------------------------------------------------------------+ +| W291 | trailing whitespace | ++------------+----------------------------------------------------------------------+ +| W292 | no newline at end of file | ++------------+----------------------------------------------------------------------+ +| W293 | blank line contains whitespace | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **W3** | *Blank line warning* | ++------------+----------------------------------------------------------------------+ +| W391 | blank line at end of file | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **W5** | *Line break warning* | ++------------+----------------------------------------------------------------------+ +| W503 | line break occurred before a binary operator | ++------------+----------------------------------------------------------------------+ ++------------+----------------------------------------------------------------------+ +| **W6** | *Deprecation warning* | ++------------+----------------------------------------------------------------------+ +| W601 | .has_key() is deprecated, use 'in' | ++------------+----------------------------------------------------------------------+ +| W602 | deprecated form of raising exception | ++------------+----------------------------------------------------------------------+ +| W603 | '<>' is deprecated, use '!=' | ++------------+----------------------------------------------------------------------+ +| W604 | backticks are deprecated, use 'repr()' | ++------------+----------------------------------------------------------------------+ **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, -- cgit v1.2.1 From 870509191e75b3914e0d92a08767c6dc492d8ccd Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 9 Sep 2015 08:34:35 -0700 Subject: Fixed issue with mixed theme on RTD --- docs/conf.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index d7d9291..d58549d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -99,7 +99,11 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # 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 -- cgit v1.2.1 From 960a10c920b254fa9db43318061f64f645014cdf Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 10 Sep 2015 12:55:57 -0700 Subject: Added minor visual whitespace improvement --- pep8.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pep8.py b/pep8.py index 0bea29a..45862bd 100755 --- a/pep8.py +++ b/pep8.py @@ -2123,17 +2123,22 @@ def _main(): pep8style = StyleGuide(parse_argv=True) options = pep8style.options + if options.doctest or options.testsuite: from testsuite.support import run_tests report = run_tests(pep8style) else: report = pep8style.check_files() + if options.statistics: report.print_statistics() + if options.benchmark: report.print_benchmark() + if options.testsuite and not options.quiet: report.print_results() + if report.total_errors: if options.count: sys.stderr.write(str(report.total_errors) + '\n') -- cgit v1.2.1 From 15dcce0065bb2d7a2a35a10e1e81571a52b9f154 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 10 Sep 2015 13:13:14 -0700 Subject: Moved splitting into _parse_multi_options function --- pep8.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pep8.py b/pep8.py index 45862bd..e5289d0 100755 --- a/pep8.py +++ b/pep8.py @@ -2081,10 +2081,10 @@ def process_options(arglist=None, parse_argv=False, config_file=None, options = read_config(options, args, arglist, parser) options.reporter = parse_argv and options.quiet == 1 and FileReport - options.filename = _parse_multi_options(options.filename.split(',')) + options.filename = _parse_multi_options(options.filename) options.exclude = normalize_paths(options.exclude) - options.select = _parse_multi_options(options.select.split(',')) - options.ignore = _parse_multi_options(options.ignore.split(',')) + options.select = _parse_multi_options(options.select) + options.ignore = _parse_multi_options(options.ignore) if options.diff: options.reporter = DiffReport @@ -2095,7 +2095,7 @@ def process_options(arglist=None, parse_argv=False, config_file=None, return options, args -def _parse_multi_options(options): +def _parse_multi_options(options, split_token=','): r"""Split and strip and discard empties. Turns the following: @@ -2106,7 +2106,7 @@ def _parse_multi_options(options): into ["A", "B"] """ if options: - return [o.strip() for o in options if o.strip()] + return [o.strip() for o in options.split(split_token) if o.strip()] else: return options -- cgit v1.2.1 From 1dcc2b2eaab4655bc5a34c818ea5fcabbbb4cdab Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 10 Sep 2015 18:55:47 -0700 Subject: Fixed #437 -- Use https instead of http for link --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index e5289d0..884952b 100755 --- a/pep8.py +++ b/pep8.py @@ -31,7 +31,7 @@ For usage and a list of options, try this: $ python pep8.py -h This program and its regression test suite live here: -http://github.com/pycqa/pep8 +https://github.com/pycqa/pep8 Groups of errors and warnings: E errors -- cgit v1.2.1 From dcb1fc098b02f7a0a4eeef356695abc47587f5c2 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Thu, 17 Sep 2015 08:23:40 -0500 Subject: Add python 3.5 support Add a notification for the PyCQA channel --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index b8fabef..3149cbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: - 3.2 - 3.3 - 3.4 + - 3.5 - nightly - pypy - pypy3 @@ -21,3 +22,8 @@ script: notifications: email: - IanLee1521@gmail.com + irc: + channels: + - "irc.freenode.org##python-code-quality" + use_notice: true + skip_join: true -- cgit v1.2.1 From 0d2fe46ddc406758f823c34989a2331bc3a3b372 Mon Sep 17 00:00:00 2001 From: Andrew Tonko Date: Wed, 7 Oct 2015 18:56:10 +0300 Subject: Fix regression in catching E711 (issue #435), add tests --- pep8.py | 2 +- testsuite/E71.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 884952b..c22e63e 100755 --- a/pep8.py +++ b/pep8.py @@ -108,7 +108,7 @@ ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') DOCSTRING_REGEX = re.compile(r'u?r?["\']') EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') -COMPARE_SINGLETON_REGEX = re.compile(r'\b(None|False|True)?\s*([=!]=)' +COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)' r'\s*(?(1)|(None|False|True))\b') COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^][)(}{ ]+\s+(in|is)\s') COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type' diff --git a/testsuite/E71.py b/testsuite/E71.py index ea870e5..7464da9 100644 --- a/testsuite/E71.py +++ b/testsuite/E71.py @@ -10,6 +10,18 @@ if None == res: #: E711 if None != res: pass +#: E711 +if res[1] == None: + pass +#: E711 +if res[1] != None: + pass +#: E711 +if None != res[1]: + pass +#: E711 +if None == res[1]: + pass # #: E712 @@ -24,6 +36,12 @@ if True != res: #: E712 if False == res: pass +#: E712 +if res[1] == True: + pass +#: E712 +if res[1] != False: + pass # #: E713 -- cgit v1.2.1 From 8b5c8f4f762ed9f660f2d034ad5c602909c133f9 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 25 Oct 2015 10:53:27 -0700 Subject: Fixed #452 - Mention `ignore` in Error codes and not Configuration --- docs/intro.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 3a605eb..97b0a68 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -207,10 +207,6 @@ present (``.pep8`` file is also supported, but it is deprecated). If none of these files have a ``[pep8]`` 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 ``E123/E133``, ``E226`` and ``E241/E242`` are ignored -(see below). - Error codes ----------- -- cgit v1.2.1 From eaff751319592db73271408d6ac282e00a1deaf3 Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Thu, 29 Oct 2015 14:11:07 -0600 Subject: Correct _get_parameters. The two versions of parameter parsing were not, in fact, equivalent. The return types of `inspect.signature` and `inspect.getargspec` are, in fact, subtly different. See: * https://docs.python.org/3/library/inspect.html#inspect.signature * https://github.com/praw-dev/praw/issues/541#issuecomment-152280322 --- pep8.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index c22e63e..b1916cc 100755 --- a/pep8.py +++ b/pep8.py @@ -1316,7 +1316,10 @@ _checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} def _get_parameters(function): if sys.version_info >= (3, 3): - return list(inspect.signature(function).parameters) + return [parameter.name + for parameter + in inspect.signature(function).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] else: return inspect.getargspec(function)[0] -- cgit v1.2.1 From 925efaea8cbefb6e5a36e3524b6457da0880c520 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 5 Nov 2015 14:18:33 +0100 Subject: fix BytesWarning on Python 3 On Python 3, when python3 with run with -bb, comparison between bytes and str raises a BytesWarning exception. Test the Python version instead of testing '' == b''. --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index b1916cc..d292341 100755 --- a/pep8.py +++ b/pep8.py @@ -1171,7 +1171,7 @@ def python_3000_backticks(logical_line): ############################################################################## -if '' == ''.encode(): +if sys.version_info < (3,): # Python 2: implicit encoding. def readlines(filename): """Read the source code.""" -- cgit v1.2.1 From 993e80c2bbb92268ae52bb20bbf8e6f6b048a1d0 Mon Sep 17 00:00:00 2001 From: Kristian Glass Date: Sun, 8 Nov 2015 16:28:03 +0000 Subject: Because documenting direction / scope decisions can be useful --- docs/developer.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/developer.rst b/docs/developer.rst index 4725acd..7ea5b2d 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -20,6 +20,14 @@ conditions of the :ref:`Expat license `. Fork away! .. _available on GitHub: https://github.com/jcrocholl/pep8 +Direction +~~~~~~~~~ + +Some high-level aims and directions to bear in mind for contributions: + + + + Contribute ~~~~~~~~~~ -- cgit v1.2.1 From e6506f4247fe162a0593f713d559200fbdf5bba5 Mon Sep 17 00:00:00 2001 From: Kristian Glass Date: Sun, 8 Nov 2015 16:41:34 +0000 Subject: Added recommendation against ast usage in pep8 Based on https://github.com/PyCQA/pep8/pull/431#issuecomment-134706116 --- docs/developer.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/developer.rst b/docs/developer.rst index 7ea5b2d..4237a2b 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -25,7 +25,9 @@ Direction Some high-level aims and directions to bear in mind for contributions: - +* ``pep8`` is intended to be as fast as possible. + Using the ``ast`` module defeats that purpose. + The `pep8-naming `_ plugin exists for this sort of functionality. Contribute -- cgit v1.2.1 From b07759504a4bf393aa0e3fdd1f6a7300aaefbd1b Mon Sep 17 00:00:00 2001 From: Kristian Glass Date: Sun, 8 Nov 2015 17:16:11 +0000 Subject: flake8 for plugins etc., not pep8 See https://github.com/PyCQA/pep8/pull/445#issuecomment-146995058 --- docs/developer.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/developer.rst b/docs/developer.rst index 4237a2b..3fcdc38 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -28,6 +28,9 @@ Some high-level aims and directions to bear in mind for contributions: * ``pep8`` is intended to be as fast as possible. Using the ``ast`` module defeats that purpose. The `pep8-naming `_ plugin exists for this sort of functionality. +* If you want to provide extensibility / plugins, + please see `flake8 `_ - + ``pep8`` doesn't want or need a plugin architecture. Contribute -- cgit v1.2.1 From 721106b5adff2c743d3817f4b99e15d81b9185c7 Mon Sep 17 00:00:00 2001 From: Kristian Glass Date: Sun, 8 Nov 2015 17:17:35 +0000 Subject: Document standalone + py26 aims "This library aims to support Python 2.6 and work as a standalone script with no dependencies." https://github.com/PyCQA/pep8/pull/455#issuecomment-151712140 --- docs/developer.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/developer.rst b/docs/developer.rst index 3fcdc38..8f1ddc1 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -31,6 +31,8 @@ Some high-level aims and directions to bear in mind for contributions: * If you want to provide extensibility / plugins, please see `flake8 `_ - ``pep8`` doesn't want or need a plugin architecture. +* Python 2.6 support is still deemed important. +* ``pep8`` aims to have no external dependencies. Contribute -- cgit v1.2.1 From 86284a8d839c854cd787fdcd66bcc0ee1fe15bc8 Mon Sep 17 00:00:00 2001 From: Kristian Glass Date: Sun, 8 Nov 2015 17:22:17 +0000 Subject: CONTRIBUTING docs Shiny shiny GitHub support (https://github.com/blog/1184-contributing-guidelines) plus general good signposting --- CONTRIBUTING.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..158dc6f --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,4 @@ +Contributing to ``pep8`` +======================== + +Please see the `developer notes `_ -- cgit v1.2.1 From da3467564b6e50cf841913d0b108b66fff3ae5e2 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 2 Jan 2016 11:57:16 -0800 Subject: Updated copyrights to 2016 --- LICENSE | 2 +- docs/conf.py | 2 +- pep8.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 2ec4d39..302779d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Copyright © 2006-2009 Johann C. Rocholl Copyright © 2009-2014 Florent Xicluna -Copyright © 2014-2015 Ian Lee +Copyright © 2014-2016 Ian Lee Licensed under the terms of the Expat License diff --git a/docs/conf.py b/docs/conf.py index d58549d..5f990f3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,7 +46,7 @@ master_doc = 'index' # General information about the project. project = u'pep8' authors = u'Johann C. Rocholl, Florent Xicluna, Ian Lee' -copyright = u'2006-2015, %s' % (authors) +copyright = u'2006-2016, %s' % (authors) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/pep8.py b/pep8.py index d292341..821f164 100755 --- a/pep8.py +++ b/pep8.py @@ -2,7 +2,7 @@ # pep8.py - Check Python source code formatting, according to PEP 8 # Copyright (C) 2006-2009 Johann C. Rocholl # Copyright (C) 2009-2014 Florent Xicluna -# Copyright (C) 2014-2015 Ian Lee +# Copyright (C) 2014-2016 Ian Lee # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files -- cgit v1.2.1 From 7680e2fdec0797e6a91b11f61d4af4d1d6f3c4ff Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 11 Jan 2016 13:26:01 -0800 Subject: Updated changelog, leaving date as unreleased for the moment --- CHANGES.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5499abd..edae3a1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,14 +2,24 @@ Changelog ========= -1.6.x (unreleased) +1.6.3 (unreleased) ------------------ +Announcements: + +* Repository moved to PyCQA Organization on GitHub: + https://github.com/pycqa/pep8 + Changes: * Reverted the fix in #368, "options passed on command line are only ones accepted" feature. This has many unintended consequences in pep8 and flake8 and needs to be reworked when I have more time. +* Added support for Python 3.5. (Issue #420) + +Bugs: + +* Fixed BytesWarning on Python 3. (Issue #459) 1.6.2 (2015-02-15) ------------------ -- cgit v1.2.1 From 1c496307f8c0e340c9f26d0972406bf53470282c Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 11 Jan 2016 13:29:06 -0800 Subject: Bumped version in preparation of release --- pep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 821f164..a87d66d 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.3a0' +__version__ = '1.6.3' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From dd2302e0fe6b213d367b0a73ad84c31b68bca5cc Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 12 Jan 2016 09:16:57 -0800 Subject: Added missing changelog entries. --- CHANGES.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index edae3a1..1950598 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,7 +15,9 @@ Changes: * Reverted the fix in #368, "options passed on command line are only ones accepted" feature. This has many unintended consequences in pep8 and flake8 and needs to be reworked when I have more time. -* Added support for Python 3.5. (Issue #420) +* Added support for Python 3.5. (Issue #420 & #459) +* Added support for multi-line config_file option parsing. (Issue #429) +* Improved parameter parsing. (Issues #420 & #456) Bugs: -- cgit v1.2.1 From a0da509108ac63582c4c96ad8a38225b4724fcf6 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 12 Jan 2016 09:17:51 -0800 Subject: Changed to make new release 1.7 rather than 1.6.3 See discussion in #469 --- CHANGES.txt | 2 +- pep8.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 1950598..860b6ad 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,7 +2,7 @@ Changelog ========= -1.6.3 (unreleased) +1.7.0 (2016-01-12) ------------------ Announcements: diff --git a/pep8.py b/pep8.py index a87d66d..3c950d4 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.6.3' +__version__ = '1.7.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From 91a7eff7216e02efc4b7e5fd5262c81f7554b8d9 Mon Sep 17 00:00:00 2001 From: Alasdair Nicol Date: Tue, 16 Feb 2016 14:57:04 +0000 Subject: Changed link from pep257 to pydocstyle now that project has been renamed. --- docs/intro.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro.rst b/docs/intro.rst index 97b0a68..6a579b1 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -47,7 +47,7 @@ the ``pep8`` library: `pep8-naming extension `_ to use this feature. * **docstring conventions**: they are not in the scope of this library; - see the `pep257 project `_. + see the `pydocstyle project `_. * **automatic fixing**: see the section *PEP8 Fixers* in the :ref:`related tools ` page. -- cgit v1.2.1 From 2c872940709a0f9d6156f29d2a2fd5a1c5a642ab Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 16 Feb 2016 19:49:39 -0800 Subject: Incremented version number marking new work after 1.7 release --- CHANGES.txt | 3 +++ pep8.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 860b6ad..47f0ace 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,9 @@ Changelog ========= +1.8.0 (unreleased) +------------------ + 1.7.0 (2016-01-12) ------------------ diff --git a/pep8.py b/pep8.py index 3c950d4..fceb245 100755 --- a/pep8.py +++ b/pep8.py @@ -62,7 +62,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.7.0' +__version__ = '1.8.0-dev' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' -- cgit v1.2.1 From 44e48c1f71b916615466c628fc6bd056df56804e Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 16 Feb 2016 19:54:39 -0800 Subject: Added missing python envs to tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 50a0d00..b3bb37b 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py32, py33, py34, pypy, jython +envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython [testenv] commands = -- cgit v1.2.1 From b688c787c88884b8aca9fe38bb5b9c9095b39d7a Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Wed, 17 Feb 2016 21:58:19 +0100 Subject: fix broken link --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b3bb37b..5661fbb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ -# Tox (http://codespeak.net/~hpk/tox/) is a tool for running tests +# Tox (https://testrun.org/tox/latest/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. -- cgit v1.2.1 From 2344a34a628b2f2e9df1c2539d9316005a35053d Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 22 Feb 2016 15:36:44 -0800 Subject: Updated source code links to point to new repository name; issue #466 / #481 --- CHANGES.txt | 4 ++++ README.rst | 2 +- docs/advanced.rst | 2 +- docs/developer.rst | 6 +++--- docs/index.rst | 2 +- docs/intro.rst | 2 +- pep8.py | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 47f0ace..6e853eb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,10 @@ Changelog 1.8.0 (unreleased) ------------------ +Announcements: + +* Repository renamed to `pycodestyle`; Issue #466 / $481. + 1.7.0 (2016-01-12) ------------------ diff --git a/README.rst b/README.rst index 4204b05..e311d4a 100644 --- a/README.rst +++ b/README.rst @@ -88,4 +88,4 @@ Links * `Read the documentation `_ -* `Fork me on GitHub `_ +* `Fork me on GitHub `_ diff --git a/docs/advanced.rst b/docs/advanced.rst index 1c3a7e1..62c1b90 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -42,7 +42,7 @@ Skip file header ---------------- Another example is related to the `feature request #143 -`_: skip a number of lines +`_: skip a number of lines at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: diff --git a/docs/developer.rst b/docs/developer.rst index 4ffc132..55a88f6 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -11,13 +11,13 @@ Source code The source code is currently `available on GitHub`_ under the terms and conditions of the :ref:`Expat license `. Fork away! -* `Source code `_ and - `issue tracker `_ on GitHub. +* `Source code `_ and + `issue tracker `_ on GitHub. * `Continuous tests `_ against Python 2.6 through 3.4 and PyPy, on `Travis-CI platform `_. -.. _available on GitHub: https://github.com/pycqa/pep8 +.. _available on GitHub: https://github.com/pycqa/pycodestyle Direction diff --git a/docs/index.rst b/docs/index.rst index 5e4a4c5..d977022 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,7 +22,7 @@ Contents: developer * Online documentation: http://pep8.readthedocs.org/ -* Source code and issue tracker: https://github.com/pycqa/pep8 +* Source code and issue tracker: https://github.com/pycqa/pycodestyle Indices and tables diff --git a/docs/intro.rst b/docs/intro.rst index 6a579b1..6d1b191 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -431,6 +431,6 @@ The `flake8 checker `_ is a wrapper around ``pep8`` and similar tools. It supports plugins. Other tools which use ``pep8`` are referenced in the Wiki: `list of related -tools `_. +tools `_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ diff --git a/pep8.py b/pep8.py index fceb245..ebf70bc 100755 --- a/pep8.py +++ b/pep8.py @@ -31,7 +31,7 @@ For usage and a list of options, try this: $ python pep8.py -h This program and its regression test suite live here: -https://github.com/pycqa/pep8 +https://github.com/pycqa/pycodestyle Groups of errors and warnings: E errors -- cgit v1.2.1 From ac548869abe75e8d2ca9f6da7f1fdfd287807880 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 23 Feb 2016 22:26:39 -0600 Subject: Add link to PyCQA code of conduct --- docs/developer.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/developer.rst b/docs/developer.rst index 55a88f6..bbafb80 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -101,7 +101,10 @@ Then be sure to pass the tests:: $ python pep8.py --doctest $ python pep8.py --verbose pep8.py +When contributing to pycodestyle, please observe our `Code of Conduct`_. + .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ +.. _Code of Conduct: http://meta.pycqa.org/en/latest/code-of-conduct.html Changes -- cgit v1.2.1 From 394a061457ab6a591c7cad5bcb0d33de8a0209d2 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 24 Feb 2016 16:09:05 -0600 Subject: Identify binary operators used as unary operators Previously we handled the case where binary operators were being used as unary operators except in the case where they followed another binary operator, e.g., foo = (1 + -10 * 2 / -5) This change updates the check for W503 to track the previous non-whitespace token type and token text to check if it is in fact also a binary operator (because you should never have two binary operators in a row). This does not handle invalid syntax, e.g., foo = (bar / /baz) But the false-positive generated for other cases was more harmful than not catching what will instead be caught by the interpreter. Closes gh-484 --- pep8.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index bfbea7f..39eb426 100755 --- a/pep8.py +++ b/pep8.py @@ -1018,6 +1018,8 @@ def break_around_binary_operator(logical_line, tokens): Okay: foo(x,\n -y) Okay: foo(x, # comment\n -y) Okay: var = (1 &\n ~2) + Okay: var = (1 /\n -2) + Okay: var = (1 +\n -1 +\n -2) """ def is_binary_operator(token_type, text): # The % character is strictly speaking a binary operator, but the @@ -1028,6 +1030,9 @@ def break_around_binary_operator(logical_line, tokens): line_break = False unary_context = True + # Previous non-newline token types and text + previous_token_type = None + previous_text = None for token_type, text, start, end, line in tokens: if token_type == tokenize.COMMENT: continue @@ -1035,10 +1040,14 @@ def break_around_binary_operator(logical_line, tokens): line_break = True else: if (is_binary_operator(token_type, text) and line_break and - not unary_context): + not unary_context and + not is_binary_operator(previous_token_type, + previous_text)): yield start, "W503 line break before binary operator" unary_context = text in '([{,;' line_break = False + previous_token_type = token_type + previous_text = text def comparison_to_singleton(logical_line, noqa): -- cgit v1.2.1 From 2e151c544a4312ec9721c6abedd0f1e4067f9231 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 24 Feb 2016 20:01:42 -0800 Subject: Updated changelog from last few merges --- CHANGES.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 6e853eb..d25315a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,16 @@ Changelog Announcements: * Repository renamed to `pycodestyle`; Issue #466 / $481. +* Added joint Code of Conduct as member of PyCQA; #483 + +Changes: + +* Added tox test support for Python 3.5 and pypy3 + +Bugs: + +* Fixed bug with treating `~` operator as binary; #384 +* Identify binary operators as unary; #484 1.7.0 (2016-01-12) ------------------ -- cgit v1.2.1 From 96de5f8551262e7347dfff434dd737485130681c Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Wed, 24 Feb 2016 17:34:03 -0800 Subject: Update Readme to reflect pycodestyle name change --- README.rst | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index e311d4a..5619c79 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,16 @@ -pep8 - Python style guide checker -================================= +pycodestyle (formerly called pep8) - Python style guide checker +=============================================================== -pep8 is a tool to check your Python code against some of the style +pycodestyle is a tool to check your Python code against some of the style conventions in `PEP 8`_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ +.. note:: + + This package used to be called ``pep8`` but was renamed to ``pycodestyle`` + to reduce confusion. Further discussion `here + `_. Features -------- @@ -15,18 +20,18 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pep8.py file for this purpose. + the pycodestyle.py file for this purpose. * Comes with a comprehensive test suite. Installation ------------ -You can install, upgrade, uninstall pep8.py with these commands:: +You can install, upgrade, uninstall pycodestyle.py with these commands:: - $ pip install pep8 - $ pip install --upgrade pep8 - $ pip uninstall pep8 + $ pip install pycodestyle + $ pip install --upgrade pycodestyle + $ pip uninstall pycodestyle There's also a package for Debian/Ubuntu, but it's not always the latest version. @@ -36,7 +41,7 @@ Example usage and output :: - $ pep8 --first optparse.py + $ pycodestyle --first optparse.py optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 @@ -46,10 +51,10 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pep8.py show the source code for each error, and +You can also make pycodestyle.py show the source code for each error, and even the relevant text from PEP 8:: - $ pep8 --show-source --show-pep8 testsuite/E40.py + $ pycodestyle --show-source --show-pep8 testsuite/E40.py testsuite/E40.py:2:10: E401 multiple imports on one line import os, sys ^ @@ -61,7 +66,7 @@ even the relevant text from PEP 8:: Or you can display how often each error was found:: - $ pep8 --statistics -qq Python-2.5/Lib + $ pycodestyle --statistics -qq Python-2.5/Lib 232 E201 whitespace after '[' 599 E202 whitespace before ')' 631 E203 whitespace before ',' @@ -78,14 +83,14 @@ Or you can display how often each error was found:: Links ----- -.. image:: https://api.travis-ci.org/PyCQA/pep8.png?branch=master - :target: https://travis-ci.org/PyCQA/pep8 +.. image:: https://api.travis-ci.org/PyCQA/pycodestyle.png?branch=master + :target: https://travis-ci.org/PyCQA/pycodestyle :alt: Build status -.. image:: https://pypip.in/wheel/pep8/badge.png?branch=master - :target: https://pypi.python.org/pypi/pep8 +.. image:: https://pypip.in/wheel/pycodestyle/badge.png?branch=master + :target: https://pypi.python.org/pypi/pycodestyle :alt: Wheel Status -* `Read the documentation `_ +* `Read the documentation `_ * `Fork me on GitHub `_ -- cgit v1.2.1 From 1dd1127605bc4dfe52e6a9ff23cba7e2b0fe7fea Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 28 Feb 2016 01:13:05 -0800 Subject: Patched pep8 to comply with pydocstyle; Closes #228. Purposely ignoring D102 and D203 error codes. - D102 appears to be a bug with D102 - D203 should be ignored by default by pydocstyle) --- pep8.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pep8.py b/pep8.py index 39eb426..ef6c7cf 100755 --- a/pep8.py +++ b/pep8.py @@ -837,7 +837,7 @@ def whitespace_before_comment(logical_line, tokens): def imports_on_separate_lines(logical_line): - r"""Imports should usually be on separate lines. + r"""Place imports on separate lines. Okay: import os\nimport sys E401: import sys, os @@ -857,8 +857,10 @@ def imports_on_separate_lines(logical_line): def module_imports_on_top_of_file( logical_line, indent_level, checker_state, noqa): - r"""Imports are always put at the top of the file, just after any module - comments and docstrings, and before module globals and constants. + r"""Place imports at the top of the file. + + Always put imports at the top of the file, just after any module comments + and docstrings, and before module globals and constants. Okay: import os Okay: # this is a comment\nimport os @@ -1166,7 +1168,7 @@ def python_3000_not_equal(logical_line): def python_3000_backticks(logical_line): - r"""Backticks are removed in Python 3: use repr() instead. + r"""Use repr() instead of backticks in Python 3. Okay: val = repr(1 + 2) W604: val = `1 + 2` @@ -1205,7 +1207,9 @@ else: isidentifier = str.isidentifier def stdin_get_value(): + """Read the value from stdin.""" return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + noqa = re.compile(r'# no(?:qa|pep8)\b', re.I).search @@ -1439,7 +1443,7 @@ class Checker(object): return check(*arguments) def init_checker_state(self, name, argument_names): - """ Prepares a custom state for the specific checker plugin.""" + """Prepare custom state for the specific checker plugin.""" if 'checker_state' in argument_names: self.checker_state = self._checker_states.setdefault(name, {}) @@ -1721,6 +1725,7 @@ class BaseReport(object): class FileReport(BaseReport): """Collect the results of the checks and print only the filenames.""" + print_filename = True @@ -1928,6 +1933,7 @@ class StyleGuide(object): def get_parser(prog='pep8', version=__version__): + """Create the parser for the program.""" parser = OptionParser(prog=prog, version=version, usage="%prog [options] input ...") parser.config_options = [ @@ -1989,7 +1995,7 @@ def get_parser(prog='pep8', version=__version__): def read_config(options, args, arglist, parser): - """Read and parse configurations + """Read and parse configurations. If a config file is specified on the command line with the "--config" option, then only it is used for configuration. -- cgit v1.2.1 From b7bf8c80608baa9ebbc6ab5c7692a58243c6f775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Rogalski?= Date: Wed, 23 Mar 2016 23:24:51 +0100 Subject: complain about missing space before opening parentheses of import statement Solves issue #489 --- docs/intro.rst | 2 ++ pep8.py | 17 +++++++++++++++++ testsuite/E27.py | 14 ++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/docs/intro.rst b/docs/intro.rst index 6d1b191..e26daf7 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -313,6 +313,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E274 | tab before keyword | +------------+----------------------------------------------------------------------+ +| E275 | missing whitespace after keyword | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **E3** | *Blank line* | +------------+----------------------------------------------------------------------+ diff --git a/pep8.py b/pep8.py index 39eb426..ec19dc5 100755 --- a/pep8.py +++ b/pep8.py @@ -325,6 +325,23 @@ def whitespace_around_keywords(logical_line): yield match.start(2), "E271 multiple spaces after keyword" +def missing_whitespace_after_import_keyword(logical_line): + r"""Multiple imports in form from x import (a, b, c) should have space + between import statement and parenthesised name list. + + Okay: from foo import (bar, baz) + E275: from foo import(bar, baz) + E275: from importable.module import(bar, baz) + """ + line = logical_line + indicator = ' import(' + if line.startswith('from '): + found = line.find(indicator) + if -1 < found: + pos = found + len(indicator) - 1 + yield pos, "E275 missing whitespace after keyword" + + def missing_whitespace(logical_line): r"""Each comma, semicolon or colon should be followed by whitespace. diff --git a/testsuite/E27.py b/testsuite/E27.py index f9d3e8e..888b3a8 100644 --- a/testsuite/E27.py +++ b/testsuite/E27.py @@ -28,3 +28,17 @@ a and b a and b #: E273 E274 this and False +#: Okay +from u import (a, b) +from v import c, d +#: E271 +from w import (e, f) +#: E275 +from w import(e, f) +#: E275 +from importable.module import(e, f) +#: E275 +try: + from importable.module import(e, f) +except ImportError: + pass -- cgit v1.2.1 From 4a37706b95a49ff61a44202f9775146de1e895db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Fri, 8 Apr 2016 14:54:57 +0300 Subject: Fix false E251 error involving square brackets Fixed a case where a comma is erroneously interpreted as an argument separator when square brackets are used in an argument annotation with a default value. Closes #496 --- pep8.py | 4 ++-- testsuite/E25.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pep8.py b/pep8.py index ec19dc5..8ec21e1 100755 --- a/pep8.py +++ b/pep8.py @@ -794,9 +794,9 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): if start != prev_end: yield (prev_end, message) if token_type == tokenize.OP: - if text == '(': + if text in '([': parens += 1 - elif text == ')': + elif text in ')]': parens -= 1 elif in_def and text == ':' and parens == 1: annotated_func_arg = True diff --git a/testsuite/E25.py b/testsuite/E25.py index ad8db88..7d00310 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -32,5 +32,6 @@ d[type(None)] = _deepcopy_atomic # Annotated Function Definitions #: Okay -def munge(input: AnyStr, sep: AnyStr = None, limit=1000) -> AnyStr: +def munge(input: AnyStr, sep: AnyStr = None, limit=1000, + extra: Union[str, dict] = None) -> AnyStr: pass -- cgit v1.2.1 From bac13b8187e5f1d120ff692897b2ac539593ebb4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 20 Apr 2016 08:29:36 -0500 Subject: Add W503 to default ignore list Closes #498 --- docs/intro.rst | 2 +- pep8.py | 2 +- testsuite/test_api.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index e26daf7..2ce1eb6 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -392,7 +392,7 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | **W5** | *Line break warning* | +------------+----------------------------------------------------------------------+ -| W503 | line break occurred before a binary operator | +| W503 (*) | line break occurred before a binary operator | +------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **W6** | *Deprecation warning* | diff --git a/pep8.py b/pep8.py index 8ec21e1..499c370 100755 --- a/pep8.py +++ b/pep8.py @@ -65,7 +65,7 @@ except ImportError: __version__ = '1.8.0-dev' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' -DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704' +DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' try: if sys.platform == 'win32': USER_CONFIG = os.path.expanduser(r'~\.pep8') diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 1cb0d4b..0b83c4e 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -181,7 +181,7 @@ class APITestCase(unittest.TestCase): self.assertEqual(options.select, ()) self.assertEqual( options.ignore, - ('E121', 'E123', 'E126', 'E226', 'E24', 'E704') + ('E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'W503') ) options = parse_argv('--doctest').options -- cgit v1.2.1 From 93209d17931da6bf4dbe2160ed761788e9aab860 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Thu, 28 Apr 2016 09:33:09 -0700 Subject: Updated text list of "ignored by default" codes --- docs/intro.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index 2ce1eb6..5f7e9cd 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -408,8 +408,8 @@ This is the current list of error and warning codes: **(*)** In the default configuration, the checks **E121**, **E123**, **E126**, -**E133**, **E226**, **E241**, **E242** and **E704** are ignored because they -are not rules unanimously accepted, and `PEP 8`_ does not enforce them. The +**E133**, **E226**, **E241**, **E242**, **E704** and **W503** are ignored because +they are not rules unanimously accepted, and `PEP 8`_ does not enforce them. The check **E133** is mutually exclusive with check **E123**. Use switch ``--hang- closing`` to report **E133** instead of **E123**. -- cgit v1.2.1 From 69d88e3ee24f980172d87612435ab86428de818e Mon Sep 17 00:00:00 2001 From: Nicholas Chammas Date: Thu, 21 Apr 2016 13:08:40 -0400 Subject: add examples showing StyleGuide config options --- docs/advanced.rst | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 62c1b90..10c09f8 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -8,7 +8,7 @@ Advanced usage Automated tests --------------- -You can also execute `pep8` tests from Python code. For example, this +You can also execute ``pep8`` tests from Python code. For example, this can be highly useful for automated testing of coding style conformance in your project:: @@ -25,7 +25,7 @@ in your project:: self.assertEqual(result.total_errors, 0, "Found code style errors (and warnings).") -If you are using `nosetests` for running tests, remove `quiet=True` +If you are using ``nosetests`` for running tests, remove ``quiet=True`` since Nose suppresses stdout. There's also a shortcut for checking a single file:: @@ -38,6 +38,23 @@ There's also a shortcut for checking a single file:: print("Found %s errors (and warnings)" % file_errors) +Configuring tests +................. + +You can configure automated ``pep8`` tests in a variety of ways. + +For example, you can pass in a path to a configuration file that ``pep8`` +should use:: + + import pep8 + + pep8style = pep8.StyleGuide(config_file='/path/to/tox.ini') + +You can also set specific options explicitly:: + + pep8style = pep8.StyleGuide(ignore=['E501']) + + Skip file header ---------------- @@ -46,7 +63,6 @@ Another example is related to the `feature request #143 at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: - #!python import pep8 LINES_SLICE = slice(14, -20) -- cgit v1.2.1 From 2f6c43bcb3a6bac525198646e99f12b013e4e460 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 23 May 2016 11:58:11 -0500 Subject: Allow spaces around = in async definitions In pull request gh-361, we allowed spaces around = for default arguments with annotations. Python 3.5 added the async keyword for a function definition and the allowance we made in gh-361 was failing. This allows a function definition to start with either 'def' or 'async def' now and accommodates both cases. Closes gh-507 --- pep8.py | 3 ++- testsuite/E25.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 499c370..247a626 100755 --- a/pep8.py +++ b/pep8.py @@ -776,6 +776,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): Okay: boolean(a <= b) Okay: boolean(a >= b) Okay: def foo(arg: int = 42): + Okay: async def foo(arg: int = 42): E251: def complex(real, imag = 0.0): E251: return magic(r = real, i = imag) @@ -784,7 +785,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = False prev_end = None annotated_func_arg = False - in_def = logical_line.startswith('def') + in_def = logical_line.startswith(('def', 'async def')) message = "E251 unexpected spaces around keyword / parameter equals" for token_type, text, start, end, line in tokens: if token_type == tokenize.NL: diff --git a/testsuite/E25.py b/testsuite/E25.py index 7d00310..7a536b5 100644 --- a/testsuite/E25.py +++ b/testsuite/E25.py @@ -35,3 +35,6 @@ d[type(None)] = _deepcopy_atomic def munge(input: AnyStr, sep: AnyStr = None, limit=1000, extra: Union[str, dict] = None) -> AnyStr: pass +#: Okay +async def add(a: int = 0, b: int = 0) -> int: + return a + b -- cgit v1.2.1 From 05627df9e1e79cb1dcad5354615ecca863647100 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 24 May 2016 08:46:16 -0700 Subject: Re-added shebang line that was removed --- docs/advanced.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/advanced.rst b/docs/advanced.rst index 10c09f8..71153b3 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -63,6 +63,7 @@ Another example is related to the `feature request #143 at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: + #!python import pep8 LINES_SLICE = slice(14, -20) -- cgit v1.2.1 From cec28bef2c30100b3d7cc669cd58ad8b3adcd618 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 24 May 2016 08:46:48 -0700 Subject: Changed heading line format for consistency --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 71153b3..de3be69 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -39,7 +39,7 @@ There's also a shortcut for checking a single file:: Configuring tests -................. +----------------- You can configure automated ``pep8`` tests in a variety of ways. -- cgit v1.2.1 From 4f42869cf8b72e54a0c252a4eda2e17c9ae96e03 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 25 May 2016 21:06:27 +0200 Subject: Fix the "Further discussion" URL --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5619c79..3ba1619 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ conventions in `PEP 8`_. This package used to be called ``pep8`` but was renamed to ``pycodestyle`` to reduce confusion. Further discussion `here - `_. + `_. Features -------- -- cgit v1.2.1 From a8f1c782763d1c43e4804733354857c76e3b7b0b Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 25 May 2016 21:43:29 +0200 Subject: Fix typo --- testsuite/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index 0b83c4e..baafff7 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -137,7 +137,7 @@ class APITestCase(unittest.TestCase): self.reset() def test_styleguide_options(self): - # Instanciate a simple checker + # Instantiate a simple checker pep8style = pep8.StyleGuide(paths=[E11]) # Check style's attributes -- cgit v1.2.1 From 481c233ffe07c13ff164bb4ce25e6791ec3e1494 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Wed, 25 May 2016 21:44:33 +0200 Subject: Fix grammar "allow" is a transitive verb, which requires an object, so "allow to " is ungrammatical. --- CHANGES.txt | 6 +++--- pep8.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d25315a..d22214f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -350,7 +350,7 @@ Bug fixes: * Initiate a graceful shutdown on ``Control+C``. -* Allow to change the ``checker_class`` for the ``StyleGuide``. +* Allow changing the ``checker_class`` for the ``StyleGuide``. 1.4.2 (2013-02-10) @@ -360,7 +360,7 @@ Bug fixes: * Register new checkers with ``register_check(func_or_cls, codes)``. -* Allow to construct a ``StyleGuide`` with a custom parser. +* Allow constructing a ``StyleGuide`` with a custom parser. * Accept visual indentation without parenthesis after the ``if`` statement. (Issue #151) @@ -569,7 +569,7 @@ Bug fixes: The ``--repeat`` flag becomes obsolete because it is the default behaviour. (Issue #6) -* Allow to specify ``--max-line-length``. (Issue #36) +* Allow specifying ``--max-line-length``. (Issue #36) * Make the shebang more flexible. (Issue #26) diff --git a/pep8.py b/pep8.py index 0d329f3..207af15 100755 --- a/pep8.py +++ b/pep8.py @@ -582,7 +582,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, break assert len(indent) == depth + 1 if start[1] not in indent_chances: - # allow to line up tokens + # allow lining up tokens indent_chances[start[1]] = text last_token_multiline = (start[0] != end[0]) -- cgit v1.2.1 From 21783518e7fe4ed7d41a777fee83c962c26dd3d3 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 28 Feb 2016 22:12:11 -0800 Subject: Added pycodestyle entry_point Converted pep8 entry point to emit a deprecation warning for users --- pep8.py | 15 +++++++++++++++ setup.py | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pep8.py b/pep8.py index 207af15..4af3c3e 100755 --- a/pep8.py +++ b/pep8.py @@ -2181,5 +2181,20 @@ def _main(): sys.stderr.write(str(report.total_errors) + '\n') sys.exit(1) + +def _main_pep8(): + """Entrypoint for pep8 commandline tool. + + Warn of deprecation and advise users to switch to pycodestyle. + """ + print( + 'Deprecation Warning:\n' + 'pep8 has been renamed to pycodestyle and the use of the pep8 ' + 'executable will be removed in a future release. Please use ' + '`pycodestyle` instead.\n' + ) + _main() + + if __name__ == '__main__': _main() diff --git a/setup.py b/setup.py index 29182c6..b767119 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,8 @@ setup( ], entry_points={ 'console_scripts': [ - 'pep8 = pep8:_main', + 'pycodestyle = pep8:_main', + 'pep8 = pep8:_main_pep8', ], }, classifiers=[ -- cgit v1.2.1 From 3f9ed3a9e62f8fdb4443fa849cca228a501ed1f4 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 28 Feb 2016 22:14:27 -0800 Subject: Made 'pep8style' more generically 'style_guide' --- pep8.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pep8.py b/pep8.py index 4af3c3e..6776303 100755 --- a/pep8.py +++ b/pep8.py @@ -2158,14 +2158,14 @@ def _main(): except AttributeError: pass # not supported on Windows - pep8style = StyleGuide(parse_argv=True) - options = pep8style.options + style_guide = StyleGuide(parse_argv=True) + options = style_guide.options if options.doctest or options.testsuite: from testsuite.support import run_tests - report = run_tests(pep8style) + report = run_tests(style_guide) else: - report = pep8style.check_files() + report = style_guide.check_files() if options.statistics: report.print_statistics() -- cgit v1.2.1 From 1a764175c78d387b66cfe02551d10f3da395a22c Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 10:44:37 -0700 Subject: Updated pycodestyle file name and setup.py --- pep8.py | 2200 -------------------------------------------------------- pycodestyle.py | 2200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 12 +- 3 files changed, 2206 insertions(+), 2206 deletions(-) delete mode 100755 pep8.py create mode 100755 pycodestyle.py diff --git a/pep8.py b/pep8.py deleted file mode 100755 index 6776303..0000000 --- a/pep8.py +++ /dev/null @@ -1,2200 +0,0 @@ -#!/usr/bin/env python -# pep8.py - Check Python source code formatting, according to PEP 8 -# Copyright (C) 2006-2009 Johann C. Rocholl -# Copyright (C) 2009-2014 Florent Xicluna -# Copyright (C) 2014-2016 Ian Lee -# -# 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. - -r""" -Check Python source code formatting, according to PEP 8. - -For usage and a list of options, try this: -$ python pep8.py -h - -This program and its regression test suite live here: -https://github.com/pycqa/pycodestyle - -Groups of errors and warnings: -E errors -W warnings -100 indentation -200 whitespace -300 blank lines -400 imports -500 line length -600 deprecation -700 statements -900 syntax error -""" -from __future__ import with_statement - -import os -import sys -import re -import time -import inspect -import keyword -import tokenize -from optparse import OptionParser -from fnmatch import fnmatch -try: - from configparser import RawConfigParser - from io import TextIOWrapper -except ImportError: - from ConfigParser import RawConfigParser - -__version__ = '1.8.0-dev' - -DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' -DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' -try: - if sys.platform == 'win32': - USER_CONFIG = os.path.expanduser(r'~\.pep8') - else: - USER_CONFIG = os.path.join( - os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), - 'pep8' - ) -except ImportError: - USER_CONFIG = None - -PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8') -TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') -MAX_LINE_LENGTH = 79 -REPORT_FORMAT = { - 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', - 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', -} - -PyCF_ONLY_AST = 1024 -SINGLETONS = frozenset(['False', 'None', 'True']) -KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS -UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) -ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-']) -WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) -WS_NEEDED_OPERATORS = frozenset([ - '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', - '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=']) -WHITESPACE = frozenset(' \t') -NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) -SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) -# ERRORTOKEN is triggered by backticks in Python 3 -SKIP_COMMENTS = SKIP_TOKENS.union([tokenize.COMMENT, tokenize.ERRORTOKEN]) -BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] - -INDENT_REGEX = re.compile(r'([ \t]*)') -RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,') -RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,.*,\s*\w+\s*$') -ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') -DOCSTRING_REGEX = re.compile(r'u?r?["\']') -EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') -WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') -COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)' - r'\s*(?(1)|(None|False|True))\b') -COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^][)(}{ ]+\s+(in|is)\s') -COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type' - r'|\s*\(\s*([^)]*[^ )])\s*\))') -KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) -OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') -LAMBDA_REGEX = re.compile(r'\blambda\b') -HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') - -# Work around Python < 2.6 behaviour, which does not generate NL after -# a comment which is on a line by itself. -COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' - - -############################################################################## -# Plugins (check functions) for physical lines -############################################################################## - - -def tabs_or_spaces(physical_line, indent_char): - r"""Never mix tabs and spaces. - - The most popular way of indenting Python is with spaces only. The - second-most popular way is with tabs only. Code indented with a mixture - of tabs and spaces should be converted to using spaces exclusively. When - invoking the Python command line interpreter with the -t option, it issues - warnings about code that illegally mixes tabs and spaces. When using -tt - these warnings become errors. These options are highly recommended! - - Okay: if a == 0:\n a = 1\n b = 1 - E101: if a == 0:\n a = 1\n\tb = 1 - """ - indent = INDENT_REGEX.match(physical_line).group(1) - for offset, char in enumerate(indent): - if char != indent_char: - return offset, "E101 indentation contains mixed spaces and tabs" - - -def tabs_obsolete(physical_line): - r"""For new projects, spaces-only are strongly recommended over tabs. - - Okay: if True:\n return - W191: if True:\n\treturn - """ - indent = INDENT_REGEX.match(physical_line).group(1) - if '\t' in indent: - return indent.index('\t'), "W191 indentation contains tabs" - - -def trailing_whitespace(physical_line): - r"""Trailing whitespace is superfluous. - - The warning returned varies on whether the line itself is blank, for easier - filtering for those who want to indent their blank lines. - - Okay: spam(1)\n# - W291: spam(1) \n# - W293: class Foo(object):\n \n bang = 12 - """ - physical_line = physical_line.rstrip('\n') # chr(10), newline - physical_line = physical_line.rstrip('\r') # chr(13), carriage return - physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L - stripped = physical_line.rstrip(' \t\v') - if physical_line != stripped: - if stripped: - return len(stripped), "W291 trailing whitespace" - else: - return 0, "W293 blank line contains whitespace" - - -def trailing_blank_lines(physical_line, lines, line_number, total_lines): - r"""Trailing blank lines are superfluous. - - Okay: spam(1) - W391: spam(1)\n - - However the last line should end with a new line (warning W292). - """ - if line_number == total_lines: - stripped_last_line = physical_line.rstrip() - if not stripped_last_line: - return 0, "W391 blank line at end of file" - if stripped_last_line == physical_line: - return len(physical_line), "W292 no newline at end of file" - - -def maximum_line_length(physical_line, max_line_length, multiline): - r"""Limit all lines to a maximum of 79 characters. - - There are still many devices around that are limited to 80 character - lines; plus, limiting windows to 80 characters makes it possible to have - several windows side-by-side. The default wrapping on such devices looks - ugly. Therefore, please limit all lines to a maximum of 79 characters. - For flowing long blocks of text (docstrings or comments), limiting the - length to 72 characters is recommended. - - Reports error E501. - """ - line = physical_line.rstrip() - length = len(line) - if length > max_line_length and not noqa(line): - # Special case for long URLs in multi-line docstrings or comments, - # but still report the error when the 72 first chars are whitespaces. - chunks = line.split() - if ((len(chunks) == 1 and multiline) or - (len(chunks) == 2 and chunks[0] == '#')) and \ - len(line) - len(chunks[-1]) < max_line_length - 7: - return - if hasattr(line, 'decode'): # Python 2 - # The line could contain multi-byte characters - try: - length = len(line.decode('utf-8')) - except UnicodeError: - pass - if length > max_line_length: - return (max_line_length, "E501 line too long " - "(%d > %d characters)" % (length, max_line_length)) - - -############################################################################## -# Plugins (check functions) for logical lines -############################################################################## - - -def blank_lines(logical_line, blank_lines, indent_level, line_number, - blank_before, previous_logical, previous_indent_level): - r"""Separate top-level function and class definitions with two blank lines. - - Method definitions inside a class are separated by a single blank line. - - Extra blank lines may be used (sparingly) to separate groups of related - functions. Blank lines may be omitted between a bunch of related - one-liners (e.g. a set of dummy implementations). - - Use blank lines in functions, sparingly, to indicate logical sections. - - Okay: def a():\n pass\n\n\ndef b():\n pass - Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass - - E301: class Foo:\n b = 0\n def bar():\n pass - E302: def a():\n pass\n\ndef b(n):\n pass - E303: def a():\n pass\n\n\n\ndef b(n):\n pass - E303: def a():\n\n\n\n pass - E304: @decorator\n\ndef a():\n pass - """ - if line_number < 3 and not previous_logical: - return # Don't expect blank lines before the first line - if previous_logical.startswith('@'): - if blank_lines: - yield 0, "E304 blank lines found after function decorator" - elif blank_lines > 2 or (indent_level and blank_lines == 2): - yield 0, "E303 too many blank lines (%d)" % blank_lines - elif logical_line.startswith(('def ', 'class ', '@')): - if indent_level: - if not (blank_before or previous_indent_level < indent_level or - DOCSTRING_REGEX.match(previous_logical)): - yield 0, "E301 expected 1 blank line, found 0" - elif blank_before != 2: - yield 0, "E302 expected 2 blank lines, found %d" % blank_before - - -def extraneous_whitespace(logical_line): - r"""Avoid extraneous whitespace. - - Avoid extraneous whitespace in these situations: - - Immediately inside parentheses, brackets or braces. - - Immediately before a comma, semicolon, or colon. - - Okay: spam(ham[1], {eggs: 2}) - E201: spam( ham[1], {eggs: 2}) - E201: spam(ham[ 1], {eggs: 2}) - E201: spam(ham[1], { eggs: 2}) - E202: spam(ham[1], {eggs: 2} ) - E202: spam(ham[1 ], {eggs: 2}) - E202: spam(ham[1], {eggs: 2 }) - - E203: if x == 4: print x, y; x, y = y , x - E203: if x == 4: print x, y ; x, y = y, x - E203: if x == 4 : print x, y; x, y = y, x - """ - line = logical_line - for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): - text = match.group() - char = text.strip() - found = match.start() - if text == char + ' ': - # assert char in '([{' - yield found + 1, "E201 whitespace after '%s'" % char - elif line[found - 1] != ',': - code = ('E202' if char in '}])' else 'E203') # if char in ',;:' - yield found, "%s whitespace before '%s'" % (code, char) - - -def whitespace_around_keywords(logical_line): - r"""Avoid extraneous whitespace around keywords. - - Okay: True and False - E271: True and False - E272: True and False - E273: True and\tFalse - E274: True\tand False - """ - for match in KEYWORD_REGEX.finditer(logical_line): - before, after = match.groups() - - if '\t' in before: - yield match.start(1), "E274 tab before keyword" - elif len(before) > 1: - yield match.start(1), "E272 multiple spaces before keyword" - - if '\t' in after: - yield match.start(2), "E273 tab after keyword" - elif len(after) > 1: - yield match.start(2), "E271 multiple spaces after keyword" - - -def missing_whitespace_after_import_keyword(logical_line): - r"""Multiple imports in form from x import (a, b, c) should have space - between import statement and parenthesised name list. - - Okay: from foo import (bar, baz) - E275: from foo import(bar, baz) - E275: from importable.module import(bar, baz) - """ - line = logical_line - indicator = ' import(' - if line.startswith('from '): - found = line.find(indicator) - if -1 < found: - pos = found + len(indicator) - 1 - yield pos, "E275 missing whitespace after keyword" - - -def missing_whitespace(logical_line): - r"""Each comma, semicolon or colon should be followed by whitespace. - - Okay: [a, b] - Okay: (3,) - Okay: a[1:4] - Okay: a[:4] - Okay: a[1:] - Okay: a[1:4:2] - E231: ['a','b'] - E231: foo(bar,baz) - E231: [{'a':'b'}] - """ - line = logical_line - for index in range(len(line) - 1): - char = line[index] - if char in ',;:' and line[index + 1] not in WHITESPACE: - before = line[:index] - if char == ':' and before.count('[') > before.count(']') and \ - before.rfind('{') < before.rfind('['): - continue # Slice syntax, no space required - if char == ',' and line[index + 1] == ')': - continue # Allow tuple with only one element: (3,) - yield index, "E231 missing whitespace after '%s'" % char - - -def indentation(logical_line, previous_logical, indent_char, - indent_level, previous_indent_level): - r"""Use 4 spaces per indentation level. - - For really old code that you don't want to mess up, you can continue to - use 8-space tabs. - - Okay: a = 1 - Okay: if a == 0:\n a = 1 - E111: a = 1 - E114: # a = 1 - - Okay: for item in items:\n pass - E112: for item in items:\npass - E115: for item in items:\n# Hi\n pass - - Okay: a = 1\nb = 2 - E113: a = 1\n b = 2 - E116: a = 1\n # b = 2 - """ - c = 0 if logical_line else 3 - tmpl = "E11%d %s" if logical_line else "E11%d %s (comment)" - if indent_level % 4: - yield 0, tmpl % (1 + c, "indentation is not a multiple of four") - indent_expect = previous_logical.endswith(':') - if indent_expect and indent_level <= previous_indent_level: - yield 0, tmpl % (2 + c, "expected an indented block") - elif not indent_expect and indent_level > previous_indent_level: - yield 0, tmpl % (3 + c, "unexpected indentation") - - -def continued_indentation(logical_line, tokens, indent_level, hang_closing, - indent_char, noqa, verbose): - r"""Continuation lines indentation. - - Continuation lines should align wrapped elements either vertically - using Python's implicit line joining inside parentheses, brackets - and braces, or using a hanging indent. - - When using a hanging indent these considerations should be applied: - - there should be no arguments on the first line, and - - further indentation should be used to clearly distinguish itself as a - continuation line. - - Okay: a = (\n) - E123: a = (\n ) - - Okay: a = (\n 42) - E121: a = (\n 42) - E122: a = (\n42) - E123: a = (\n 42\n ) - E124: a = (24,\n 42\n) - E125: if (\n b):\n pass - E126: a = (\n 42) - E127: a = (24,\n 42) - E128: a = (24,\n 42) - E129: if (a or\n b):\n pass - E131: a = (\n 42\n 24) - """ - first_row = tokens[0][2][0] - nrows = 1 + tokens[-1][2][0] - first_row - if noqa or nrows == 1: - return - - # indent_next tells us whether the next block is indented; assuming - # that it is indented by 4 spaces, then we should not allow 4-space - # indents on the final continuation line; in turn, some other - # indents are allowed to have an extra 4 spaces. - indent_next = logical_line.endswith(':') - - row = depth = 0 - valid_hangs = (4,) if indent_char != '\t' else (4, 8) - # remember how many brackets were opened on each line - parens = [0] * nrows - # relative indents of physical lines - rel_indent = [0] * nrows - # for each depth, collect a list of opening rows - open_rows = [[0]] - # for each depth, memorize the hanging indentation - hangs = [None] - # visual indents - indent_chances = {} - last_indent = tokens[0][2] - visual_indent = None - last_token_multiline = False - # for each depth, memorize the visual indent column - indent = [last_indent[1]] - if verbose >= 3: - print(">>> " + tokens[0][4].rstrip()) - - for token_type, text, start, end, line in tokens: - - newline = row < start[0] - first_row - if newline: - row = start[0] - first_row - newline = not last_token_multiline and token_type not in NEWLINE - - if newline: - # this is the beginning of a continuation line. - last_indent = start - if verbose >= 3: - print("... " + line.rstrip()) - - # record the initial indent. - rel_indent[row] = expand_indent(line) - indent_level - - # identify closing bracket - close_bracket = (token_type == tokenize.OP and text in ']})') - - # is the indent relative to an opening bracket line? - for open_row in reversed(open_rows[depth]): - hang = rel_indent[row] - rel_indent[open_row] - hanging_indent = hang in valid_hangs - if hanging_indent: - break - if hangs[depth]: - hanging_indent = (hang == hangs[depth]) - # is there any chance of visual indent? - visual_indent = (not close_bracket and hang > 0 and - indent_chances.get(start[1])) - - if close_bracket and indent[depth]: - # closing bracket for visual indent - if start[1] != indent[depth]: - yield (start, "E124 closing bracket does not match " - "visual indentation") - elif close_bracket and not hang: - # closing bracket matches indentation of opening bracket's line - if hang_closing: - yield start, "E133 closing bracket is missing indentation" - elif indent[depth] and start[1] < indent[depth]: - if visual_indent is not True: - # visual indent is broken - yield (start, "E128 continuation line " - "under-indented for visual indent") - elif hanging_indent or (indent_next and rel_indent[row] == 8): - # hanging indent is verified - if close_bracket and not hang_closing: - yield (start, "E123 closing bracket does not match " - "indentation of opening bracket's line") - hangs[depth] = hang - elif visual_indent is True: - # visual indent is verified - indent[depth] = start[1] - elif visual_indent in (text, str): - # ignore token lined up with matching one from a previous line - pass - else: - # indent is broken - if hang <= 0: - error = "E122", "missing indentation or outdented" - elif indent[depth]: - error = "E127", "over-indented for visual indent" - elif not close_bracket and hangs[depth]: - error = "E131", "unaligned for hanging indent" - else: - hangs[depth] = hang - if hang > 4: - error = "E126", "over-indented for hanging indent" - else: - error = "E121", "under-indented for hanging indent" - yield start, "%s continuation line %s" % error - - # look for visual indenting - if (parens[row] and - token_type not in (tokenize.NL, tokenize.COMMENT) and - not indent[depth]): - indent[depth] = start[1] - indent_chances[start[1]] = True - if verbose >= 4: - print("bracket depth %s indent to %s" % (depth, start[1])) - # deal with implicit string concatenation - elif (token_type in (tokenize.STRING, tokenize.COMMENT) or - text in ('u', 'ur', 'b', 'br')): - indent_chances[start[1]] = str - # special case for the "if" statement because len("if (") == 4 - elif not indent_chances and not row and not depth and text == 'if': - indent_chances[end[1] + 1] = True - elif text == ':' and line[end[1]:].isspace(): - open_rows[depth].append(row) - - # keep track of bracket depth - if token_type == tokenize.OP: - if text in '([{': - depth += 1 - indent.append(0) - hangs.append(None) - if len(open_rows) == depth: - open_rows.append([]) - open_rows[depth].append(row) - parens[row] += 1 - if verbose >= 4: - print("bracket depth %s seen, col %s, visual min = %s" % - (depth, start[1], indent[depth])) - elif text in ')]}' and depth > 0: - # parent indents should not be more than this one - prev_indent = indent.pop() or last_indent[1] - hangs.pop() - for d in range(depth): - if indent[d] > prev_indent: - indent[d] = 0 - for ind in list(indent_chances): - if ind >= prev_indent: - del indent_chances[ind] - del open_rows[depth + 1:] - depth -= 1 - if depth: - indent_chances[indent[depth]] = True - for idx in range(row, -1, -1): - if parens[idx]: - parens[idx] -= 1 - break - assert len(indent) == depth + 1 - if start[1] not in indent_chances: - # allow lining up tokens - indent_chances[start[1]] = text - - last_token_multiline = (start[0] != end[0]) - if last_token_multiline: - rel_indent[end[0] - first_row] = rel_indent[row] - - if indent_next and expand_indent(line) == indent_level + 4: - pos = (start[0], indent[0] + 4) - if visual_indent: - code = "E129 visually indented line" - else: - code = "E125 continuation line" - yield pos, "%s with same indent as next logical line" % code - - -def whitespace_before_parameters(logical_line, tokens): - r"""Avoid extraneous whitespace. - - Avoid extraneous whitespace in the following situations: - - before the open parenthesis that starts the argument list of a - function call. - - before the open parenthesis that starts an indexing or slicing. - - Okay: spam(1) - E211: spam (1) - - Okay: dict['key'] = list[index] - E211: dict ['key'] = list[index] - E211: dict['key'] = list [index] - """ - prev_type, prev_text, __, prev_end, __ = tokens[0] - for index in range(1, len(tokens)): - token_type, text, start, end, __ = tokens[index] - if (token_type == tokenize.OP and - text in '([' and - start != prev_end and - (prev_type == tokenize.NAME or prev_text in '}])') and - # Syntax "class A (B):" is allowed, but avoid it - (index < 2 or tokens[index - 2][1] != 'class') and - # Allow "return (a.foo for a in range(5))" - not keyword.iskeyword(prev_text)): - yield prev_end, "E211 whitespace before '%s'" % text - prev_type = token_type - prev_text = text - prev_end = end - - -def whitespace_around_operator(logical_line): - r"""Avoid extraneous whitespace around an operator. - - Okay: a = 12 + 3 - E221: a = 4 + 5 - E222: a = 4 + 5 - E223: a = 4\t+ 5 - E224: a = 4 +\t5 - """ - for match in OPERATOR_REGEX.finditer(logical_line): - before, after = match.groups() - - if '\t' in before: - yield match.start(1), "E223 tab before operator" - elif len(before) > 1: - yield match.start(1), "E221 multiple spaces before operator" - - if '\t' in after: - yield match.start(2), "E224 tab after operator" - elif len(after) > 1: - yield match.start(2), "E222 multiple spaces after operator" - - -def missing_whitespace_around_operator(logical_line, tokens): - r"""Surround operators with a single space on either side. - - - Always surround these binary operators with a single space on - either side: assignment (=), augmented assignment (+=, -= etc.), - comparisons (==, <, >, !=, <=, >=, in, not in, is, is not), - Booleans (and, or, not). - - - If operators with different priorities are used, consider adding - whitespace around the operators with the lowest priorities. - - Okay: i = i + 1 - Okay: submitted += 1 - Okay: x = x * 2 - 1 - Okay: hypot2 = x * x + y * y - Okay: c = (a + b) * (a - b) - Okay: foo(bar, key='word', *args, **kwargs) - Okay: alpha[:-i] - - E225: i=i+1 - E225: submitted +=1 - E225: x = x /2 - 1 - E225: z = x **y - E226: c = (a+b) * (a-b) - E226: hypot2 = x*x + y*y - E227: c = a|b - E228: msg = fmt%(errno, errmsg) - """ - parens = 0 - need_space = False - prev_type = tokenize.OP - prev_text = prev_end = None - for token_type, text, start, end, line in tokens: - if token_type in SKIP_COMMENTS: - continue - if text in ('(', 'lambda'): - parens += 1 - elif text == ')': - parens -= 1 - if need_space: - if start != prev_end: - # Found a (probably) needed space - if need_space is not True and not need_space[1]: - yield (need_space[0], - "E225 missing whitespace around operator") - need_space = False - elif text == '>' and prev_text in ('<', '-'): - # Tolerate the "<>" operator, even if running Python 3 - # Deal with Python 3's annotated return value "->" - pass - else: - if need_space is True or need_space[1]: - # A needed trailing space was not found - yield prev_end, "E225 missing whitespace around operator" - elif prev_text != '**': - code, optype = 'E226', 'arithmetic' - if prev_text == '%': - code, optype = 'E228', 'modulo' - elif prev_text not in ARITHMETIC_OP: - code, optype = 'E227', 'bitwise or shift' - yield (need_space[0], "%s missing whitespace " - "around %s operator" % (code, optype)) - need_space = False - elif token_type == tokenize.OP and prev_end is not None: - if text == '=' and parens: - # Allow keyword args or defaults: foo(bar=None). - pass - elif text in WS_NEEDED_OPERATORS: - need_space = True - elif text in UNARY_OPERATORS: - # Check if the operator is being used as a binary operator - # Allow unary operators: -123, -x, +1. - # Allow argument unpacking: foo(*args, **kwargs). - if (prev_text in '}])' if prev_type == tokenize.OP - else prev_text not in KEYWORDS): - need_space = None - elif text in WS_OPTIONAL_OPERATORS: - need_space = None - - if need_space is None: - # Surrounding space is optional, but ensure that - # trailing space matches opening space - need_space = (prev_end, start != prev_end) - elif need_space and start == prev_end: - # A needed opening space was not found - yield prev_end, "E225 missing whitespace around operator" - need_space = False - prev_type = token_type - prev_text = text - prev_end = end - - -def whitespace_around_comma(logical_line): - r"""Avoid extraneous whitespace after a comma or a colon. - - Note: these checks are disabled by default - - Okay: a = (1, 2) - E241: a = (1, 2) - E242: a = (1,\t2) - """ - line = logical_line - for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line): - found = m.start() + 1 - if '\t' in m.group(): - yield found, "E242 tab after '%s'" % m.group()[0] - else: - yield found, "E241 multiple spaces after '%s'" % m.group()[0] - - -def whitespace_around_named_parameter_equals(logical_line, tokens): - r"""Don't use spaces around the '=' sign in function arguments. - - Don't use spaces around the '=' sign when used to indicate a - keyword argument or a default parameter value. - - Okay: def complex(real, imag=0.0): - Okay: return magic(r=real, i=imag) - Okay: boolean(a == b) - Okay: boolean(a != b) - Okay: boolean(a <= b) - Okay: boolean(a >= b) - Okay: def foo(arg: int = 42): - Okay: async def foo(arg: int = 42): - - E251: def complex(real, imag = 0.0): - E251: return magic(r = real, i = imag) - """ - parens = 0 - no_space = False - prev_end = None - annotated_func_arg = False - in_def = logical_line.startswith(('def', 'async def')) - message = "E251 unexpected spaces around keyword / parameter equals" - for token_type, text, start, end, line in tokens: - if token_type == tokenize.NL: - continue - if no_space: - no_space = False - if start != prev_end: - yield (prev_end, message) - if token_type == tokenize.OP: - if text in '([': - parens += 1 - elif text in ')]': - parens -= 1 - elif in_def and text == ':' and parens == 1: - annotated_func_arg = True - elif parens and text == ',' and parens == 1: - annotated_func_arg = False - elif parens and text == '=' and not annotated_func_arg: - no_space = True - if start != prev_end: - yield (prev_end, message) - if not parens: - annotated_func_arg = False - - prev_end = end - - -def whitespace_before_comment(logical_line, tokens): - r"""Separate inline comments by at least two spaces. - - An inline comment is a comment on the same line as a statement. Inline - comments should be separated by at least two spaces from the statement. - They should start with a # and a single space. - - Each line of a block comment starts with a # and a single space - (unless it is indented text inside the comment). - - Okay: x = x + 1 # Increment x - Okay: x = x + 1 # Increment x - Okay: # Block comment - E261: x = x + 1 # Increment x - E262: x = x + 1 #Increment x - E262: x = x + 1 # Increment x - E265: #Block comment - E266: ### Block comment - """ - prev_end = (0, 0) - for token_type, text, start, end, line in tokens: - if token_type == tokenize.COMMENT: - inline_comment = line[:start[1]].strip() - if inline_comment: - if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: - yield (prev_end, - "E261 at least two spaces before inline comment") - symbol, sp, comment = text.partition(' ') - bad_prefix = symbol not in '#:' and (symbol.lstrip('#')[:1] or '#') - if inline_comment: - if bad_prefix or comment[:1] in WHITESPACE: - yield start, "E262 inline comment should start with '# '" - elif bad_prefix and (bad_prefix != '!' or start[0] > 1): - if bad_prefix != '#': - yield start, "E265 block comment should start with '# '" - elif comment: - yield start, "E266 too many leading '#' for block comment" - elif token_type != tokenize.NL: - prev_end = end - - -def imports_on_separate_lines(logical_line): - r"""Place imports on separate lines. - - Okay: import os\nimport sys - E401: import sys, os - - Okay: from subprocess import Popen, PIPE - Okay: from myclas import MyClass - Okay: from foo.bar.yourclass import YourClass - Okay: import myclass - Okay: import foo.bar.yourclass - """ - line = logical_line - if line.startswith('import '): - found = line.find(',') - if -1 < found and ';' not in line[:found]: - yield found, "E401 multiple imports on one line" - - -def module_imports_on_top_of_file( - logical_line, indent_level, checker_state, noqa): - r"""Place imports at the top of the file. - - Always put imports at the top of the file, just after any module comments - and docstrings, and before module globals and constants. - - Okay: import os - Okay: # this is a comment\nimport os - Okay: '''this is a module docstring'''\nimport os - Okay: r'''this is a module docstring'''\nimport os - Okay: try:\n import x\nexcept:\n pass\nelse:\n pass\nimport y - Okay: try:\n import x\nexcept:\n pass\nfinally:\n pass\nimport y - E402: a=1\nimport os - E402: 'One string'\n"Two string"\nimport os - E402: a=1\nfrom sys import x - - Okay: if x:\n import os - """ - def is_string_literal(line): - if line[0] in 'uUbB': - line = line[1:] - if line and line[0] in 'rR': - line = line[1:] - return line and (line[0] == '"' or line[0] == "'") - - allowed_try_keywords = ('try', 'except', 'else', 'finally') - - if indent_level: # Allow imports in conditional statements or functions - return - if not logical_line: # Allow empty lines or comments - return - if noqa: - return - line = logical_line - if line.startswith('import ') or line.startswith('from '): - if checker_state.get('seen_non_imports', False): - yield 0, "E402 module level import not at top of file" - elif any(line.startswith(kw) for kw in allowed_try_keywords): - # Allow try, except, else, finally keywords intermixed with imports in - # order to support conditional importing - return - elif is_string_literal(line): - # The first literal is a docstring, allow it. Otherwise, report error. - if checker_state.get('seen_docstring', False): - checker_state['seen_non_imports'] = True - else: - checker_state['seen_docstring'] = True - else: - checker_state['seen_non_imports'] = True - - -def compound_statements(logical_line): - r"""Compound statements (on the same line) are generally discouraged. - - While sometimes it's okay to put an if/for/while with a small body - on the same line, never do this for multi-clause statements. - Also avoid folding such long lines! - - Always use a def statement instead of an assignment statement that - binds a lambda expression directly to a name. - - Okay: if foo == 'blah':\n do_blah_thing() - Okay: do_one() - Okay: do_two() - Okay: do_three() - - E701: if foo == 'blah': do_blah_thing() - E701: for x in lst: total += x - E701: while t < 10: t = delay() - E701: if foo == 'blah': do_blah_thing() - E701: else: do_non_blah_thing() - E701: try: something() - E701: finally: cleanup() - E701: if foo == 'blah': one(); two(); three() - E702: do_one(); do_two(); do_three() - E703: do_four(); # useless semicolon - E704: def f(x): return 2*x - E731: f = lambda x: 2*x - """ - line = logical_line - last_char = len(line) - 1 - found = line.find(':') - while -1 < found < last_char: - before = line[:found] - if ((before.count('{') <= before.count('}') and # {'a': 1} (dict) - before.count('[') <= before.count(']') and # [1:2] (slice) - before.count('(') <= before.count(')'))): # (annotation) - lambda_kw = LAMBDA_REGEX.search(before) - if lambda_kw: - before = line[:lambda_kw.start()].rstrip() - if before[-1:] == '=' and isidentifier(before[:-1].strip()): - yield 0, ("E731 do not assign a lambda expression, use a " - "def") - break - if before.startswith('def '): - yield 0, "E704 multiple statements on one line (def)" - else: - yield found, "E701 multiple statements on one line (colon)" - found = line.find(':', found + 1) - found = line.find(';') - while -1 < found: - if found < last_char: - yield found, "E702 multiple statements on one line (semicolon)" - else: - yield found, "E703 statement ends with a semicolon" - found = line.find(';', found + 1) - - -def explicit_line_join(logical_line, tokens): - r"""Avoid explicit line join between brackets. - - The preferred way of wrapping long lines is by using Python's implied line - continuation inside parentheses, brackets and braces. Long lines can be - broken over multiple lines by wrapping expressions in parentheses. These - should be used in preference to using a backslash for line continuation. - - E502: aaa = [123, \\n 123] - E502: aaa = ("bbb " \\n "ccc") - - Okay: aaa = [123,\n 123] - Okay: aaa = ("bbb "\n "ccc") - Okay: aaa = "bbb " \\n "ccc" - Okay: aaa = 123 # \\ - """ - prev_start = prev_end = parens = 0 - comment = False - backslash = None - for token_type, text, start, end, line in tokens: - if token_type == tokenize.COMMENT: - comment = True - if start[0] != prev_start and parens and backslash and not comment: - yield backslash, "E502 the backslash is redundant between brackets" - if end[0] != prev_end: - if line.rstrip('\r\n').endswith('\\'): - backslash = (end[0], len(line.splitlines()[-1]) - 1) - else: - backslash = None - prev_start = prev_end = end[0] - else: - prev_start = start[0] - if token_type == tokenize.OP: - if text in '([{': - parens += 1 - elif text in ')]}': - parens -= 1 - - -def break_around_binary_operator(logical_line, tokens): - r""" - Avoid breaks before binary operators. - - The preferred place to break around a binary operator is after the - operator, not before it. - - W503: (width == 0\n + height == 0) - W503: (width == 0\n and height == 0) - - Okay: (width == 0 +\n height == 0) - Okay: foo(\n -x) - Okay: foo(x\n []) - Okay: x = '''\n''' + '' - Okay: foo(x,\n -y) - Okay: foo(x, # comment\n -y) - Okay: var = (1 &\n ~2) - Okay: var = (1 /\n -2) - Okay: var = (1 +\n -1 +\n -2) - """ - def is_binary_operator(token_type, text): - # The % character is strictly speaking a binary operator, but the - # common usage seems to be to put it next to the format parameters, - # after a line break. - return ((token_type == tokenize.OP or text in ['and', 'or']) and - text not in "()[]{},:.;@=%~") - - line_break = False - unary_context = True - # Previous non-newline token types and text - previous_token_type = None - previous_text = None - for token_type, text, start, end, line in tokens: - if token_type == tokenize.COMMENT: - continue - if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: - line_break = True - else: - if (is_binary_operator(token_type, text) and line_break and - not unary_context and - not is_binary_operator(previous_token_type, - previous_text)): - yield start, "W503 line break before binary operator" - unary_context = text in '([{,;' - line_break = False - previous_token_type = token_type - previous_text = text - - -def comparison_to_singleton(logical_line, noqa): - r"""Comparison to singletons should use "is" or "is not". - - Comparisons to singletons like None should always be done - with "is" or "is not", never the equality operators. - - Okay: if arg is not None: - E711: if arg != None: - E711: if None == arg: - E712: if arg == True: - E712: if False == arg: - - Also, beware of writing if x when you really mean if x is not None -- - e.g. when testing whether a variable or argument that defaults to None was - set to some other value. The other value might have a type (such as a - container) that could be false in a boolean context! - """ - match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line) - if match: - singleton = match.group(1) or match.group(3) - same = (match.group(2) == '==') - - msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton) - if singleton in ('None',): - code = 'E711' - else: - code = 'E712' - nonzero = ((singleton == 'True' and same) or - (singleton == 'False' and not same)) - msg += " or 'if %scond:'" % ('' if nonzero else 'not ') - yield match.start(2), ("%s comparison to %s should be %s" % - (code, singleton, msg)) - - -def comparison_negative(logical_line): - r"""Negative comparison should be done using "not in" and "is not". - - Okay: if x not in y:\n pass - Okay: assert (X in Y or X is Z) - Okay: if not (X in Y):\n pass - Okay: zz = x is not y - E713: Z = not X in Y - E713: if not X.B in Y:\n pass - E714: if not X is Y:\n pass - E714: Z = not X.B is Y - """ - match = COMPARE_NEGATIVE_REGEX.search(logical_line) - if match: - pos = match.start(1) - if match.group(2) == 'in': - yield pos, "E713 test for membership should be 'not in'" - else: - yield pos, "E714 test for object identity should be 'is not'" - - -def comparison_type(logical_line, noqa): - r"""Object type comparisons should always use isinstance(). - - Do not compare types directly. - - Okay: if isinstance(obj, int): - E721: if type(obj) is type(1): - - When checking if an object is a string, keep in mind that it might be a - unicode string too! In Python 2.3, str and unicode have a common base - class, basestring, so you can do: - - Okay: if isinstance(obj, basestring): - Okay: if type(a1) is type(b1): - """ - match = COMPARE_TYPE_REGEX.search(logical_line) - if match and not noqa: - inst = match.group(1) - if inst and isidentifier(inst) and inst not in SINGLETONS: - return # Allow comparison for types which are not obvious - yield match.start(), "E721 do not compare types, use 'isinstance()'" - - -def python_3000_has_key(logical_line, noqa): - r"""The {}.has_key() method is removed in Python 3: use the 'in' operator. - - Okay: if "alph" in d:\n print d["alph"] - W601: assert d.has_key('alph') - """ - pos = logical_line.find('.has_key(') - if pos > -1 and not noqa: - yield pos, "W601 .has_key() is deprecated, use 'in'" - - -def python_3000_raise_comma(logical_line): - r"""When raising an exception, use "raise ValueError('message')". - - The older form is removed in Python 3. - - Okay: raise DummyError("Message") - W602: raise DummyError, "Message" - """ - match = RAISE_COMMA_REGEX.match(logical_line) - if match and not RERAISE_COMMA_REGEX.match(logical_line): - yield match.end() - 1, "W602 deprecated form of raising exception" - - -def python_3000_not_equal(logical_line): - r"""New code should always use != instead of <>. - - The older syntax is removed in Python 3. - - Okay: if a != 'no': - W603: if a <> 'no': - """ - pos = logical_line.find('<>') - if pos > -1: - yield pos, "W603 '<>' is deprecated, use '!='" - - -def python_3000_backticks(logical_line): - r"""Use repr() instead of backticks in Python 3. - - Okay: val = repr(1 + 2) - W604: val = `1 + 2` - """ - pos = logical_line.find('`') - if pos > -1: - yield pos, "W604 backticks are deprecated, use 'repr()'" - - -############################################################################## -# Helper functions -############################################################################## - - -if sys.version_info < (3,): - # Python 2: implicit encoding. - def readlines(filename): - """Read the source code.""" - with open(filename, 'rU') as f: - return f.readlines() - isidentifier = re.compile(r'[a-zA-Z_]\w*$').match - stdin_get_value = sys.stdin.read -else: - # Python 3 - def readlines(filename): - """Read the source code.""" - try: - with open(filename, 'rb') as f: - (coding, lines) = tokenize.detect_encoding(f.readline) - f = TextIOWrapper(f, coding, line_buffering=True) - return [l.decode(coding) for l in lines] + f.readlines() - except (LookupError, SyntaxError, UnicodeError): - # Fall back if file encoding is improperly declared - with open(filename, encoding='latin-1') as f: - return f.readlines() - isidentifier = str.isidentifier - - def stdin_get_value(): - """Read the value from stdin.""" - return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() - -noqa = re.compile(r'# no(?:qa|pep8)\b', re.I).search - - -def expand_indent(line): - r"""Return the amount of indentation. - - Tabs are expanded to the next multiple of 8. - - >>> expand_indent(' ') - 4 - >>> expand_indent('\t') - 8 - >>> expand_indent(' \t') - 8 - >>> expand_indent(' \t') - 16 - """ - if '\t' not in line: - return len(line) - len(line.lstrip()) - result = 0 - for char in line: - if char == '\t': - result = result // 8 * 8 + 8 - elif char == ' ': - result += 1 - else: - break - return result - - -def mute_string(text): - """Replace contents with 'xxx' to prevent syntax matching. - - >>> mute_string('"abc"') - '"xxx"' - >>> mute_string("'''abc'''") - "'''xxx'''" - >>> mute_string("r'abc'") - "r'xxx'" - """ - # String modifiers (e.g. u or r) - start = text.index(text[-1]) + 1 - end = len(text) - 1 - # Triple quotes - if text[-3:] in ('"""', "'''"): - start += 2 - end -= 2 - return text[:start] + 'x' * (end - start) + text[end:] - - -def parse_udiff(diff, patterns=None, parent='.'): - """Return a dictionary of matching lines.""" - # For each file of the diff, the entry key is the filename, - # and the value is a set of row numbers to consider. - rv = {} - path = nrows = None - for line in diff.splitlines(): - if nrows: - if line[:1] != '-': - nrows -= 1 - continue - if line[:3] == '@@ ': - hunk_match = HUNK_REGEX.match(line) - (row, nrows) = [int(g or '1') for g in hunk_match.groups()] - rv[path].update(range(row, row + nrows)) - elif line[:3] == '+++': - path = line[4:].split('\t', 1)[0] - if path[:2] == 'b/': - path = path[2:] - rv[path] = set() - return dict([(os.path.join(parent, path), rows) - for (path, rows) in rv.items() - if rows and filename_match(path, patterns)]) - - -def normalize_paths(value, parent=os.curdir): - """Parse a comma-separated list of paths. - - Return a list of absolute paths. - """ - if not value: - return [] - if isinstance(value, list): - return value - paths = [] - for path in value.split(','): - path = path.strip() - if '/' in path: - path = os.path.abspath(os.path.join(parent, path)) - paths.append(path.rstrip('/')) - return paths - - -def filename_match(filename, patterns, default=True): - """Check if patterns contains a pattern that matches filename. - - If patterns is unspecified, this always returns True. - """ - if not patterns: - return default - return any(fnmatch(filename, pattern) for pattern in patterns) - - -def _is_eol_token(token): - return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' -if COMMENT_WITH_NL: - def _is_eol_token(token, _eol_token=_is_eol_token): - return _eol_token(token) or (token[0] == tokenize.COMMENT and - token[1] == token[4]) - -############################################################################## -# Framework to run all checks -############################################################################## - - -_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} - - -def _get_parameters(function): - if sys.version_info >= (3, 3): - return [parameter.name - for parameter - in inspect.signature(function).parameters.values() - if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] - else: - return inspect.getargspec(function)[0] - - -def register_check(check, codes=None): - """Register a new check object.""" - def _add_check(check, kind, codes, args): - if check in _checks[kind]: - _checks[kind][check][0].extend(codes or []) - else: - _checks[kind][check] = (codes or [''], args) - if inspect.isfunction(check): - args = _get_parameters(check) - if args and args[0] in ('physical_line', 'logical_line'): - if codes is None: - codes = ERRORCODE_REGEX.findall(check.__doc__ or '') - _add_check(check, args[0], codes, args) - elif inspect.isclass(check): - if _get_parameters(check.__init__)[:2] == ['self', 'tree']: - _add_check(check, 'tree', codes, None) - - -def init_checks_registry(): - """Register all globally visible functions. - - The first argument name is either 'physical_line' or 'logical_line'. - """ - mod = inspect.getmodule(register_check) - for (name, function) in inspect.getmembers(mod, inspect.isfunction): - register_check(function) -init_checks_registry() - - -class Checker(object): - """Load a Python source file, tokenize it, check coding style.""" - - def __init__(self, filename=None, lines=None, - options=None, report=None, **kwargs): - if options is None: - options = StyleGuide(kwargs).options - else: - assert not kwargs - self._io_error = None - self._physical_checks = options.physical_checks - self._logical_checks = options.logical_checks - self._ast_checks = options.ast_checks - self.max_line_length = options.max_line_length - self.multiline = False # in a multiline string? - self.hang_closing = options.hang_closing - self.verbose = options.verbose - self.filename = filename - # Dictionary where a checker can store its custom state. - self._checker_states = {} - if filename is None: - self.filename = 'stdin' - self.lines = lines or [] - elif filename == '-': - self.filename = 'stdin' - self.lines = stdin_get_value().splitlines(True) - elif lines is None: - try: - self.lines = readlines(filename) - except IOError: - (exc_type, exc) = sys.exc_info()[:2] - self._io_error = '%s: %s' % (exc_type.__name__, exc) - self.lines = [] - else: - self.lines = lines - if self.lines: - ord0 = ord(self.lines[0][0]) - if ord0 in (0xef, 0xfeff): # Strip the UTF-8 BOM - if ord0 == 0xfeff: - self.lines[0] = self.lines[0][1:] - elif self.lines[0][:3] == '\xef\xbb\xbf': - self.lines[0] = self.lines[0][3:] - self.report = report or options.report - self.report_error = self.report.error - - def report_invalid_syntax(self): - """Check if the syntax is valid.""" - (exc_type, exc) = sys.exc_info()[:2] - if len(exc.args) > 1: - offset = exc.args[1] - if len(offset) > 2: - offset = offset[1:3] - else: - offset = (1, 0) - self.report_error(offset[0], offset[1] or 0, - 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), - self.report_invalid_syntax) - - def readline(self): - """Get the next line from the input buffer.""" - if self.line_number >= self.total_lines: - return '' - line = self.lines[self.line_number] - self.line_number += 1 - if self.indent_char is None and line[:1] in WHITESPACE: - self.indent_char = line[0] - return line - - def run_check(self, check, argument_names): - """Run a check plugin.""" - arguments = [] - for name in argument_names: - arguments.append(getattr(self, name)) - return check(*arguments) - - def init_checker_state(self, name, argument_names): - """Prepare custom state for the specific checker plugin.""" - if 'checker_state' in argument_names: - self.checker_state = self._checker_states.setdefault(name, {}) - - def check_physical(self, line): - """Run all physical checks on a raw input line.""" - self.physical_line = line - for name, check, argument_names in self._physical_checks: - self.init_checker_state(name, argument_names) - result = self.run_check(check, argument_names) - if result is not None: - (offset, text) = result - self.report_error(self.line_number, offset, text, check) - if text[:4] == 'E101': - self.indent_char = line[0] - - def build_tokens_line(self): - """Build a logical line from tokens.""" - logical = [] - comments = [] - length = 0 - prev_row = prev_col = mapping = None - for token_type, text, start, end, line in self.tokens: - if token_type in SKIP_TOKENS: - continue - if not mapping: - mapping = [(0, start)] - if token_type == tokenize.COMMENT: - comments.append(text) - continue - if token_type == tokenize.STRING: - text = mute_string(text) - if prev_row: - (start_row, start_col) = start - if prev_row != start_row: # different row - prev_text = self.lines[prev_row - 1][prev_col - 1] - if prev_text == ',' or (prev_text not in '{[(' and - text not in '}])'): - text = ' ' + text - elif prev_col != start_col: # different column - text = line[prev_col:start_col] + text - logical.append(text) - length += len(text) - mapping.append((length, end)) - (prev_row, prev_col) = end - self.logical_line = ''.join(logical) - self.noqa = comments and noqa(''.join(comments)) - return mapping - - def check_logical(self): - """Build a line from tokens and run all logical checks on it.""" - self.report.increment_logical_line() - mapping = self.build_tokens_line() - - if not mapping: - return - - (start_row, start_col) = mapping[0][1] - start_line = self.lines[start_row - 1] - self.indent_level = expand_indent(start_line[:start_col]) - if self.blank_before < self.blank_lines: - self.blank_before = self.blank_lines - if self.verbose >= 2: - print(self.logical_line[:80].rstrip()) - for name, check, argument_names in self._logical_checks: - if self.verbose >= 4: - print(' ' + name) - self.init_checker_state(name, argument_names) - for offset, text in self.run_check(check, argument_names) or (): - if not isinstance(offset, tuple): - for token_offset, pos in mapping: - if offset <= token_offset: - break - offset = (pos[0], pos[1] + offset - token_offset) - self.report_error(offset[0], offset[1], text, check) - if self.logical_line: - self.previous_indent_level = self.indent_level - self.previous_logical = self.logical_line - self.blank_lines = 0 - self.tokens = [] - - def check_ast(self): - """Build the file's AST and run all AST checks.""" - try: - tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) - except (ValueError, SyntaxError, TypeError): - return self.report_invalid_syntax() - for name, cls, __ in self._ast_checks: - checker = cls(tree, self.filename) - for lineno, offset, text, check in checker.run(): - if not self.lines or not noqa(self.lines[lineno - 1]): - self.report_error(lineno, offset, text, check) - - def generate_tokens(self): - """Tokenize the file, run physical line checks and yield tokens.""" - if self._io_error: - self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) - tokengen = tokenize.generate_tokens(self.readline) - try: - for token in tokengen: - if token[2][0] > self.total_lines: - return - self.maybe_check_physical(token) - yield token - except (SyntaxError, tokenize.TokenError): - self.report_invalid_syntax() - - def maybe_check_physical(self, token): - """If appropriate (based on token), check current physical line(s).""" - # Called after every token, but act only on end of line. - if _is_eol_token(token): - # Obviously, a newline token ends a single physical line. - self.check_physical(token[4]) - elif token[0] == tokenize.STRING and '\n' in token[1]: - # Less obviously, a string that contains newlines is a - # multiline string, either triple-quoted or with internal - # newlines backslash-escaped. Check every physical line in the - # string *except* for the last one: its newline is outside of - # the multiline string, so we consider it a regular physical - # line, and will check it like any other physical line. - # - # Subtleties: - # - we don't *completely* ignore the last line; if it contains - # the magical "# noqa" comment, we disable all physical - # checks for the entire multiline string - # - have to wind self.line_number back because initially it - # points to the last line of the string, and we want - # check_physical() to give accurate feedback - if noqa(token[4]): - return - self.multiline = True - self.line_number = token[2][0] - for line in token[1].split('\n')[:-1]: - self.check_physical(line + '\n') - self.line_number += 1 - self.multiline = False - - def check_all(self, expected=None, line_offset=0): - """Run all checks on the input file.""" - self.report.init_file(self.filename, self.lines, expected, line_offset) - self.total_lines = len(self.lines) - if self._ast_checks: - self.check_ast() - self.line_number = 0 - self.indent_char = None - self.indent_level = self.previous_indent_level = 0 - self.previous_logical = '' - self.tokens = [] - self.blank_lines = self.blank_before = 0 - parens = 0 - for token in self.generate_tokens(): - self.tokens.append(token) - token_type, text = token[0:2] - if self.verbose >= 3: - if token[2][0] == token[3][0]: - pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) - else: - pos = 'l.%s' % token[3][0] - print('l.%s\t%s\t%s\t%r' % - (token[2][0], pos, tokenize.tok_name[token[0]], text)) - if token_type == tokenize.OP: - if text in '([{': - parens += 1 - elif text in '}])': - parens -= 1 - elif not parens: - if token_type in NEWLINE: - if token_type == tokenize.NEWLINE: - self.check_logical() - self.blank_before = 0 - elif len(self.tokens) == 1: - # The physical line contains only this token. - self.blank_lines += 1 - del self.tokens[0] - else: - self.check_logical() - elif COMMENT_WITH_NL and token_type == tokenize.COMMENT: - if len(self.tokens) == 1: - # The comment also ends a physical line - token = list(token) - token[1] = text.rstrip('\r\n') - token[3] = (token[2][0], token[2][1] + len(token[1])) - self.tokens = [tuple(token)] - self.check_logical() - if self.tokens: - self.check_physical(self.lines[-1]) - self.check_logical() - return self.report.get_file_results() - - -class BaseReport(object): - """Collect the results of the checks.""" - - print_filename = False - - def __init__(self, options): - self._benchmark_keys = options.benchmark_keys - self._ignore_code = options.ignore_code - # Results - self.elapsed = 0 - self.total_errors = 0 - self.counters = dict.fromkeys(self._benchmark_keys, 0) - self.messages = {} - - def start(self): - """Start the timer.""" - self._start_time = time.time() - - def stop(self): - """Stop the timer.""" - self.elapsed = time.time() - self._start_time - - def init_file(self, filename, lines, expected, line_offset): - """Signal a new file.""" - self.filename = filename - self.lines = lines - self.expected = expected or () - self.line_offset = line_offset - self.file_errors = 0 - self.counters['files'] += 1 - self.counters['physical lines'] += len(lines) - - def increment_logical_line(self): - """Signal a new logical line.""" - self.counters['logical lines'] += 1 - - def error(self, line_number, offset, text, check): - """Report an error, according to options.""" - code = text[:4] - if self._ignore_code(code): - return - if code in self.counters: - self.counters[code] += 1 - else: - self.counters[code] = 1 - self.messages[code] = text[5:] - # Don't care about expected errors or warnings - if code in self.expected: - return - if self.print_filename and not self.file_errors: - print(self.filename) - self.file_errors += 1 - self.total_errors += 1 - return code - - def get_file_results(self): - """Return the count of errors and warnings for this file.""" - return self.file_errors - - def get_count(self, prefix=''): - """Return the total count of errors and warnings.""" - return sum([self.counters[key] - for key in self.messages if key.startswith(prefix)]) - - def get_statistics(self, prefix=''): - """Get statistics for message codes that start with the prefix. - - prefix='' matches all errors and warnings - prefix='E' matches all errors - prefix='W' matches all warnings - prefix='E4' matches all errors that have to do with imports - """ - return ['%-7s %s %s' % (self.counters[key], key, self.messages[key]) - for key in sorted(self.messages) if key.startswith(prefix)] - - def print_statistics(self, prefix=''): - """Print overall statistics (number of errors and warnings).""" - for line in self.get_statistics(prefix): - print(line) - - def print_benchmark(self): - """Print benchmark numbers.""" - print('%-7.2f %s' % (self.elapsed, 'seconds elapsed')) - if self.elapsed: - for key in self._benchmark_keys: - print('%-7d %s per second (%d total)' % - (self.counters[key] / self.elapsed, key, - self.counters[key])) - - -class FileReport(BaseReport): - """Collect the results of the checks and print only the filenames.""" - - print_filename = True - - -class StandardReport(BaseReport): - """Collect and print the results of the checks.""" - - def __init__(self, options): - super(StandardReport, self).__init__(options) - self._fmt = REPORT_FORMAT.get(options.format.lower(), - options.format) - self._repeat = options.repeat - self._show_source = options.show_source - self._show_pep8 = options.show_pep8 - - def init_file(self, filename, lines, expected, line_offset): - """Signal a new file.""" - self._deferred_print = [] - return super(StandardReport, self).init_file( - filename, lines, expected, line_offset) - - def error(self, line_number, offset, text, check): - """Report an error, according to options.""" - code = super(StandardReport, self).error(line_number, offset, - text, check) - if code and (self.counters[code] == 1 or self._repeat): - self._deferred_print.append( - (line_number, offset, code, text[5:], check.__doc__)) - return code - - def get_file_results(self): - """Print the result and return the overall count for this file.""" - self._deferred_print.sort() - for line_number, offset, code, text, doc in self._deferred_print: - print(self._fmt % { - 'path': self.filename, - 'row': self.line_offset + line_number, 'col': offset + 1, - 'code': code, 'text': text, - }) - if self._show_source: - if line_number > len(self.lines): - line = '' - else: - line = self.lines[line_number - 1] - print(line.rstrip()) - print(re.sub(r'\S', ' ', line[:offset]) + '^') - if self._show_pep8 and doc: - print(' ' + doc.strip()) - - # stdout is block buffered when not stdout.isatty(). - # line can be broken where buffer boundary since other processes - # write to same file. - # flush() after print() to avoid buffer boundary. - # Typical buffer size is 8192. line written safely when - # len(line) < 8192. - sys.stdout.flush() - return self.file_errors - - -class DiffReport(StandardReport): - """Collect and print the results for the changed lines only.""" - - def __init__(self, options): - super(DiffReport, self).__init__(options) - self._selected = options.selected_lines - - def error(self, line_number, offset, text, check): - if line_number not in self._selected[self.filename]: - return - return super(DiffReport, self).error(line_number, offset, text, check) - - -class StyleGuide(object): - """Initialize a PEP-8 instance with few options.""" - - def __init__(self, *args, **kwargs): - # build options from the command line - self.checker_class = kwargs.pop('checker_class', Checker) - parse_argv = kwargs.pop('parse_argv', False) - config_file = kwargs.pop('config_file', False) - parser = kwargs.pop('parser', None) - # build options from dict - options_dict = dict(*args, **kwargs) - arglist = None if parse_argv else options_dict.get('paths', None) - options, self.paths = process_options( - arglist, parse_argv, config_file, parser) - if options_dict: - options.__dict__.update(options_dict) - if 'paths' in options_dict: - self.paths = options_dict['paths'] - - self.runner = self.input_file - self.options = options - - if not options.reporter: - options.reporter = BaseReport if options.quiet else StandardReport - - options.select = tuple(options.select or ()) - if not (options.select or options.ignore or - options.testsuite or options.doctest) and DEFAULT_IGNORE: - # The default choice: ignore controversial checks - options.ignore = tuple(DEFAULT_IGNORE.split(',')) - else: - # Ignore all checks which are not explicitly selected - options.ignore = ('',) if options.select else tuple(options.ignore) - options.benchmark_keys = BENCHMARK_KEYS[:] - options.ignore_code = self.ignore_code - options.physical_checks = self.get_checks('physical_line') - options.logical_checks = self.get_checks('logical_line') - options.ast_checks = self.get_checks('tree') - self.init_report() - - def init_report(self, reporter=None): - """Initialize the report instance.""" - self.options.report = (reporter or self.options.reporter)(self.options) - return self.options.report - - def check_files(self, paths=None): - """Run all checks on the paths.""" - if paths is None: - paths = self.paths - report = self.options.report - runner = self.runner - report.start() - try: - for path in paths: - if os.path.isdir(path): - self.input_dir(path) - elif not self.excluded(path): - runner(path) - except KeyboardInterrupt: - print('... stopped') - report.stop() - return report - - 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) - return fchecker.check_all(expected=expected, line_offset=line_offset) - - def input_dir(self, dirname): - """Check all files in this directory and all subdirectories.""" - dirname = dirname.rstrip('/') - if self.excluded(dirname): - return 0 - counters = self.options.report.counters - verbose = self.options.verbose - filepatterns = self.options.filename - runner = self.runner - for root, dirs, files in os.walk(dirname): - if verbose: - print('directory ' + root) - counters['directories'] += 1 - for subdir in sorted(dirs): - if self.excluded(subdir, root): - dirs.remove(subdir) - for filename in sorted(files): - # contain a pattern that matches? - if ((filename_match(filename, filepatterns) and - not self.excluded(filename, root))): - runner(os.path.join(root, filename)) - - def excluded(self, filename, parent=None): - """Check if the file should be excluded. - - Check if 'options.exclude' contains a pattern that matches filename. - """ - if not self.options.exclude: - return False - basename = os.path.basename(filename) - if filename_match(basename, self.options.exclude): - return True - if parent: - filename = os.path.join(parent, filename) - filename = os.path.abspath(filename) - return filename_match(filename, self.options.exclude) - - def ignore_code(self, code): - """Check if the error code should be ignored. - - If 'options.select' contains a prefix of the error code, - return False. Else, if 'options.ignore' contains a prefix of - the error code, return True. - """ - if len(code) < 4 and any(s.startswith(code) - for s in self.options.select): - return False - return (code.startswith(self.options.ignore) and - not code.startswith(self.options.select)) - - def get_checks(self, argument_name): - """Get all the checks for this category. - - Find all globally visible functions where the first argument name - starts with argument_name and which contain selected tests. - """ - checks = [] - for check, attrs in _checks[argument_name].items(): - (codes, args) = attrs - if any(not (code and self.ignore_code(code)) for code in codes): - checks.append((check.__name__, check, args)) - return sorted(checks) - - -def get_parser(prog='pep8', version=__version__): - """Create the parser for the program.""" - parser = OptionParser(prog=prog, version=version, - usage="%prog [options] input ...") - parser.config_options = [ - 'exclude', 'filename', 'select', 'ignore', 'max-line-length', - 'hang-closing', 'count', 'format', 'quiet', 'show-pep8', - 'show-source', 'statistics', 'verbose'] - parser.add_option('-v', '--verbose', default=0, action='count', - help="print status messages, or debug with -vv") - parser.add_option('-q', '--quiet', default=0, action='count', - help="report only file names, or nothing with -qq") - parser.add_option('-r', '--repeat', default=True, action='store_true', - help="(obsolete) show all occurrences of the same error") - parser.add_option('--first', action='store_false', dest='repeat', - help="show first occurrence of each error") - parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, - help="exclude files or directories which match these " - "comma separated patterns (default: %default)") - parser.add_option('--filename', metavar='patterns', default='*.py', - help="when parsing directories, only check filenames " - "matching these comma separated patterns " - "(default: %default)") - parser.add_option('--select', metavar='errors', default='', - help="select errors and warnings (e.g. E,W6)") - parser.add_option('--ignore', metavar='errors', default='', - help="skip errors and warnings (e.g. E4,W) " - "(default: %s)" % DEFAULT_IGNORE) - parser.add_option('--show-source', action='store_true', - help="show source code for each error") - parser.add_option('--show-pep8', action='store_true', - help="show text of PEP 8 for each error " - "(implies --first)") - parser.add_option('--statistics', action='store_true', - help="count errors and warnings") - parser.add_option('--count', action='store_true', - help="print total number of errors and warnings " - "to standard error and set exit code to 1 if " - "total is not null") - parser.add_option('--max-line-length', type='int', metavar='n', - default=MAX_LINE_LENGTH, - help="set maximum allowed line length " - "(default: %default)") - parser.add_option('--hang-closing', action='store_true', - help="hang closing bracket instead of matching " - "indentation of opening bracket's line") - parser.add_option('--format', metavar='format', default='default', - help="set the error format [default|pylint|]") - parser.add_option('--diff', action='store_true', - help="report changes only within line number ranges in " - "the unified diff received on STDIN") - group = parser.add_option_group("Testing Options") - if os.path.exists(TESTSUITE_PATH): - group.add_option('--testsuite', metavar='dir', - help="run regression tests from dir") - group.add_option('--doctest', action='store_true', - help="run doctest on myself") - group.add_option('--benchmark', action='store_true', - help="measure processing speed") - return parser - - -def read_config(options, args, arglist, parser): - """Read and parse configurations. - - If a config file is specified on the command line with the "--config" - option, then only it is used for configuration. - - Otherwise, the user configuration (~/.config/pep8) and any local - configurations in the current directory or above will be merged together - (in that order) using the read method of ConfigParser. - """ - config = RawConfigParser() - - cli_conf = options.config - - local_dir = os.curdir - - if USER_CONFIG and os.path.isfile(USER_CONFIG): - if options.verbose: - print('user configuration: %s' % USER_CONFIG) - config.read(USER_CONFIG) - - parent = tail = args and os.path.abspath(os.path.commonprefix(args)) - while tail: - if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): - local_dir = parent - if options.verbose: - print('local configuration: in %s' % parent) - break - (parent, tail) = os.path.split(parent) - - if cli_conf and os.path.isfile(cli_conf): - if options.verbose: - print('cli configuration: %s' % cli_conf) - config.read(cli_conf) - - pep8_section = parser.prog - if config.has_section(pep8_section): - option_list = dict([(o.dest, o.type or o.action) - for o in parser.option_list]) - - # First, read the default values - (new_options, __) = parser.parse_args([]) - - # Second, parse the configuration - for opt in config.options(pep8_section): - if opt.replace('_', '-') not in parser.config_options: - print(" unknown option '%s' ignored" % opt) - continue - if options.verbose > 1: - print(" %s = %s" % (opt, config.get(pep8_section, opt))) - normalized_opt = opt.replace('-', '_') - opt_type = option_list[normalized_opt] - if opt_type in ('int', 'count'): - value = config.getint(pep8_section, opt) - elif opt_type == 'string': - value = config.get(pep8_section, opt) - if normalized_opt == 'exclude': - value = normalize_paths(value, local_dir) - else: - assert opt_type in ('store_true', 'store_false') - value = config.getboolean(pep8_section, opt) - setattr(new_options, normalized_opt, value) - - # Third, overwrite with the command-line options - (options, __) = parser.parse_args(arglist, values=new_options) - options.doctest = options.testsuite = False - return options - - -def process_options(arglist=None, parse_argv=False, config_file=None, - parser=None): - """Process options passed either via arglist or via command line args. - - Passing in the ``config_file`` parameter allows other tools, such as flake8 - to specify their own options to be processed in pep8. - """ - if not parser: - parser = get_parser() - if not parser.has_option('--config'): - group = parser.add_option_group("Configuration", description=( - "The project options are read from the [%s] section of the " - "tox.ini file or the setup.cfg file located in any parent folder " - "of the path(s) being processed. Allowed options are: %s." % - (parser.prog, ', '.join(parser.config_options)))) - group.add_option('--config', metavar='path', default=config_file, - help="user config file location") - # Don't read the command line if the module is used as a library. - if not arglist and not parse_argv: - arglist = [] - # If parse_argv is True and arglist is None, arguments are - # parsed from the command line (sys.argv) - (options, args) = parser.parse_args(arglist) - options.reporter = None - - if options.ensure_value('testsuite', False): - args.append(options.testsuite) - elif not options.ensure_value('doctest', False): - if parse_argv and not args: - if options.diff or any(os.path.exists(name) - for name in PROJECT_CONFIG): - args = ['.'] - else: - parser.error('input not specified') - options = read_config(options, args, arglist, parser) - options.reporter = parse_argv and options.quiet == 1 and FileReport - - options.filename = _parse_multi_options(options.filename) - options.exclude = normalize_paths(options.exclude) - options.select = _parse_multi_options(options.select) - options.ignore = _parse_multi_options(options.ignore) - - if options.diff: - options.reporter = DiffReport - stdin = stdin_get_value() - options.selected_lines = parse_udiff(stdin, options.filename, args[0]) - args = sorted(options.selected_lines) - - return options, args - - -def _parse_multi_options(options, split_token=','): - r"""Split and strip and discard empties. - - Turns the following: - - A, - B, - - into ["A", "B"] - """ - if options: - return [o.strip() for o in options.split(split_token) if o.strip()] - else: - return options - - -def _main(): - """Parse options and run checks on Python source.""" - import signal - - # Handle "Broken pipe" gracefully - try: - signal.signal(signal.SIGPIPE, lambda signum, frame: sys.exit(1)) - except AttributeError: - pass # not supported on Windows - - style_guide = StyleGuide(parse_argv=True) - options = style_guide.options - - if options.doctest or options.testsuite: - from testsuite.support import run_tests - report = run_tests(style_guide) - else: - report = style_guide.check_files() - - if options.statistics: - report.print_statistics() - - if options.benchmark: - report.print_benchmark() - - if options.testsuite and not options.quiet: - report.print_results() - - if report.total_errors: - if options.count: - sys.stderr.write(str(report.total_errors) + '\n') - sys.exit(1) - - -def _main_pep8(): - """Entrypoint for pep8 commandline tool. - - Warn of deprecation and advise users to switch to pycodestyle. - """ - print( - 'Deprecation Warning:\n' - 'pep8 has been renamed to pycodestyle and the use of the pep8 ' - 'executable will be removed in a future release. Please use ' - '`pycodestyle` instead.\n' - ) - _main() - - -if __name__ == '__main__': - _main() diff --git a/pycodestyle.py b/pycodestyle.py new file mode 100755 index 0000000..6776303 --- /dev/null +++ b/pycodestyle.py @@ -0,0 +1,2200 @@ +#!/usr/bin/env python +# pep8.py - Check Python source code formatting, according to PEP 8 +# Copyright (C) 2006-2009 Johann C. Rocholl +# Copyright (C) 2009-2014 Florent Xicluna +# Copyright (C) 2014-2016 Ian Lee +# +# 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. + +r""" +Check Python source code formatting, according to PEP 8. + +For usage and a list of options, try this: +$ python pep8.py -h + +This program and its regression test suite live here: +https://github.com/pycqa/pycodestyle + +Groups of errors and warnings: +E errors +W warnings +100 indentation +200 whitespace +300 blank lines +400 imports +500 line length +600 deprecation +700 statements +900 syntax error +""" +from __future__ import with_statement + +import os +import sys +import re +import time +import inspect +import keyword +import tokenize +from optparse import OptionParser +from fnmatch import fnmatch +try: + from configparser import RawConfigParser + from io import TextIOWrapper +except ImportError: + from ConfigParser import RawConfigParser + +__version__ = '1.8.0-dev' + +DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' +DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' +try: + if sys.platform == 'win32': + USER_CONFIG = os.path.expanduser(r'~\.pep8') + else: + USER_CONFIG = os.path.join( + os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), + 'pep8' + ) +except ImportError: + USER_CONFIG = None + +PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8') +TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') +MAX_LINE_LENGTH = 79 +REPORT_FORMAT = { + 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', + 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', +} + +PyCF_ONLY_AST = 1024 +SINGLETONS = frozenset(['False', 'None', 'True']) +KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS +UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) +ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-']) +WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) +WS_NEEDED_OPERATORS = frozenset([ + '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', + '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=']) +WHITESPACE = frozenset(' \t') +NEWLINE = frozenset([tokenize.NL, tokenize.NEWLINE]) +SKIP_TOKENS = NEWLINE.union([tokenize.INDENT, tokenize.DEDENT]) +# ERRORTOKEN is triggered by backticks in Python 3 +SKIP_COMMENTS = SKIP_TOKENS.union([tokenize.COMMENT, tokenize.ERRORTOKEN]) +BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] + +INDENT_REGEX = re.compile(r'([ \t]*)') +RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,') +RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,.*,\s*\w+\s*$') +ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') +DOCSTRING_REGEX = re.compile(r'u?r?["\']') +EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') +WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') +COMPARE_SINGLETON_REGEX = re.compile(r'(\bNone|\bFalse|\bTrue)?\s*([=!]=)' + r'\s*(?(1)|(None|False|True))\b') +COMPARE_NEGATIVE_REGEX = re.compile(r'\b(not)\s+[^][)(}{ ]+\s+(in|is)\s') +COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type' + r'|\s*\(\s*([^)]*[^ )])\s*\))') +KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) +OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') +LAMBDA_REGEX = re.compile(r'\blambda\b') +HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') + +# Work around Python < 2.6 behaviour, which does not generate NL after +# a comment which is on a line by itself. +COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' + + +############################################################################## +# Plugins (check functions) for physical lines +############################################################################## + + +def tabs_or_spaces(physical_line, indent_char): + r"""Never mix tabs and spaces. + + The most popular way of indenting Python is with spaces only. The + second-most popular way is with tabs only. Code indented with a mixture + of tabs and spaces should be converted to using spaces exclusively. When + invoking the Python command line interpreter with the -t option, it issues + warnings about code that illegally mixes tabs and spaces. When using -tt + these warnings become errors. These options are highly recommended! + + Okay: if a == 0:\n a = 1\n b = 1 + E101: if a == 0:\n a = 1\n\tb = 1 + """ + indent = INDENT_REGEX.match(physical_line).group(1) + for offset, char in enumerate(indent): + if char != indent_char: + return offset, "E101 indentation contains mixed spaces and tabs" + + +def tabs_obsolete(physical_line): + r"""For new projects, spaces-only are strongly recommended over tabs. + + Okay: if True:\n return + W191: if True:\n\treturn + """ + indent = INDENT_REGEX.match(physical_line).group(1) + if '\t' in indent: + return indent.index('\t'), "W191 indentation contains tabs" + + +def trailing_whitespace(physical_line): + r"""Trailing whitespace is superfluous. + + The warning returned varies on whether the line itself is blank, for easier + filtering for those who want to indent their blank lines. + + Okay: spam(1)\n# + W291: spam(1) \n# + W293: class Foo(object):\n \n bang = 12 + """ + physical_line = physical_line.rstrip('\n') # chr(10), newline + physical_line = physical_line.rstrip('\r') # chr(13), carriage return + physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L + stripped = physical_line.rstrip(' \t\v') + if physical_line != stripped: + if stripped: + return len(stripped), "W291 trailing whitespace" + else: + return 0, "W293 blank line contains whitespace" + + +def trailing_blank_lines(physical_line, lines, line_number, total_lines): + r"""Trailing blank lines are superfluous. + + Okay: spam(1) + W391: spam(1)\n + + However the last line should end with a new line (warning W292). + """ + if line_number == total_lines: + stripped_last_line = physical_line.rstrip() + if not stripped_last_line: + return 0, "W391 blank line at end of file" + if stripped_last_line == physical_line: + return len(physical_line), "W292 no newline at end of file" + + +def maximum_line_length(physical_line, max_line_length, multiline): + r"""Limit all lines to a maximum of 79 characters. + + There are still many devices around that are limited to 80 character + lines; plus, limiting windows to 80 characters makes it possible to have + several windows side-by-side. The default wrapping on such devices looks + ugly. Therefore, please limit all lines to a maximum of 79 characters. + For flowing long blocks of text (docstrings or comments), limiting the + length to 72 characters is recommended. + + Reports error E501. + """ + line = physical_line.rstrip() + length = len(line) + if length > max_line_length and not noqa(line): + # Special case for long URLs in multi-line docstrings or comments, + # but still report the error when the 72 first chars are whitespaces. + chunks = line.split() + if ((len(chunks) == 1 and multiline) or + (len(chunks) == 2 and chunks[0] == '#')) and \ + len(line) - len(chunks[-1]) < max_line_length - 7: + return + if hasattr(line, 'decode'): # Python 2 + # The line could contain multi-byte characters + try: + length = len(line.decode('utf-8')) + except UnicodeError: + pass + if length > max_line_length: + return (max_line_length, "E501 line too long " + "(%d > %d characters)" % (length, max_line_length)) + + +############################################################################## +# Plugins (check functions) for logical lines +############################################################################## + + +def blank_lines(logical_line, blank_lines, indent_level, line_number, + blank_before, previous_logical, previous_indent_level): + r"""Separate top-level function and class definitions with two blank lines. + + Method definitions inside a class are separated by a single blank line. + + Extra blank lines may be used (sparingly) to separate groups of related + functions. Blank lines may be omitted between a bunch of related + one-liners (e.g. a set of dummy implementations). + + Use blank lines in functions, sparingly, to indicate logical sections. + + Okay: def a():\n pass\n\n\ndef b():\n pass + Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass + + E301: class Foo:\n b = 0\n def bar():\n pass + E302: def a():\n pass\n\ndef b(n):\n pass + E303: def a():\n pass\n\n\n\ndef b(n):\n pass + E303: def a():\n\n\n\n pass + E304: @decorator\n\ndef a():\n pass + """ + if line_number < 3 and not previous_logical: + return # Don't expect blank lines before the first line + if previous_logical.startswith('@'): + if blank_lines: + yield 0, "E304 blank lines found after function decorator" + elif blank_lines > 2 or (indent_level and blank_lines == 2): + yield 0, "E303 too many blank lines (%d)" % blank_lines + elif logical_line.startswith(('def ', 'class ', '@')): + if indent_level: + if not (blank_before or previous_indent_level < indent_level or + DOCSTRING_REGEX.match(previous_logical)): + yield 0, "E301 expected 1 blank line, found 0" + elif blank_before != 2: + yield 0, "E302 expected 2 blank lines, found %d" % blank_before + + +def extraneous_whitespace(logical_line): + r"""Avoid extraneous whitespace. + + Avoid extraneous whitespace in these situations: + - Immediately inside parentheses, brackets or braces. + - Immediately before a comma, semicolon, or colon. + + Okay: spam(ham[1], {eggs: 2}) + E201: spam( ham[1], {eggs: 2}) + E201: spam(ham[ 1], {eggs: 2}) + E201: spam(ham[1], { eggs: 2}) + E202: spam(ham[1], {eggs: 2} ) + E202: spam(ham[1 ], {eggs: 2}) + E202: spam(ham[1], {eggs: 2 }) + + E203: if x == 4: print x, y; x, y = y , x + E203: if x == 4: print x, y ; x, y = y, x + E203: if x == 4 : print x, y; x, y = y, x + """ + line = logical_line + for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): + text = match.group() + char = text.strip() + found = match.start() + if text == char + ' ': + # assert char in '([{' + yield found + 1, "E201 whitespace after '%s'" % char + elif line[found - 1] != ',': + code = ('E202' if char in '}])' else 'E203') # if char in ',;:' + yield found, "%s whitespace before '%s'" % (code, char) + + +def whitespace_around_keywords(logical_line): + r"""Avoid extraneous whitespace around keywords. + + Okay: True and False + E271: True and False + E272: True and False + E273: True and\tFalse + E274: True\tand False + """ + for match in KEYWORD_REGEX.finditer(logical_line): + before, after = match.groups() + + if '\t' in before: + yield match.start(1), "E274 tab before keyword" + elif len(before) > 1: + yield match.start(1), "E272 multiple spaces before keyword" + + if '\t' in after: + yield match.start(2), "E273 tab after keyword" + elif len(after) > 1: + yield match.start(2), "E271 multiple spaces after keyword" + + +def missing_whitespace_after_import_keyword(logical_line): + r"""Multiple imports in form from x import (a, b, c) should have space + between import statement and parenthesised name list. + + Okay: from foo import (bar, baz) + E275: from foo import(bar, baz) + E275: from importable.module import(bar, baz) + """ + line = logical_line + indicator = ' import(' + if line.startswith('from '): + found = line.find(indicator) + if -1 < found: + pos = found + len(indicator) - 1 + yield pos, "E275 missing whitespace after keyword" + + +def missing_whitespace(logical_line): + r"""Each comma, semicolon or colon should be followed by whitespace. + + Okay: [a, b] + Okay: (3,) + Okay: a[1:4] + Okay: a[:4] + Okay: a[1:] + Okay: a[1:4:2] + E231: ['a','b'] + E231: foo(bar,baz) + E231: [{'a':'b'}] + """ + line = logical_line + for index in range(len(line) - 1): + char = line[index] + if char in ',;:' and line[index + 1] not in WHITESPACE: + before = line[:index] + if char == ':' and before.count('[') > before.count(']') and \ + before.rfind('{') < before.rfind('['): + continue # Slice syntax, no space required + if char == ',' and line[index + 1] == ')': + continue # Allow tuple with only one element: (3,) + yield index, "E231 missing whitespace after '%s'" % char + + +def indentation(logical_line, previous_logical, indent_char, + indent_level, previous_indent_level): + r"""Use 4 spaces per indentation level. + + For really old code that you don't want to mess up, you can continue to + use 8-space tabs. + + Okay: a = 1 + Okay: if a == 0:\n a = 1 + E111: a = 1 + E114: # a = 1 + + Okay: for item in items:\n pass + E112: for item in items:\npass + E115: for item in items:\n# Hi\n pass + + Okay: a = 1\nb = 2 + E113: a = 1\n b = 2 + E116: a = 1\n # b = 2 + """ + c = 0 if logical_line else 3 + tmpl = "E11%d %s" if logical_line else "E11%d %s (comment)" + if indent_level % 4: + yield 0, tmpl % (1 + c, "indentation is not a multiple of four") + indent_expect = previous_logical.endswith(':') + if indent_expect and indent_level <= previous_indent_level: + yield 0, tmpl % (2 + c, "expected an indented block") + elif not indent_expect and indent_level > previous_indent_level: + yield 0, tmpl % (3 + c, "unexpected indentation") + + +def continued_indentation(logical_line, tokens, indent_level, hang_closing, + indent_char, noqa, verbose): + r"""Continuation lines indentation. + + Continuation lines should align wrapped elements either vertically + using Python's implicit line joining inside parentheses, brackets + and braces, or using a hanging indent. + + When using a hanging indent these considerations should be applied: + - there should be no arguments on the first line, and + - further indentation should be used to clearly distinguish itself as a + continuation line. + + Okay: a = (\n) + E123: a = (\n ) + + Okay: a = (\n 42) + E121: a = (\n 42) + E122: a = (\n42) + E123: a = (\n 42\n ) + E124: a = (24,\n 42\n) + E125: if (\n b):\n pass + E126: a = (\n 42) + E127: a = (24,\n 42) + E128: a = (24,\n 42) + E129: if (a or\n b):\n pass + E131: a = (\n 42\n 24) + """ + first_row = tokens[0][2][0] + nrows = 1 + tokens[-1][2][0] - first_row + if noqa or nrows == 1: + return + + # indent_next tells us whether the next block is indented; assuming + # that it is indented by 4 spaces, then we should not allow 4-space + # indents on the final continuation line; in turn, some other + # indents are allowed to have an extra 4 spaces. + indent_next = logical_line.endswith(':') + + row = depth = 0 + valid_hangs = (4,) if indent_char != '\t' else (4, 8) + # remember how many brackets were opened on each line + parens = [0] * nrows + # relative indents of physical lines + rel_indent = [0] * nrows + # for each depth, collect a list of opening rows + open_rows = [[0]] + # for each depth, memorize the hanging indentation + hangs = [None] + # visual indents + indent_chances = {} + last_indent = tokens[0][2] + visual_indent = None + last_token_multiline = False + # for each depth, memorize the visual indent column + indent = [last_indent[1]] + if verbose >= 3: + print(">>> " + tokens[0][4].rstrip()) + + for token_type, text, start, end, line in tokens: + + newline = row < start[0] - first_row + if newline: + row = start[0] - first_row + newline = not last_token_multiline and token_type not in NEWLINE + + if newline: + # this is the beginning of a continuation line. + last_indent = start + if verbose >= 3: + print("... " + line.rstrip()) + + # record the initial indent. + rel_indent[row] = expand_indent(line) - indent_level + + # identify closing bracket + close_bracket = (token_type == tokenize.OP and text in ']})') + + # is the indent relative to an opening bracket line? + for open_row in reversed(open_rows[depth]): + hang = rel_indent[row] - rel_indent[open_row] + hanging_indent = hang in valid_hangs + if hanging_indent: + break + if hangs[depth]: + hanging_indent = (hang == hangs[depth]) + # is there any chance of visual indent? + visual_indent = (not close_bracket and hang > 0 and + indent_chances.get(start[1])) + + if close_bracket and indent[depth]: + # closing bracket for visual indent + if start[1] != indent[depth]: + yield (start, "E124 closing bracket does not match " + "visual indentation") + elif close_bracket and not hang: + # closing bracket matches indentation of opening bracket's line + if hang_closing: + yield start, "E133 closing bracket is missing indentation" + elif indent[depth] and start[1] < indent[depth]: + if visual_indent is not True: + # visual indent is broken + yield (start, "E128 continuation line " + "under-indented for visual indent") + elif hanging_indent or (indent_next and rel_indent[row] == 8): + # hanging indent is verified + if close_bracket and not hang_closing: + yield (start, "E123 closing bracket does not match " + "indentation of opening bracket's line") + hangs[depth] = hang + elif visual_indent is True: + # visual indent is verified + indent[depth] = start[1] + elif visual_indent in (text, str): + # ignore token lined up with matching one from a previous line + pass + else: + # indent is broken + if hang <= 0: + error = "E122", "missing indentation or outdented" + elif indent[depth]: + error = "E127", "over-indented for visual indent" + elif not close_bracket and hangs[depth]: + error = "E131", "unaligned for hanging indent" + else: + hangs[depth] = hang + if hang > 4: + error = "E126", "over-indented for hanging indent" + else: + error = "E121", "under-indented for hanging indent" + yield start, "%s continuation line %s" % error + + # look for visual indenting + if (parens[row] and + token_type not in (tokenize.NL, tokenize.COMMENT) and + not indent[depth]): + indent[depth] = start[1] + indent_chances[start[1]] = True + if verbose >= 4: + print("bracket depth %s indent to %s" % (depth, start[1])) + # deal with implicit string concatenation + elif (token_type in (tokenize.STRING, tokenize.COMMENT) or + text in ('u', 'ur', 'b', 'br')): + indent_chances[start[1]] = str + # special case for the "if" statement because len("if (") == 4 + elif not indent_chances and not row and not depth and text == 'if': + indent_chances[end[1] + 1] = True + elif text == ':' and line[end[1]:].isspace(): + open_rows[depth].append(row) + + # keep track of bracket depth + if token_type == tokenize.OP: + if text in '([{': + depth += 1 + indent.append(0) + hangs.append(None) + if len(open_rows) == depth: + open_rows.append([]) + open_rows[depth].append(row) + parens[row] += 1 + if verbose >= 4: + print("bracket depth %s seen, col %s, visual min = %s" % + (depth, start[1], indent[depth])) + elif text in ')]}' and depth > 0: + # parent indents should not be more than this one + prev_indent = indent.pop() or last_indent[1] + hangs.pop() + for d in range(depth): + if indent[d] > prev_indent: + indent[d] = 0 + for ind in list(indent_chances): + if ind >= prev_indent: + del indent_chances[ind] + del open_rows[depth + 1:] + depth -= 1 + if depth: + indent_chances[indent[depth]] = True + for idx in range(row, -1, -1): + if parens[idx]: + parens[idx] -= 1 + break + assert len(indent) == depth + 1 + if start[1] not in indent_chances: + # allow lining up tokens + indent_chances[start[1]] = text + + last_token_multiline = (start[0] != end[0]) + if last_token_multiline: + rel_indent[end[0] - first_row] = rel_indent[row] + + if indent_next and expand_indent(line) == indent_level + 4: + pos = (start[0], indent[0] + 4) + if visual_indent: + code = "E129 visually indented line" + else: + code = "E125 continuation line" + yield pos, "%s with same indent as next logical line" % code + + +def whitespace_before_parameters(logical_line, tokens): + r"""Avoid extraneous whitespace. + + Avoid extraneous whitespace in the following situations: + - before the open parenthesis that starts the argument list of a + function call. + - before the open parenthesis that starts an indexing or slicing. + + Okay: spam(1) + E211: spam (1) + + Okay: dict['key'] = list[index] + E211: dict ['key'] = list[index] + E211: dict['key'] = list [index] + """ + prev_type, prev_text, __, prev_end, __ = tokens[0] + for index in range(1, len(tokens)): + token_type, text, start, end, __ = tokens[index] + if (token_type == tokenize.OP and + text in '([' and + start != prev_end and + (prev_type == tokenize.NAME or prev_text in '}])') and + # Syntax "class A (B):" is allowed, but avoid it + (index < 2 or tokens[index - 2][1] != 'class') and + # Allow "return (a.foo for a in range(5))" + not keyword.iskeyword(prev_text)): + yield prev_end, "E211 whitespace before '%s'" % text + prev_type = token_type + prev_text = text + prev_end = end + + +def whitespace_around_operator(logical_line): + r"""Avoid extraneous whitespace around an operator. + + Okay: a = 12 + 3 + E221: a = 4 + 5 + E222: a = 4 + 5 + E223: a = 4\t+ 5 + E224: a = 4 +\t5 + """ + for match in OPERATOR_REGEX.finditer(logical_line): + before, after = match.groups() + + if '\t' in before: + yield match.start(1), "E223 tab before operator" + elif len(before) > 1: + yield match.start(1), "E221 multiple spaces before operator" + + if '\t' in after: + yield match.start(2), "E224 tab after operator" + elif len(after) > 1: + yield match.start(2), "E222 multiple spaces after operator" + + +def missing_whitespace_around_operator(logical_line, tokens): + r"""Surround operators with a single space on either side. + + - Always surround these binary operators with a single space on + either side: assignment (=), augmented assignment (+=, -= etc.), + comparisons (==, <, >, !=, <=, >=, in, not in, is, is not), + Booleans (and, or, not). + + - If operators with different priorities are used, consider adding + whitespace around the operators with the lowest priorities. + + Okay: i = i + 1 + Okay: submitted += 1 + Okay: x = x * 2 - 1 + Okay: hypot2 = x * x + y * y + Okay: c = (a + b) * (a - b) + Okay: foo(bar, key='word', *args, **kwargs) + Okay: alpha[:-i] + + E225: i=i+1 + E225: submitted +=1 + E225: x = x /2 - 1 + E225: z = x **y + E226: c = (a+b) * (a-b) + E226: hypot2 = x*x + y*y + E227: c = a|b + E228: msg = fmt%(errno, errmsg) + """ + parens = 0 + need_space = False + prev_type = tokenize.OP + prev_text = prev_end = None + for token_type, text, start, end, line in tokens: + if token_type in SKIP_COMMENTS: + continue + if text in ('(', 'lambda'): + parens += 1 + elif text == ')': + parens -= 1 + if need_space: + if start != prev_end: + # Found a (probably) needed space + if need_space is not True and not need_space[1]: + yield (need_space[0], + "E225 missing whitespace around operator") + need_space = False + elif text == '>' and prev_text in ('<', '-'): + # Tolerate the "<>" operator, even if running Python 3 + # Deal with Python 3's annotated return value "->" + pass + else: + if need_space is True or need_space[1]: + # A needed trailing space was not found + yield prev_end, "E225 missing whitespace around operator" + elif prev_text != '**': + code, optype = 'E226', 'arithmetic' + if prev_text == '%': + code, optype = 'E228', 'modulo' + elif prev_text not in ARITHMETIC_OP: + code, optype = 'E227', 'bitwise or shift' + yield (need_space[0], "%s missing whitespace " + "around %s operator" % (code, optype)) + need_space = False + elif token_type == tokenize.OP and prev_end is not None: + if text == '=' and parens: + # Allow keyword args or defaults: foo(bar=None). + pass + elif text in WS_NEEDED_OPERATORS: + need_space = True + elif text in UNARY_OPERATORS: + # Check if the operator is being used as a binary operator + # Allow unary operators: -123, -x, +1. + # Allow argument unpacking: foo(*args, **kwargs). + if (prev_text in '}])' if prev_type == tokenize.OP + else prev_text not in KEYWORDS): + need_space = None + elif text in WS_OPTIONAL_OPERATORS: + need_space = None + + if need_space is None: + # Surrounding space is optional, but ensure that + # trailing space matches opening space + need_space = (prev_end, start != prev_end) + elif need_space and start == prev_end: + # A needed opening space was not found + yield prev_end, "E225 missing whitespace around operator" + need_space = False + prev_type = token_type + prev_text = text + prev_end = end + + +def whitespace_around_comma(logical_line): + r"""Avoid extraneous whitespace after a comma or a colon. + + Note: these checks are disabled by default + + Okay: a = (1, 2) + E241: a = (1, 2) + E242: a = (1,\t2) + """ + line = logical_line + for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line): + found = m.start() + 1 + if '\t' in m.group(): + yield found, "E242 tab after '%s'" % m.group()[0] + else: + yield found, "E241 multiple spaces after '%s'" % m.group()[0] + + +def whitespace_around_named_parameter_equals(logical_line, tokens): + r"""Don't use spaces around the '=' sign in function arguments. + + Don't use spaces around the '=' sign when used to indicate a + keyword argument or a default parameter value. + + Okay: def complex(real, imag=0.0): + Okay: return magic(r=real, i=imag) + Okay: boolean(a == b) + Okay: boolean(a != b) + Okay: boolean(a <= b) + Okay: boolean(a >= b) + Okay: def foo(arg: int = 42): + Okay: async def foo(arg: int = 42): + + E251: def complex(real, imag = 0.0): + E251: return magic(r = real, i = imag) + """ + parens = 0 + no_space = False + prev_end = None + annotated_func_arg = False + in_def = logical_line.startswith(('def', 'async def')) + message = "E251 unexpected spaces around keyword / parameter equals" + for token_type, text, start, end, line in tokens: + if token_type == tokenize.NL: + continue + if no_space: + no_space = False + if start != prev_end: + yield (prev_end, message) + if token_type == tokenize.OP: + if text in '([': + parens += 1 + elif text in ')]': + parens -= 1 + elif in_def and text == ':' and parens == 1: + annotated_func_arg = True + elif parens and text == ',' and parens == 1: + annotated_func_arg = False + elif parens and text == '=' and not annotated_func_arg: + no_space = True + if start != prev_end: + yield (prev_end, message) + if not parens: + annotated_func_arg = False + + prev_end = end + + +def whitespace_before_comment(logical_line, tokens): + r"""Separate inline comments by at least two spaces. + + An inline comment is a comment on the same line as a statement. Inline + comments should be separated by at least two spaces from the statement. + They should start with a # and a single space. + + Each line of a block comment starts with a # and a single space + (unless it is indented text inside the comment). + + Okay: x = x + 1 # Increment x + Okay: x = x + 1 # Increment x + Okay: # Block comment + E261: x = x + 1 # Increment x + E262: x = x + 1 #Increment x + E262: x = x + 1 # Increment x + E265: #Block comment + E266: ### Block comment + """ + prev_end = (0, 0) + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + inline_comment = line[:start[1]].strip() + if inline_comment: + if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: + yield (prev_end, + "E261 at least two spaces before inline comment") + symbol, sp, comment = text.partition(' ') + bad_prefix = symbol not in '#:' and (symbol.lstrip('#')[:1] or '#') + if inline_comment: + if bad_prefix or comment[:1] in WHITESPACE: + yield start, "E262 inline comment should start with '# '" + elif bad_prefix and (bad_prefix != '!' or start[0] > 1): + if bad_prefix != '#': + yield start, "E265 block comment should start with '# '" + elif comment: + yield start, "E266 too many leading '#' for block comment" + elif token_type != tokenize.NL: + prev_end = end + + +def imports_on_separate_lines(logical_line): + r"""Place imports on separate lines. + + Okay: import os\nimport sys + E401: import sys, os + + Okay: from subprocess import Popen, PIPE + Okay: from myclas import MyClass + Okay: from foo.bar.yourclass import YourClass + Okay: import myclass + Okay: import foo.bar.yourclass + """ + line = logical_line + if line.startswith('import '): + found = line.find(',') + if -1 < found and ';' not in line[:found]: + yield found, "E401 multiple imports on one line" + + +def module_imports_on_top_of_file( + logical_line, indent_level, checker_state, noqa): + r"""Place imports at the top of the file. + + Always put imports at the top of the file, just after any module comments + and docstrings, and before module globals and constants. + + Okay: import os + Okay: # this is a comment\nimport os + Okay: '''this is a module docstring'''\nimport os + Okay: r'''this is a module docstring'''\nimport os + Okay: try:\n import x\nexcept:\n pass\nelse:\n pass\nimport y + Okay: try:\n import x\nexcept:\n pass\nfinally:\n pass\nimport y + E402: a=1\nimport os + E402: 'One string'\n"Two string"\nimport os + E402: a=1\nfrom sys import x + + Okay: if x:\n import os + """ + def is_string_literal(line): + if line[0] in 'uUbB': + line = line[1:] + if line and line[0] in 'rR': + line = line[1:] + return line and (line[0] == '"' or line[0] == "'") + + allowed_try_keywords = ('try', 'except', 'else', 'finally') + + if indent_level: # Allow imports in conditional statements or functions + return + if not logical_line: # Allow empty lines or comments + return + if noqa: + return + line = logical_line + if line.startswith('import ') or line.startswith('from '): + if checker_state.get('seen_non_imports', False): + yield 0, "E402 module level import not at top of file" + elif any(line.startswith(kw) for kw in allowed_try_keywords): + # Allow try, except, else, finally keywords intermixed with imports in + # order to support conditional importing + return + elif is_string_literal(line): + # The first literal is a docstring, allow it. Otherwise, report error. + if checker_state.get('seen_docstring', False): + checker_state['seen_non_imports'] = True + else: + checker_state['seen_docstring'] = True + else: + checker_state['seen_non_imports'] = True + + +def compound_statements(logical_line): + r"""Compound statements (on the same line) are generally discouraged. + + While sometimes it's okay to put an if/for/while with a small body + on the same line, never do this for multi-clause statements. + Also avoid folding such long lines! + + Always use a def statement instead of an assignment statement that + binds a lambda expression directly to a name. + + Okay: if foo == 'blah':\n do_blah_thing() + Okay: do_one() + Okay: do_two() + Okay: do_three() + + E701: if foo == 'blah': do_blah_thing() + E701: for x in lst: total += x + E701: while t < 10: t = delay() + E701: if foo == 'blah': do_blah_thing() + E701: else: do_non_blah_thing() + E701: try: something() + E701: finally: cleanup() + E701: if foo == 'blah': one(); two(); three() + E702: do_one(); do_two(); do_three() + E703: do_four(); # useless semicolon + E704: def f(x): return 2*x + E731: f = lambda x: 2*x + """ + line = logical_line + last_char = len(line) - 1 + found = line.find(':') + while -1 < found < last_char: + before = line[:found] + if ((before.count('{') <= before.count('}') and # {'a': 1} (dict) + before.count('[') <= before.count(']') and # [1:2] (slice) + before.count('(') <= before.count(')'))): # (annotation) + lambda_kw = LAMBDA_REGEX.search(before) + if lambda_kw: + before = line[:lambda_kw.start()].rstrip() + if before[-1:] == '=' and isidentifier(before[:-1].strip()): + yield 0, ("E731 do not assign a lambda expression, use a " + "def") + break + if before.startswith('def '): + yield 0, "E704 multiple statements on one line (def)" + else: + yield found, "E701 multiple statements on one line (colon)" + found = line.find(':', found + 1) + found = line.find(';') + while -1 < found: + if found < last_char: + yield found, "E702 multiple statements on one line (semicolon)" + else: + yield found, "E703 statement ends with a semicolon" + found = line.find(';', found + 1) + + +def explicit_line_join(logical_line, tokens): + r"""Avoid explicit line join between brackets. + + The preferred way of wrapping long lines is by using Python's implied line + continuation inside parentheses, brackets and braces. Long lines can be + broken over multiple lines by wrapping expressions in parentheses. These + should be used in preference to using a backslash for line continuation. + + E502: aaa = [123, \\n 123] + E502: aaa = ("bbb " \\n "ccc") + + Okay: aaa = [123,\n 123] + Okay: aaa = ("bbb "\n "ccc") + Okay: aaa = "bbb " \\n "ccc" + Okay: aaa = 123 # \\ + """ + prev_start = prev_end = parens = 0 + comment = False + backslash = None + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + comment = True + if start[0] != prev_start and parens and backslash and not comment: + yield backslash, "E502 the backslash is redundant between brackets" + if end[0] != prev_end: + if line.rstrip('\r\n').endswith('\\'): + backslash = (end[0], len(line.splitlines()[-1]) - 1) + else: + backslash = None + prev_start = prev_end = end[0] + else: + prev_start = start[0] + if token_type == tokenize.OP: + if text in '([{': + parens += 1 + elif text in ')]}': + parens -= 1 + + +def break_around_binary_operator(logical_line, tokens): + r""" + Avoid breaks before binary operators. + + The preferred place to break around a binary operator is after the + operator, not before it. + + W503: (width == 0\n + height == 0) + W503: (width == 0\n and height == 0) + + Okay: (width == 0 +\n height == 0) + Okay: foo(\n -x) + Okay: foo(x\n []) + Okay: x = '''\n''' + '' + Okay: foo(x,\n -y) + Okay: foo(x, # comment\n -y) + Okay: var = (1 &\n ~2) + Okay: var = (1 /\n -2) + Okay: var = (1 +\n -1 +\n -2) + """ + def is_binary_operator(token_type, text): + # The % character is strictly speaking a binary operator, but the + # common usage seems to be to put it next to the format parameters, + # after a line break. + return ((token_type == tokenize.OP or text in ['and', 'or']) and + text not in "()[]{},:.;@=%~") + + line_break = False + unary_context = True + # Previous non-newline token types and text + previous_token_type = None + previous_text = None + for token_type, text, start, end, line in tokens: + if token_type == tokenize.COMMENT: + continue + if ('\n' in text or '\r' in text) and token_type != tokenize.STRING: + line_break = True + else: + if (is_binary_operator(token_type, text) and line_break and + not unary_context and + not is_binary_operator(previous_token_type, + previous_text)): + yield start, "W503 line break before binary operator" + unary_context = text in '([{,;' + line_break = False + previous_token_type = token_type + previous_text = text + + +def comparison_to_singleton(logical_line, noqa): + r"""Comparison to singletons should use "is" or "is not". + + Comparisons to singletons like None should always be done + with "is" or "is not", never the equality operators. + + Okay: if arg is not None: + E711: if arg != None: + E711: if None == arg: + E712: if arg == True: + E712: if False == arg: + + Also, beware of writing if x when you really mean if x is not None -- + e.g. when testing whether a variable or argument that defaults to None was + set to some other value. The other value might have a type (such as a + container) that could be false in a boolean context! + """ + match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line) + if match: + singleton = match.group(1) or match.group(3) + same = (match.group(2) == '==') + + msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton) + if singleton in ('None',): + code = 'E711' + else: + code = 'E712' + nonzero = ((singleton == 'True' and same) or + (singleton == 'False' and not same)) + msg += " or 'if %scond:'" % ('' if nonzero else 'not ') + yield match.start(2), ("%s comparison to %s should be %s" % + (code, singleton, msg)) + + +def comparison_negative(logical_line): + r"""Negative comparison should be done using "not in" and "is not". + + Okay: if x not in y:\n pass + Okay: assert (X in Y or X is Z) + Okay: if not (X in Y):\n pass + Okay: zz = x is not y + E713: Z = not X in Y + E713: if not X.B in Y:\n pass + E714: if not X is Y:\n pass + E714: Z = not X.B is Y + """ + match = COMPARE_NEGATIVE_REGEX.search(logical_line) + if match: + pos = match.start(1) + if match.group(2) == 'in': + yield pos, "E713 test for membership should be 'not in'" + else: + yield pos, "E714 test for object identity should be 'is not'" + + +def comparison_type(logical_line, noqa): + r"""Object type comparisons should always use isinstance(). + + Do not compare types directly. + + Okay: if isinstance(obj, int): + E721: if type(obj) is type(1): + + When checking if an object is a string, keep in mind that it might be a + unicode string too! In Python 2.3, str and unicode have a common base + class, basestring, so you can do: + + Okay: if isinstance(obj, basestring): + Okay: if type(a1) is type(b1): + """ + match = COMPARE_TYPE_REGEX.search(logical_line) + if match and not noqa: + inst = match.group(1) + if inst and isidentifier(inst) and inst not in SINGLETONS: + return # Allow comparison for types which are not obvious + yield match.start(), "E721 do not compare types, use 'isinstance()'" + + +def python_3000_has_key(logical_line, noqa): + r"""The {}.has_key() method is removed in Python 3: use the 'in' operator. + + Okay: if "alph" in d:\n print d["alph"] + W601: assert d.has_key('alph') + """ + pos = logical_line.find('.has_key(') + if pos > -1 and not noqa: + yield pos, "W601 .has_key() is deprecated, use 'in'" + + +def python_3000_raise_comma(logical_line): + r"""When raising an exception, use "raise ValueError('message')". + + The older form is removed in Python 3. + + Okay: raise DummyError("Message") + W602: raise DummyError, "Message" + """ + match = RAISE_COMMA_REGEX.match(logical_line) + if match and not RERAISE_COMMA_REGEX.match(logical_line): + yield match.end() - 1, "W602 deprecated form of raising exception" + + +def python_3000_not_equal(logical_line): + r"""New code should always use != instead of <>. + + The older syntax is removed in Python 3. + + Okay: if a != 'no': + W603: if a <> 'no': + """ + pos = logical_line.find('<>') + if pos > -1: + yield pos, "W603 '<>' is deprecated, use '!='" + + +def python_3000_backticks(logical_line): + r"""Use repr() instead of backticks in Python 3. + + Okay: val = repr(1 + 2) + W604: val = `1 + 2` + """ + pos = logical_line.find('`') + if pos > -1: + yield pos, "W604 backticks are deprecated, use 'repr()'" + + +############################################################################## +# Helper functions +############################################################################## + + +if sys.version_info < (3,): + # Python 2: implicit encoding. + def readlines(filename): + """Read the source code.""" + with open(filename, 'rU') as f: + return f.readlines() + isidentifier = re.compile(r'[a-zA-Z_]\w*$').match + stdin_get_value = sys.stdin.read +else: + # Python 3 + def readlines(filename): + """Read the source code.""" + try: + with open(filename, 'rb') as f: + (coding, lines) = tokenize.detect_encoding(f.readline) + f = TextIOWrapper(f, coding, line_buffering=True) + return [l.decode(coding) for l in lines] + f.readlines() + except (LookupError, SyntaxError, UnicodeError): + # Fall back if file encoding is improperly declared + with open(filename, encoding='latin-1') as f: + return f.readlines() + isidentifier = str.isidentifier + + def stdin_get_value(): + """Read the value from stdin.""" + return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() + +noqa = re.compile(r'# no(?:qa|pep8)\b', re.I).search + + +def expand_indent(line): + r"""Return the amount of indentation. + + Tabs are expanded to the next multiple of 8. + + >>> expand_indent(' ') + 4 + >>> expand_indent('\t') + 8 + >>> expand_indent(' \t') + 8 + >>> expand_indent(' \t') + 16 + """ + if '\t' not in line: + return len(line) - len(line.lstrip()) + result = 0 + for char in line: + if char == '\t': + result = result // 8 * 8 + 8 + elif char == ' ': + result += 1 + else: + break + return result + + +def mute_string(text): + """Replace contents with 'xxx' to prevent syntax matching. + + >>> mute_string('"abc"') + '"xxx"' + >>> mute_string("'''abc'''") + "'''xxx'''" + >>> mute_string("r'abc'") + "r'xxx'" + """ + # String modifiers (e.g. u or r) + start = text.index(text[-1]) + 1 + end = len(text) - 1 + # Triple quotes + if text[-3:] in ('"""', "'''"): + start += 2 + end -= 2 + return text[:start] + 'x' * (end - start) + text[end:] + + +def parse_udiff(diff, patterns=None, parent='.'): + """Return a dictionary of matching lines.""" + # For each file of the diff, the entry key is the filename, + # and the value is a set of row numbers to consider. + rv = {} + path = nrows = None + for line in diff.splitlines(): + if nrows: + if line[:1] != '-': + nrows -= 1 + continue + if line[:3] == '@@ ': + hunk_match = HUNK_REGEX.match(line) + (row, nrows) = [int(g or '1') for g in hunk_match.groups()] + rv[path].update(range(row, row + nrows)) + elif line[:3] == '+++': + path = line[4:].split('\t', 1)[0] + if path[:2] == 'b/': + path = path[2:] + rv[path] = set() + return dict([(os.path.join(parent, path), rows) + for (path, rows) in rv.items() + if rows and filename_match(path, patterns)]) + + +def normalize_paths(value, parent=os.curdir): + """Parse a comma-separated list of paths. + + Return a list of absolute paths. + """ + if not value: + return [] + if isinstance(value, list): + return value + paths = [] + for path in value.split(','): + path = path.strip() + if '/' in path: + path = os.path.abspath(os.path.join(parent, path)) + paths.append(path.rstrip('/')) + return paths + + +def filename_match(filename, patterns, default=True): + """Check if patterns contains a pattern that matches filename. + + If patterns is unspecified, this always returns True. + """ + if not patterns: + return default + return any(fnmatch(filename, pattern) for pattern in patterns) + + +def _is_eol_token(token): + return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' +if COMMENT_WITH_NL: + def _is_eol_token(token, _eol_token=_is_eol_token): + return _eol_token(token) or (token[0] == tokenize.COMMENT and + token[1] == token[4]) + +############################################################################## +# Framework to run all checks +############################################################################## + + +_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} + + +def _get_parameters(function): + if sys.version_info >= (3, 3): + return [parameter.name + for parameter + in inspect.signature(function).parameters.values() + if parameter.kind == parameter.POSITIONAL_OR_KEYWORD] + else: + return inspect.getargspec(function)[0] + + +def register_check(check, codes=None): + """Register a new check object.""" + def _add_check(check, kind, codes, args): + if check in _checks[kind]: + _checks[kind][check][0].extend(codes or []) + else: + _checks[kind][check] = (codes or [''], args) + if inspect.isfunction(check): + args = _get_parameters(check) + if args and args[0] in ('physical_line', 'logical_line'): + if codes is None: + codes = ERRORCODE_REGEX.findall(check.__doc__ or '') + _add_check(check, args[0], codes, args) + elif inspect.isclass(check): + if _get_parameters(check.__init__)[:2] == ['self', 'tree']: + _add_check(check, 'tree', codes, None) + + +def init_checks_registry(): + """Register all globally visible functions. + + The first argument name is either 'physical_line' or 'logical_line'. + """ + mod = inspect.getmodule(register_check) + for (name, function) in inspect.getmembers(mod, inspect.isfunction): + register_check(function) +init_checks_registry() + + +class Checker(object): + """Load a Python source file, tokenize it, check coding style.""" + + def __init__(self, filename=None, lines=None, + options=None, report=None, **kwargs): + if options is None: + options = StyleGuide(kwargs).options + else: + assert not kwargs + self._io_error = None + self._physical_checks = options.physical_checks + self._logical_checks = options.logical_checks + self._ast_checks = options.ast_checks + self.max_line_length = options.max_line_length + self.multiline = False # in a multiline string? + self.hang_closing = options.hang_closing + self.verbose = options.verbose + self.filename = filename + # Dictionary where a checker can store its custom state. + self._checker_states = {} + if filename is None: + self.filename = 'stdin' + self.lines = lines or [] + elif filename == '-': + self.filename = 'stdin' + self.lines = stdin_get_value().splitlines(True) + elif lines is None: + try: + self.lines = readlines(filename) + except IOError: + (exc_type, exc) = sys.exc_info()[:2] + self._io_error = '%s: %s' % (exc_type.__name__, exc) + self.lines = [] + else: + self.lines = lines + if self.lines: + ord0 = ord(self.lines[0][0]) + if ord0 in (0xef, 0xfeff): # Strip the UTF-8 BOM + if ord0 == 0xfeff: + self.lines[0] = self.lines[0][1:] + elif self.lines[0][:3] == '\xef\xbb\xbf': + self.lines[0] = self.lines[0][3:] + self.report = report or options.report + self.report_error = self.report.error + + def report_invalid_syntax(self): + """Check if the syntax is valid.""" + (exc_type, exc) = sys.exc_info()[:2] + if len(exc.args) > 1: + offset = exc.args[1] + if len(offset) > 2: + offset = offset[1:3] + else: + offset = (1, 0) + self.report_error(offset[0], offset[1] or 0, + 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), + self.report_invalid_syntax) + + def readline(self): + """Get the next line from the input buffer.""" + if self.line_number >= self.total_lines: + return '' + line = self.lines[self.line_number] + self.line_number += 1 + if self.indent_char is None and line[:1] in WHITESPACE: + self.indent_char = line[0] + return line + + def run_check(self, check, argument_names): + """Run a check plugin.""" + arguments = [] + for name in argument_names: + arguments.append(getattr(self, name)) + return check(*arguments) + + def init_checker_state(self, name, argument_names): + """Prepare custom state for the specific checker plugin.""" + if 'checker_state' in argument_names: + self.checker_state = self._checker_states.setdefault(name, {}) + + def check_physical(self, line): + """Run all physical checks on a raw input line.""" + self.physical_line = line + for name, check, argument_names in self._physical_checks: + self.init_checker_state(name, argument_names) + result = self.run_check(check, argument_names) + if result is not None: + (offset, text) = result + self.report_error(self.line_number, offset, text, check) + if text[:4] == 'E101': + self.indent_char = line[0] + + def build_tokens_line(self): + """Build a logical line from tokens.""" + logical = [] + comments = [] + length = 0 + prev_row = prev_col = mapping = None + for token_type, text, start, end, line in self.tokens: + if token_type in SKIP_TOKENS: + continue + if not mapping: + mapping = [(0, start)] + if token_type == tokenize.COMMENT: + comments.append(text) + continue + if token_type == tokenize.STRING: + text = mute_string(text) + if prev_row: + (start_row, start_col) = start + if prev_row != start_row: # different row + prev_text = self.lines[prev_row - 1][prev_col - 1] + if prev_text == ',' or (prev_text not in '{[(' and + text not in '}])'): + text = ' ' + text + elif prev_col != start_col: # different column + text = line[prev_col:start_col] + text + logical.append(text) + length += len(text) + mapping.append((length, end)) + (prev_row, prev_col) = end + self.logical_line = ''.join(logical) + self.noqa = comments and noqa(''.join(comments)) + return mapping + + def check_logical(self): + """Build a line from tokens and run all logical checks on it.""" + self.report.increment_logical_line() + mapping = self.build_tokens_line() + + if not mapping: + return + + (start_row, start_col) = mapping[0][1] + start_line = self.lines[start_row - 1] + self.indent_level = expand_indent(start_line[:start_col]) + if self.blank_before < self.blank_lines: + self.blank_before = self.blank_lines + if self.verbose >= 2: + print(self.logical_line[:80].rstrip()) + for name, check, argument_names in self._logical_checks: + if self.verbose >= 4: + print(' ' + name) + self.init_checker_state(name, argument_names) + for offset, text in self.run_check(check, argument_names) or (): + if not isinstance(offset, tuple): + for token_offset, pos in mapping: + if offset <= token_offset: + break + offset = (pos[0], pos[1] + offset - token_offset) + self.report_error(offset[0], offset[1], text, check) + if self.logical_line: + self.previous_indent_level = self.indent_level + self.previous_logical = self.logical_line + self.blank_lines = 0 + self.tokens = [] + + def check_ast(self): + """Build the file's AST and run all AST checks.""" + try: + tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) + except (ValueError, SyntaxError, TypeError): + return self.report_invalid_syntax() + for name, cls, __ in self._ast_checks: + checker = cls(tree, self.filename) + for lineno, offset, text, check in checker.run(): + if not self.lines or not noqa(self.lines[lineno - 1]): + self.report_error(lineno, offset, text, check) + + def generate_tokens(self): + """Tokenize the file, run physical line checks and yield tokens.""" + if self._io_error: + self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) + tokengen = tokenize.generate_tokens(self.readline) + try: + for token in tokengen: + if token[2][0] > self.total_lines: + return + self.maybe_check_physical(token) + yield token + except (SyntaxError, tokenize.TokenError): + self.report_invalid_syntax() + + def maybe_check_physical(self, token): + """If appropriate (based on token), check current physical line(s).""" + # Called after every token, but act only on end of line. + if _is_eol_token(token): + # Obviously, a newline token ends a single physical line. + self.check_physical(token[4]) + elif token[0] == tokenize.STRING and '\n' in token[1]: + # Less obviously, a string that contains newlines is a + # multiline string, either triple-quoted or with internal + # newlines backslash-escaped. Check every physical line in the + # string *except* for the last one: its newline is outside of + # the multiline string, so we consider it a regular physical + # line, and will check it like any other physical line. + # + # Subtleties: + # - we don't *completely* ignore the last line; if it contains + # the magical "# noqa" comment, we disable all physical + # checks for the entire multiline string + # - have to wind self.line_number back because initially it + # points to the last line of the string, and we want + # check_physical() to give accurate feedback + if noqa(token[4]): + return + self.multiline = True + self.line_number = token[2][0] + for line in token[1].split('\n')[:-1]: + self.check_physical(line + '\n') + self.line_number += 1 + self.multiline = False + + def check_all(self, expected=None, line_offset=0): + """Run all checks on the input file.""" + self.report.init_file(self.filename, self.lines, expected, line_offset) + self.total_lines = len(self.lines) + if self._ast_checks: + self.check_ast() + self.line_number = 0 + self.indent_char = None + self.indent_level = self.previous_indent_level = 0 + self.previous_logical = '' + self.tokens = [] + self.blank_lines = self.blank_before = 0 + parens = 0 + for token in self.generate_tokens(): + self.tokens.append(token) + token_type, text = token[0:2] + if self.verbose >= 3: + if token[2][0] == token[3][0]: + pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) + else: + pos = 'l.%s' % token[3][0] + print('l.%s\t%s\t%s\t%r' % + (token[2][0], pos, tokenize.tok_name[token[0]], text)) + if token_type == tokenize.OP: + if text in '([{': + parens += 1 + elif text in '}])': + parens -= 1 + elif not parens: + if token_type in NEWLINE: + if token_type == tokenize.NEWLINE: + self.check_logical() + self.blank_before = 0 + elif len(self.tokens) == 1: + # The physical line contains only this token. + self.blank_lines += 1 + del self.tokens[0] + else: + self.check_logical() + elif COMMENT_WITH_NL and token_type == tokenize.COMMENT: + if len(self.tokens) == 1: + # The comment also ends a physical line + token = list(token) + token[1] = text.rstrip('\r\n') + token[3] = (token[2][0], token[2][1] + len(token[1])) + self.tokens = [tuple(token)] + self.check_logical() + if self.tokens: + self.check_physical(self.lines[-1]) + self.check_logical() + return self.report.get_file_results() + + +class BaseReport(object): + """Collect the results of the checks.""" + + print_filename = False + + def __init__(self, options): + self._benchmark_keys = options.benchmark_keys + self._ignore_code = options.ignore_code + # Results + self.elapsed = 0 + self.total_errors = 0 + self.counters = dict.fromkeys(self._benchmark_keys, 0) + self.messages = {} + + def start(self): + """Start the timer.""" + self._start_time = time.time() + + def stop(self): + """Stop the timer.""" + self.elapsed = time.time() - self._start_time + + def init_file(self, filename, lines, expected, line_offset): + """Signal a new file.""" + self.filename = filename + self.lines = lines + self.expected = expected or () + self.line_offset = line_offset + self.file_errors = 0 + self.counters['files'] += 1 + self.counters['physical lines'] += len(lines) + + def increment_logical_line(self): + """Signal a new logical line.""" + self.counters['logical lines'] += 1 + + def error(self, line_number, offset, text, check): + """Report an error, according to options.""" + code = text[:4] + if self._ignore_code(code): + return + if code in self.counters: + self.counters[code] += 1 + else: + self.counters[code] = 1 + self.messages[code] = text[5:] + # Don't care about expected errors or warnings + if code in self.expected: + return + if self.print_filename and not self.file_errors: + print(self.filename) + self.file_errors += 1 + self.total_errors += 1 + return code + + def get_file_results(self): + """Return the count of errors and warnings for this file.""" + return self.file_errors + + def get_count(self, prefix=''): + """Return the total count of errors and warnings.""" + return sum([self.counters[key] + for key in self.messages if key.startswith(prefix)]) + + def get_statistics(self, prefix=''): + """Get statistics for message codes that start with the prefix. + + prefix='' matches all errors and warnings + prefix='E' matches all errors + prefix='W' matches all warnings + prefix='E4' matches all errors that have to do with imports + """ + return ['%-7s %s %s' % (self.counters[key], key, self.messages[key]) + for key in sorted(self.messages) if key.startswith(prefix)] + + def print_statistics(self, prefix=''): + """Print overall statistics (number of errors and warnings).""" + for line in self.get_statistics(prefix): + print(line) + + def print_benchmark(self): + """Print benchmark numbers.""" + print('%-7.2f %s' % (self.elapsed, 'seconds elapsed')) + if self.elapsed: + for key in self._benchmark_keys: + print('%-7d %s per second (%d total)' % + (self.counters[key] / self.elapsed, key, + self.counters[key])) + + +class FileReport(BaseReport): + """Collect the results of the checks and print only the filenames.""" + + print_filename = True + + +class StandardReport(BaseReport): + """Collect and print the results of the checks.""" + + def __init__(self, options): + super(StandardReport, self).__init__(options) + self._fmt = REPORT_FORMAT.get(options.format.lower(), + options.format) + self._repeat = options.repeat + self._show_source = options.show_source + self._show_pep8 = options.show_pep8 + + def init_file(self, filename, lines, expected, line_offset): + """Signal a new file.""" + self._deferred_print = [] + return super(StandardReport, self).init_file( + filename, lines, expected, line_offset) + + def error(self, line_number, offset, text, check): + """Report an error, according to options.""" + code = super(StandardReport, self).error(line_number, offset, + text, check) + if code and (self.counters[code] == 1 or self._repeat): + self._deferred_print.append( + (line_number, offset, code, text[5:], check.__doc__)) + return code + + def get_file_results(self): + """Print the result and return the overall count for this file.""" + self._deferred_print.sort() + for line_number, offset, code, text, doc in self._deferred_print: + print(self._fmt % { + 'path': self.filename, + 'row': self.line_offset + line_number, 'col': offset + 1, + 'code': code, 'text': text, + }) + if self._show_source: + if line_number > len(self.lines): + line = '' + else: + line = self.lines[line_number - 1] + print(line.rstrip()) + print(re.sub(r'\S', ' ', line[:offset]) + '^') + if self._show_pep8 and doc: + print(' ' + doc.strip()) + + # stdout is block buffered when not stdout.isatty(). + # line can be broken where buffer boundary since other processes + # write to same file. + # flush() after print() to avoid buffer boundary. + # Typical buffer size is 8192. line written safely when + # len(line) < 8192. + sys.stdout.flush() + return self.file_errors + + +class DiffReport(StandardReport): + """Collect and print the results for the changed lines only.""" + + def __init__(self, options): + super(DiffReport, self).__init__(options) + self._selected = options.selected_lines + + def error(self, line_number, offset, text, check): + if line_number not in self._selected[self.filename]: + return + return super(DiffReport, self).error(line_number, offset, text, check) + + +class StyleGuide(object): + """Initialize a PEP-8 instance with few options.""" + + def __init__(self, *args, **kwargs): + # build options from the command line + self.checker_class = kwargs.pop('checker_class', Checker) + parse_argv = kwargs.pop('parse_argv', False) + config_file = kwargs.pop('config_file', False) + parser = kwargs.pop('parser', None) + # build options from dict + options_dict = dict(*args, **kwargs) + arglist = None if parse_argv else options_dict.get('paths', None) + options, self.paths = process_options( + arglist, parse_argv, config_file, parser) + if options_dict: + options.__dict__.update(options_dict) + if 'paths' in options_dict: + self.paths = options_dict['paths'] + + self.runner = self.input_file + self.options = options + + if not options.reporter: + options.reporter = BaseReport if options.quiet else StandardReport + + options.select = tuple(options.select or ()) + if not (options.select or options.ignore or + options.testsuite or options.doctest) and DEFAULT_IGNORE: + # The default choice: ignore controversial checks + options.ignore = tuple(DEFAULT_IGNORE.split(',')) + else: + # Ignore all checks which are not explicitly selected + options.ignore = ('',) if options.select else tuple(options.ignore) + options.benchmark_keys = BENCHMARK_KEYS[:] + options.ignore_code = self.ignore_code + options.physical_checks = self.get_checks('physical_line') + options.logical_checks = self.get_checks('logical_line') + options.ast_checks = self.get_checks('tree') + self.init_report() + + def init_report(self, reporter=None): + """Initialize the report instance.""" + self.options.report = (reporter or self.options.reporter)(self.options) + return self.options.report + + def check_files(self, paths=None): + """Run all checks on the paths.""" + if paths is None: + paths = self.paths + report = self.options.report + runner = self.runner + report.start() + try: + for path in paths: + if os.path.isdir(path): + self.input_dir(path) + elif not self.excluded(path): + runner(path) + except KeyboardInterrupt: + print('... stopped') + report.stop() + return report + + 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) + return fchecker.check_all(expected=expected, line_offset=line_offset) + + def input_dir(self, dirname): + """Check all files in this directory and all subdirectories.""" + dirname = dirname.rstrip('/') + if self.excluded(dirname): + return 0 + counters = self.options.report.counters + verbose = self.options.verbose + filepatterns = self.options.filename + runner = self.runner + for root, dirs, files in os.walk(dirname): + if verbose: + print('directory ' + root) + counters['directories'] += 1 + for subdir in sorted(dirs): + if self.excluded(subdir, root): + dirs.remove(subdir) + for filename in sorted(files): + # contain a pattern that matches? + if ((filename_match(filename, filepatterns) and + not self.excluded(filename, root))): + runner(os.path.join(root, filename)) + + def excluded(self, filename, parent=None): + """Check if the file should be excluded. + + Check if 'options.exclude' contains a pattern that matches filename. + """ + if not self.options.exclude: + return False + basename = os.path.basename(filename) + if filename_match(basename, self.options.exclude): + return True + if parent: + filename = os.path.join(parent, filename) + filename = os.path.abspath(filename) + return filename_match(filename, self.options.exclude) + + def ignore_code(self, code): + """Check if the error code should be ignored. + + If 'options.select' contains a prefix of the error code, + return False. Else, if 'options.ignore' contains a prefix of + the error code, return True. + """ + if len(code) < 4 and any(s.startswith(code) + for s in self.options.select): + return False + return (code.startswith(self.options.ignore) and + not code.startswith(self.options.select)) + + def get_checks(self, argument_name): + """Get all the checks for this category. + + Find all globally visible functions where the first argument name + starts with argument_name and which contain selected tests. + """ + checks = [] + for check, attrs in _checks[argument_name].items(): + (codes, args) = attrs + if any(not (code and self.ignore_code(code)) for code in codes): + checks.append((check.__name__, check, args)) + return sorted(checks) + + +def get_parser(prog='pep8', version=__version__): + """Create the parser for the program.""" + parser = OptionParser(prog=prog, version=version, + usage="%prog [options] input ...") + parser.config_options = [ + 'exclude', 'filename', 'select', 'ignore', 'max-line-length', + 'hang-closing', 'count', 'format', 'quiet', 'show-pep8', + 'show-source', 'statistics', 'verbose'] + parser.add_option('-v', '--verbose', default=0, action='count', + help="print status messages, or debug with -vv") + parser.add_option('-q', '--quiet', default=0, action='count', + help="report only file names, or nothing with -qq") + parser.add_option('-r', '--repeat', default=True, action='store_true', + help="(obsolete) show all occurrences of the same error") + parser.add_option('--first', action='store_false', dest='repeat', + help="show first occurrence of each error") + parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, + help="exclude files or directories which match these " + "comma separated patterns (default: %default)") + parser.add_option('--filename', metavar='patterns', default='*.py', + help="when parsing directories, only check filenames " + "matching these comma separated patterns " + "(default: %default)") + parser.add_option('--select', metavar='errors', default='', + help="select errors and warnings (e.g. E,W6)") + parser.add_option('--ignore', metavar='errors', default='', + help="skip errors and warnings (e.g. E4,W) " + "(default: %s)" % DEFAULT_IGNORE) + parser.add_option('--show-source', action='store_true', + help="show source code for each error") + parser.add_option('--show-pep8', action='store_true', + help="show text of PEP 8 for each error " + "(implies --first)") + parser.add_option('--statistics', action='store_true', + help="count errors and warnings") + parser.add_option('--count', action='store_true', + help="print total number of errors and warnings " + "to standard error and set exit code to 1 if " + "total is not null") + parser.add_option('--max-line-length', type='int', metavar='n', + default=MAX_LINE_LENGTH, + help="set maximum allowed line length " + "(default: %default)") + parser.add_option('--hang-closing', action='store_true', + help="hang closing bracket instead of matching " + "indentation of opening bracket's line") + parser.add_option('--format', metavar='format', default='default', + help="set the error format [default|pylint|]") + parser.add_option('--diff', action='store_true', + help="report changes only within line number ranges in " + "the unified diff received on STDIN") + group = parser.add_option_group("Testing Options") + if os.path.exists(TESTSUITE_PATH): + group.add_option('--testsuite', metavar='dir', + help="run regression tests from dir") + group.add_option('--doctest', action='store_true', + help="run doctest on myself") + group.add_option('--benchmark', action='store_true', + help="measure processing speed") + return parser + + +def read_config(options, args, arglist, parser): + """Read and parse configurations. + + If a config file is specified on the command line with the "--config" + option, then only it is used for configuration. + + Otherwise, the user configuration (~/.config/pep8) and any local + configurations in the current directory or above will be merged together + (in that order) using the read method of ConfigParser. + """ + config = RawConfigParser() + + cli_conf = options.config + + local_dir = os.curdir + + if USER_CONFIG and os.path.isfile(USER_CONFIG): + if options.verbose: + print('user configuration: %s' % USER_CONFIG) + config.read(USER_CONFIG) + + parent = tail = args and os.path.abspath(os.path.commonprefix(args)) + while tail: + if config.read(os.path.join(parent, fn) for fn in PROJECT_CONFIG): + local_dir = parent + if options.verbose: + print('local configuration: in %s' % parent) + break + (parent, tail) = os.path.split(parent) + + if cli_conf and os.path.isfile(cli_conf): + if options.verbose: + print('cli configuration: %s' % cli_conf) + config.read(cli_conf) + + pep8_section = parser.prog + if config.has_section(pep8_section): + option_list = dict([(o.dest, o.type or o.action) + for o in parser.option_list]) + + # First, read the default values + (new_options, __) = parser.parse_args([]) + + # Second, parse the configuration + for opt in config.options(pep8_section): + if opt.replace('_', '-') not in parser.config_options: + print(" unknown option '%s' ignored" % opt) + continue + if options.verbose > 1: + print(" %s = %s" % (opt, config.get(pep8_section, opt))) + normalized_opt = opt.replace('-', '_') + opt_type = option_list[normalized_opt] + if opt_type in ('int', 'count'): + value = config.getint(pep8_section, opt) + elif opt_type == 'string': + value = config.get(pep8_section, opt) + if normalized_opt == 'exclude': + value = normalize_paths(value, local_dir) + else: + assert opt_type in ('store_true', 'store_false') + value = config.getboolean(pep8_section, opt) + setattr(new_options, normalized_opt, value) + + # Third, overwrite with the command-line options + (options, __) = parser.parse_args(arglist, values=new_options) + options.doctest = options.testsuite = False + return options + + +def process_options(arglist=None, parse_argv=False, config_file=None, + parser=None): + """Process options passed either via arglist or via command line args. + + Passing in the ``config_file`` parameter allows other tools, such as flake8 + to specify their own options to be processed in pep8. + """ + if not parser: + parser = get_parser() + if not parser.has_option('--config'): + group = parser.add_option_group("Configuration", description=( + "The project options are read from the [%s] section of the " + "tox.ini file or the setup.cfg file located in any parent folder " + "of the path(s) being processed. Allowed options are: %s." % + (parser.prog, ', '.join(parser.config_options)))) + group.add_option('--config', metavar='path', default=config_file, + help="user config file location") + # Don't read the command line if the module is used as a library. + if not arglist and not parse_argv: + arglist = [] + # If parse_argv is True and arglist is None, arguments are + # parsed from the command line (sys.argv) + (options, args) = parser.parse_args(arglist) + options.reporter = None + + if options.ensure_value('testsuite', False): + args.append(options.testsuite) + elif not options.ensure_value('doctest', False): + if parse_argv and not args: + if options.diff or any(os.path.exists(name) + for name in PROJECT_CONFIG): + args = ['.'] + else: + parser.error('input not specified') + options = read_config(options, args, arglist, parser) + options.reporter = parse_argv and options.quiet == 1 and FileReport + + options.filename = _parse_multi_options(options.filename) + options.exclude = normalize_paths(options.exclude) + options.select = _parse_multi_options(options.select) + options.ignore = _parse_multi_options(options.ignore) + + if options.diff: + options.reporter = DiffReport + stdin = stdin_get_value() + options.selected_lines = parse_udiff(stdin, options.filename, args[0]) + args = sorted(options.selected_lines) + + return options, args + + +def _parse_multi_options(options, split_token=','): + r"""Split and strip and discard empties. + + Turns the following: + + A, + B, + + into ["A", "B"] + """ + if options: + return [o.strip() for o in options.split(split_token) if o.strip()] + else: + return options + + +def _main(): + """Parse options and run checks on Python source.""" + import signal + + # Handle "Broken pipe" gracefully + try: + signal.signal(signal.SIGPIPE, lambda signum, frame: sys.exit(1)) + except AttributeError: + pass # not supported on Windows + + style_guide = StyleGuide(parse_argv=True) + options = style_guide.options + + if options.doctest or options.testsuite: + from testsuite.support import run_tests + report = run_tests(style_guide) + else: + report = style_guide.check_files() + + if options.statistics: + report.print_statistics() + + if options.benchmark: + report.print_benchmark() + + if options.testsuite and not options.quiet: + report.print_results() + + if report.total_errors: + if options.count: + sys.stderr.write(str(report.total_errors) + '\n') + sys.exit(1) + + +def _main_pep8(): + """Entrypoint for pep8 commandline tool. + + Warn of deprecation and advise users to switch to pycodestyle. + """ + print( + 'Deprecation Warning:\n' + 'pep8 has been renamed to pycodestyle and the use of the pep8 ' + 'executable will be removed in a future release. Please use ' + '`pycodestyle` instead.\n' + ) + _main() + + +if __name__ == '__main__': + _main() diff --git a/setup.py b/setup.py index b767119..c0afd37 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ from setuptools import setup def get_version(): - with open('pep8.py') as f: + with open('pycodestyle.py') as f: for line in f: if line.startswith('__version__'): return eval(line.split('=')[-1]) @@ -19,16 +19,16 @@ def get_long_description(): setup( - name='pep8', + name='pycodestyle', version=get_version(), description="Python style guide checker", long_description=get_long_description(), - keywords='pep8', + keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', url='http://pep8.readthedocs.org/', license='Expat license', - py_modules=['pep8'], + py_modules=['pycodestyle'], namespace_packages=[], include_package_data=True, zip_safe=False, @@ -38,8 +38,8 @@ setup( ], entry_points={ 'console_scripts': [ - 'pycodestyle = pep8:_main', - 'pep8 = pep8:_main_pep8', + 'pycodestyle = pycodestyle:_main', + 'pep8 = pycodestyle:_main_pep8', ], }, classifiers=[ -- cgit v1.2.1 From 59a9af64174132e69e101e625e30e0c7a9254a17 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 10:45:03 -0700 Subject: Updated header comment from pep8 -> pycodestyle --- pycodestyle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 6776303..87f8f62 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -1,5 +1,6 @@ #!/usr/bin/env python -# pep8.py - Check Python source code formatting, according to PEP 8 +# pycodestyle.py - Check Python source code formatting, according to PEP 8 +# # Copyright (C) 2006-2009 Johann C. Rocholl # Copyright (C) 2009-2014 Florent Xicluna # Copyright (C) 2014-2016 Ian Lee -- cgit v1.2.1 From 07647a06e45157caf86ea50a8e9d6d1951f32425 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 10:47:39 -0700 Subject: Fixed import in testsuite --- testsuite/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/support.py b/testsuite/support.py index 6bc795d..61a5427 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -3,7 +3,7 @@ import os.path import re import sys -from pep8 import Checker, BaseReport, StandardReport, readlines +from pycodestyle import Checker, BaseReport, StandardReport, readlines SELFTEST_REGEX = re.compile(r'\b(Okay|[EW]\d{3}):\s(.*)') ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) -- cgit v1.2.1 From 4d5f3e405cc3e587cdbc52d4e0828a6ece4eaf95 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 11:43:02 -0700 Subject: Updated Travis configuration for package rename --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3149cbd..8771cca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,9 @@ install: - pip install -e . - pip list script: - - python pep8.py --testsuite testsuite - - python pep8.py --statistics pep8.py - - python pep8.py --doctest + - python pycodestyle.py --testsuite testsuite + - python pycodestyle.py --statistics pep8.py + - python pycodestyle.py --doctest - python setup.py test notifications: -- cgit v1.2.1 From e2f023d6bce383228a65b8c033b8fa8fc31bfcf9 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 11:43:27 -0700 Subject: Updated tox for package rename --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 5661fbb..958b172 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython [testenv] commands = {envpython} setup.py install - {envpython} pep8.py --testsuite testsuite - {envpython} pep8.py --statistics pep8.py - {envpython} pep8.py --doctest + {envpython} pycodestyle.py --testsuite testsuite + {envpython} pycodestyle.py --statistics pep8.py + {envpython} pycodestyle.py --doctest {envpython} -m testsuite.test_all -- cgit v1.2.1 From fed323d41fd8d2da73b8dd6db8ef1759a0d6b9c0 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sat, 21 May 2016 11:44:17 -0700 Subject: Updated makefile for package rename --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 29b243d..146a2cc 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ test : - python pep8.py --testsuite testsuite + python pycodestyle.py --testsuite testsuite selftest : - python pep8.py --statistics pep8.py + python pycodestyle.py --statistics pep8.py doctest : - python pep8.py --doctest + python pycodestyle.py --doctest unittest : python -m testsuite.test_all -- cgit v1.2.1 From fba2cc63736fa9d522fdc90d2d3cae1ff4aeaa90 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 22 May 2016 00:35:09 -0700 Subject: Fixed additional reference to pep8.py in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8771cca..ee069e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: - pip list script: - python pycodestyle.py --testsuite testsuite - - python pycodestyle.py --statistics pep8.py + - python pycodestyle.py --statistics pycodestyle.py - python pycodestyle.py --doctest - python setup.py test -- cgit v1.2.1 From 90d83aa28ae6ed0ebc49cc83079364b172405fb8 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 25 May 2016 10:49:18 -0700 Subject: Updated file names tested --- Makefile | 2 +- pycodestyle.py | 2 +- testsuite/support.py | 2 +- tox.ini | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 146a2cc..366f580 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ test : python pycodestyle.py --testsuite testsuite selftest : - python pycodestyle.py --statistics pep8.py + python pycodestyle.py --statistics pycodestyle.py doctest : python pycodestyle.py --doctest diff --git a/pycodestyle.py b/pycodestyle.py index 87f8f62..5e90dcf 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -29,7 +29,7 @@ r""" Check Python source code formatting, according to PEP 8. For usage and a list of options, try this: -$ python pep8.py -h +$ python pycodestyle.py -h This program and its regression test suite live here: https://github.com/pycqa/pycodestyle diff --git a/testsuite/support.py b/testsuite/support.py index 61a5427..003f181 100644 --- a/testsuite/support.py +++ b/testsuite/support.py @@ -119,7 +119,7 @@ def selftest(options): print("%s: %s" % (code, source)) else: count_failed += 1 - print("pep8.py: %s:" % error) + print("pycodestyle.py: %s:" % error) for line in checker.lines: print(line.rstrip()) return count_failed, count_all diff --git a/tox.ini b/tox.ini index 958b172..a4992e9 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,6 @@ envlist = py26, py27, py32, py33, py34, py35, pypy, pypy3, jython commands = {envpython} setup.py install {envpython} pycodestyle.py --testsuite testsuite - {envpython} pycodestyle.py --statistics pep8.py + {envpython} pycodestyle.py --statistics pycodestyle.py {envpython} pycodestyle.py --doctest {envpython} -m testsuite.test_all -- cgit v1.2.1 From ccc7f14000d92e8dc08bbdf34b9fac3435425087 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Wed, 25 May 2016 11:25:57 -0700 Subject: Updated tests and doc examples -> pycodestyle --- docs/advanced.rst | 22 +++---- testsuite/test_all.py | 16 +++--- testsuite/test_api.py | 145 ++++++++++++++++++++++++----------------------- testsuite/test_parser.py | 4 +- testsuite/test_shell.py | 26 ++++----- testsuite/test_util.py | 20 +++---- 6 files changed, 119 insertions(+), 114 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index de3be69..4f72e6e 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -13,14 +13,14 @@ can be highly useful for automated testing of coding style conformance in your project:: import unittest - import pep8 + import pycodestyle class TestCodeFormat(unittest.TestCase): def test_pep8_conformance(self): """Test that we conform to PEP8.""" - pep8style = pep8.StyleGuide(quiet=True) + pep8style = pycodestyle.StyleGuide(quiet=True) result = pep8style.check_files(['file1.py', 'file2.py']) self.assertEqual(result.total_errors, 0, "Found code style errors (and warnings).") @@ -30,9 +30,9 @@ since Nose suppresses stdout. There's also a shortcut for checking a single file:: - import pep8 + import pycodestyle - fchecker = pep8.Checker('testsuite/E27.py', show_source=True) + fchecker = pycodestyle.Checker('testsuite/E27.py', show_source=True) file_errors = fchecker.check_all() print("Found %s errors (and warnings)" % file_errors) @@ -46,13 +46,13 @@ You can configure automated ``pep8`` tests in a variety of ways. For example, you can pass in a path to a configuration file that ``pep8`` should use:: - import pep8 + import pycodestyle - pep8style = pep8.StyleGuide(config_file='/path/to/tox.ini') + pep8style = pycodestyle.StyleGuide(config_file='/path/to/tox.ini') You can also set specific options explicitly:: - pep8style = pep8.StyleGuide(ignore=['E501']) + pep8style = pycodestyle.StyleGuide(ignore=['E501']) Skip file header @@ -64,19 +64,19 @@ at the beginning and the end of a file. This use case is easy to implement through a custom wrapper for the PEP 8 library:: #!python - import pep8 + import pycodestyle LINES_SLICE = slice(14, -20) - class PEP8(pep8.StyleGuide): - """This subclass of pep8.StyleGuide will skip the first and last lines + class PEP8(pycodestyle.StyleGuide): + """This subclass of pycodestyle.StyleGuide will skip the first and last lines of each file.""" def input_file(self, filename, lines=None, expected=None, line_offset=0): if lines is None: assert line_offset == 0 line_offset = LINES_SLICE.start or 0 - lines = pep8.readlines(filename)[LINES_SLICE] + lines = pycodestyle.readlines(filename)[LINES_SLICE] return super(PEP8, self).input_file( filename, lines=lines, expected=expected, line_offset=line_offset) diff --git a/testsuite/test_all.py b/testsuite/test_all.py index bfb61d5..bd18943 100644 --- a/testsuite/test_all.py +++ b/testsuite/test_all.py @@ -4,24 +4,26 @@ import os.path import sys import unittest -import pep8 +import pycodestyle from testsuite.support import init_tests, selftest, ROOT_DIR # Note: please only use a subset of unittest methods which were present # in Python 2.5: assert(True|False|Equal|NotEqual|Raises) -class Pep8TestCase(unittest.TestCase): +class PycodestyleTestCase(unittest.TestCase): """Test the standard errors and warnings (E and W).""" def setUp(self): - self._style = pep8.StyleGuide( + self._style = pycodestyle.StyleGuide( paths=[os.path.join(ROOT_DIR, 'testsuite')], select='E,W', quiet=True) def test_doctest(self): import doctest - fail_d, done_d = doctest.testmod(pep8, verbose=False, report=False) + fail_d, done_d = doctest.testmod( + pycodestyle, verbose=False, report=False + ) self.assertTrue(done_d, msg='tests not found') self.assertFalse(fail_d, msg='%s failure(s)' % fail_d) @@ -37,9 +39,9 @@ class Pep8TestCase(unittest.TestCase): msg='%s failure(s)' % report.total_errors) def test_own_dog_food(self): - files = [pep8.__file__.rstrip('oc'), __file__.rstrip('oc'), + files = [pycodestyle.__file__.rstrip('oc'), __file__.rstrip('oc'), os.path.join(ROOT_DIR, 'setup.py')] - report = self._style.init_report(pep8.StandardReport) + report = self._style.init_report(pycodestyle.StandardReport) report = self._style.check_files(files) self.assertFalse(report.total_errors, msg='Failures: %s' % report.messages) @@ -49,7 +51,7 @@ def suite(): from testsuite import test_api, test_parser, test_shell, test_util suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(Pep8TestCase)) + suite.addTest(unittest.makeSuite(PycodestyleTestCase)) suite.addTest(unittest.makeSuite(test_api.APITestCase)) suite.addTest(unittest.makeSuite(test_parser.ParserTestCase)) suite.addTest(unittest.makeSuite(test_shell.ShellTestCase)) diff --git a/testsuite/test_api.py b/testsuite/test_api.py index baafff7..6549a46 100644 --- a/testsuite/test_api.py +++ b/testsuite/test_api.py @@ -4,7 +4,7 @@ import shlex import sys import unittest -import pep8 +import pycodestyle from testsuite.support import ROOT_DIR, PseudoFile E11 = os.path.join(ROOT_DIR, 'testsuite', 'E11.py') @@ -25,17 +25,18 @@ class APITestCase(unittest.TestCase): def setUp(self): self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr - self._saved_checks = pep8._checks + self._saved_checks = pycodestyle._checks sys.stdout = PseudoFile() sys.stderr = PseudoFile() - pep8._checks = dict((k, dict((f, (vals[0][:], vals[1])) - for (f, vals) in v.items())) - for (k, v) in self._saved_checks.items()) + pycodestyle._checks = dict( + (k, dict((f, (vals[0][:], vals[1])) for (f, vals) in v.items())) + for (k, v) in self._saved_checks.items() + ) def tearDown(self): sys.stdout = self._saved_stdout sys.stderr = self._saved_stderr - pep8._checks = self._saved_checks + pycodestyle._checks = self._saved_checks def reset(self): del sys.stdout[:], sys.stderr[:] @@ -44,14 +45,14 @@ class APITestCase(unittest.TestCase): def check_dummy(physical_line, line_number): if False: yield - pep8.register_check(check_dummy, ['Z001']) + pycodestyle.register_check(check_dummy, ['Z001']) - self.assertTrue(check_dummy in pep8._checks['physical_line']) - codes, args = pep8._checks['physical_line'][check_dummy] + self.assertTrue(check_dummy in pycodestyle._checks['physical_line']) + codes, args = pycodestyle._checks['physical_line'][check_dummy] self.assertTrue('Z001' in codes) self.assertEqual(args, ['physical_line', 'line_number']) - options = pep8.StyleGuide().options + options = pycodestyle.StyleGuide().options self.assertTrue(any(func == check_dummy for name, func, args in options.physical_checks)) @@ -59,32 +60,32 @@ class APITestCase(unittest.TestCase): def check_dummy(logical_line, tokens): if False: yield - pep8.register_check(check_dummy, ['Z401']) + pycodestyle.register_check(check_dummy, ['Z401']) - self.assertTrue(check_dummy in pep8._checks['logical_line']) - codes, args = pep8._checks['logical_line'][check_dummy] + self.assertTrue(check_dummy in pycodestyle._checks['logical_line']) + codes, args = pycodestyle._checks['logical_line'][check_dummy] self.assertTrue('Z401' in codes) self.assertEqual(args, ['logical_line', 'tokens']) - pep8.register_check(check_dummy, []) - pep8.register_check(check_dummy, ['Z402', 'Z403']) - codes, args = pep8._checks['logical_line'][check_dummy] + pycodestyle.register_check(check_dummy, []) + pycodestyle.register_check(check_dummy, ['Z402', 'Z403']) + codes, args = pycodestyle._checks['logical_line'][check_dummy] self.assertEqual(codes, ['Z401', 'Z402', 'Z403']) self.assertEqual(args, ['logical_line', 'tokens']) - options = pep8.StyleGuide().options + options = pycodestyle.StyleGuide().options self.assertTrue(any(func == check_dummy for name, func, args in options.logical_checks)) def test_register_ast_check(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) - self.assertTrue(DummyChecker in pep8._checks['tree']) - codes, args = pep8._checks['tree'][DummyChecker] + self.assertTrue(DummyChecker in pycodestyle._checks['tree']) + codes, args = pycodestyle._checks['tree'][DummyChecker] self.assertTrue('Z701' in codes) self.assertTrue(args is None) - options = pep8.StyleGuide().options + options = pycodestyle.StyleGuide().options self.assertTrue(any(cls == DummyChecker for name, cls, args in options.ast_checks)) @@ -96,23 +97,23 @@ class APITestCase(unittest.TestCase): def check_dummy(logical, tokens): if False: yield - pep8.register_check(InvalidChecker, ['Z741']) - pep8.register_check(check_dummy, ['Z441']) + pycodestyle.register_check(InvalidChecker, ['Z741']) + pycodestyle.register_check(check_dummy, ['Z441']) - for checkers in pep8._checks.values(): + for checkers in pycodestyle._checks.values(): self.assertTrue(DummyChecker not in checkers) self.assertTrue(check_dummy not in checkers) - self.assertRaises(TypeError, pep8.register_check) + self.assertRaises(TypeError, pycodestyle.register_check) def test_styleguide(self): - report = pep8.StyleGuide().check_files() + report = pycodestyle.StyleGuide().check_files() self.assertEqual(report.total_errors, 0) self.assertFalse(sys.stdout) self.assertFalse(sys.stderr) self.reset() - report = pep8.StyleGuide().check_files(['missing-file']) + report = pycodestyle.StyleGuide().check_files(['missing-file']) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 1) @@ -121,7 +122,7 @@ class APITestCase(unittest.TestCase): self.assertFalse(sys.stderr) self.reset() - report = pep8.StyleGuide().check_files([E11]) + report = pycodestyle.StyleGuide().check_files([E11]) stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 17) @@ -129,7 +130,7 @@ class APITestCase(unittest.TestCase): self.reset() # Passing the paths in the constructor gives same result - report = pep8.StyleGuide(paths=[E11]).check_files() + report = pycodestyle.StyleGuide(paths=[E11]).check_files() stdout = sys.stdout.getvalue().splitlines() self.assertEqual(len(stdout), report.total_errors) self.assertEqual(report.total_errors, 17) @@ -137,11 +138,11 @@ class APITestCase(unittest.TestCase): self.reset() def test_styleguide_options(self): - # Instantiate a simple checker - pep8style = pep8.StyleGuide(paths=[E11]) + # Instanciate a simple checker + pep8style = pycodestyle.StyleGuide(paths=[E11]) # Check style's attributes - self.assertEqual(pep8style.checker_class, pep8.Checker) + self.assertEqual(pep8style.checker_class, pycodestyle.Checker) self.assertEqual(pep8style.paths, [E11]) self.assertEqual(pep8style.runner, pep8style.input_file) self.assertEqual(pep8style.options.ignore_code, pep8style.ignore_code) @@ -173,7 +174,7 @@ class APITestCase(unittest.TestCase): _saved_argv = sys.argv sys.argv = shlex.split('pep8 %s /dev/null' % argstring) try: - return pep8.StyleGuide(parse_argv=True) + return pycodestyle.StyleGuide(parse_argv=True) finally: sys.argv = _saved_argv @@ -208,22 +209,22 @@ class APITestCase(unittest.TestCase): self.assertEqual(options.select, ('E24',)) self.assertEqual(options.ignore, ('',)) - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) self.assertFalse(pep8style.ignore_code('E112')) self.assertFalse(pep8style.ignore_code('W191')) self.assertTrue(pep8style.ignore_code('E241')) - pep8style = pep8.StyleGuide(select='E', paths=[E11]) + pep8style = pycodestyle.StyleGuide(select='E', paths=[E11]) self.assertFalse(pep8style.ignore_code('E112')) self.assertTrue(pep8style.ignore_code('W191')) self.assertFalse(pep8style.ignore_code('E241')) - pep8style = pep8.StyleGuide(select='W', paths=[E11]) + pep8style = pycodestyle.StyleGuide(select='W', paths=[E11]) self.assertTrue(pep8style.ignore_code('E112')) self.assertFalse(pep8style.ignore_code('W191')) self.assertTrue(pep8style.ignore_code('E241')) - pep8style = pep8.StyleGuide(select=('F401',), paths=[E11]) + pep8style = pycodestyle.StyleGuide(select=('F401',), paths=[E11]) self.assertEqual(pep8style.options.select, ('F401',)) self.assertEqual(pep8style.options.ignore, ('',)) self.assertFalse(pep8style.ignore_code('F')) @@ -231,7 +232,7 @@ class APITestCase(unittest.TestCase): self.assertTrue(pep8style.ignore_code('F402')) def test_styleguide_excluded(self): - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) self.assertFalse(pep8style.excluded('./foo/bar')) self.assertFalse(pep8style.excluded('./foo/bar/main.py')) @@ -248,7 +249,7 @@ class APITestCase(unittest.TestCase): self.assertFalse(pep8style.excluded('./CVS/subdir')) def test_styleguide_checks(self): - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) # Default lists of checkers self.assertTrue(len(pep8style.options.physical_checks) > 4) @@ -264,49 +265,51 @@ class APITestCase(unittest.TestCase): self.assertEqual(args[0], 'logical_line') # Do run E11 checks - options = pep8.StyleGuide().options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide().options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(select=['E']).options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide(select=['E']).options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['W']).options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['W']).options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['E12']).options - self.assertTrue(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['E12']).options + self.assertTrue(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) # Do not run E11 checks - options = pep8.StyleGuide(select=['W']).options - self.assertFalse(any(func == pep8.indentation + options = pycodestyle.StyleGuide(select=['W']).options + self.assertFalse(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['E']).options - self.assertFalse(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['E']).options + self.assertFalse(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) - options = pep8.StyleGuide(ignore=['E11']).options - self.assertFalse(any(func == pep8.indentation + options = pycodestyle.StyleGuide(ignore=['E11']).options + self.assertFalse(any(func == pycodestyle.indentation for name, func, args in options.logical_checks)) def test_styleguide_init_report(self): - pep8style = pep8.StyleGuide(paths=[E11]) + style = pycodestyle.StyleGuide(paths=[E11]) + + standard_report = pycodestyle.StandardReport - self.assertEqual(pep8style.options.reporter, pep8.StandardReport) - self.assertEqual(type(pep8style.options.report), pep8.StandardReport) + self.assertEqual(style.options.reporter, standard_report) + self.assertEqual(type(style.options.report), standard_report) - class MinorityReport(pep8.BaseReport): + class MinorityReport(pycodestyle.BaseReport): pass - report = pep8style.init_report(MinorityReport) - self.assertEqual(pep8style.options.report, report) + report = style.init_report(MinorityReport) + self.assertEqual(style.options.report, report) self.assertEqual(type(report), MinorityReport) - pep8style = pep8.StyleGuide(paths=[E11], reporter=MinorityReport) - self.assertEqual(type(pep8style.options.report), MinorityReport) - self.assertEqual(pep8style.options.reporter, MinorityReport) + style = pycodestyle.StyleGuide(paths=[E11], reporter=MinorityReport) + self.assertEqual(type(style.options.report), MinorityReport) + self.assertEqual(style.options.reporter, MinorityReport) def test_styleguide_check_files(self): - pep8style = pep8.StyleGuide(paths=[E11]) + pep8style = pycodestyle.StyleGuide(paths=[E11]) report = pep8style.check_files() self.assertTrue(report.total_errors) @@ -317,12 +320,12 @@ class APITestCase(unittest.TestCase): def test_check_unicode(self): # Do not crash if lines are Unicode (Python 2.x) - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) source = '#\n' if hasattr(source, 'decode'): source = source.decode('ascii') - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=[source]) self.assertFalse(sys.stdout) @@ -330,9 +333,9 @@ class APITestCase(unittest.TestCase): self.assertEqual(count_errors, 0) def test_check_nullbytes(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=['\x00\n']) stdout = sys.stdout.getvalue() @@ -351,13 +354,13 @@ class APITestCase(unittest.TestCase): self.assertEqual(count_errors, 1) def test_styleguide_unmatched_triple_quotes(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) lines = [ 'def foo():\n', ' """test docstring""\'\n', ] - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() pep8style.input_file('stdin', lines=lines) stdout = sys.stdout.getvalue() @@ -365,7 +368,7 @@ class APITestCase(unittest.TestCase): self.assertTrue(expected in stdout) def test_styleguide_continuation_line_outdented(self): - pep8.register_check(DummyChecker, ['Z701']) + pycodestyle.register_check(DummyChecker, ['Z701']) lines = [ 'def foo():\n', ' pass\n', @@ -376,7 +379,7 @@ class APITestCase(unittest.TestCase): ' pass\n', ] - pep8style = pep8.StyleGuide() + pep8style = pycodestyle.StyleGuide() count_errors = pep8style.input_file('stdin', lines=lines) self.assertEqual(count_errors, 2) stdout = sys.stdout.getvalue() diff --git a/testsuite/test_parser.py b/testsuite/test_parser.py index 1d9e1ac..26a45fc 100644 --- a/testsuite/test_parser.py +++ b/testsuite/test_parser.py @@ -2,14 +2,14 @@ import os import tempfile import unittest -import pep8 +import pycodestyle def _process_file(contents): with tempfile.NamedTemporaryFile(delete=False) as f: f.write(contents) - options, args = pep8.process_options(config_file=f.name) + options, args = pycodestyle.process_options(config_file=f.name) os.remove(f.name) return options, args diff --git a/testsuite/test_shell.py b/testsuite/test_shell.py index e536852..760c228 100644 --- a/testsuite/test_shell.py +++ b/testsuite/test_shell.py @@ -3,7 +3,7 @@ import os.path import sys import unittest -import pep8 +import pycodestyle from testsuite.support import ROOT_DIR, PseudoFile @@ -14,9 +14,9 @@ class ShellTestCase(unittest.TestCase): self._saved_argv = sys.argv self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr - self._saved_pconfig = pep8.PROJECT_CONFIG - self._saved_cpread = pep8.RawConfigParser._read - self._saved_stdin_get_value = pep8.stdin_get_value + self._saved_pconfig = pycodestyle.PROJECT_CONFIG + self._saved_cpread = pycodestyle.RawConfigParser._read + self._saved_stdin_get_value = pycodestyle.stdin_get_value self._config_filenames = [] self.stdin = '' sys.argv = ['pep8'] @@ -25,16 +25,16 @@ class ShellTestCase(unittest.TestCase): def fake_config_parser_read(cp, fp, filename): self._config_filenames.append(filename) - pep8.RawConfigParser._read = fake_config_parser_read - pep8.stdin_get_value = self.stdin_get_value + pycodestyle.RawConfigParser._read = fake_config_parser_read + pycodestyle.stdin_get_value = self.stdin_get_value def tearDown(self): sys.argv = self._saved_argv sys.stdout = self._saved_stdout sys.stderr = self._saved_stderr - pep8.PROJECT_CONFIG = self._saved_pconfig - pep8.RawConfigParser._read = self._saved_cpread - pep8.stdin_get_value = self._saved_stdin_get_value + pycodestyle.PROJECT_CONFIG = self._saved_pconfig + pycodestyle.RawConfigParser._read = self._saved_cpread + pycodestyle.stdin_get_value = self._saved_stdin_get_value def stdin_get_value(self): return self.stdin @@ -43,7 +43,7 @@ class ShellTestCase(unittest.TestCase): del sys.stdout[:], sys.stderr[:] sys.argv[1:] = args try: - pep8._main() + pycodestyle._main() errorcode = None except SystemExit: errorcode = sys.exc_info()[1].code @@ -88,7 +88,7 @@ class ShellTestCase(unittest.TestCase): self.assertTrue('setup.cfg' in config_filenames) def test_check_stdin(self): - pep8.PROJECT_CONFIG = () + pycodestyle.PROJECT_CONFIG = () stdout, stderr, errcode = self.pep8('-') self.assertFalse(errcode) self.assertFalse(stderr) @@ -111,7 +111,7 @@ class ShellTestCase(unittest.TestCase): def test_check_noarg(self): # issue #170: do not read stdin by default - pep8.PROJECT_CONFIG = () + pycodestyle.PROJECT_CONFIG = () stdout, stderr, errcode = self.pep8() self.assertEqual(errcode, 2) self.assertEqual(stderr.splitlines(), @@ -120,7 +120,7 @@ class ShellTestCase(unittest.TestCase): self.assertFalse(self._config_filenames) def test_check_diff(self): - pep8.PROJECT_CONFIG = () + pycodestyle.PROJECT_CONFIG = () diff_lines = [ "--- testsuite/E11.py 2006-06-01 08:49:50 +0500", "+++ testsuite/E11.py 2008-04-06 17:36:29 +0500", diff --git a/testsuite/test_util.py b/testsuite/test_util.py index 11395cc..8eaba7e 100644 --- a/testsuite/test_util.py +++ b/testsuite/test_util.py @@ -3,21 +3,21 @@ import os import unittest -import pep8 +from pycodestyle import normalize_paths class UtilTestCase(unittest.TestCase): def test_normalize_paths(self): cwd = os.getcwd() - self.assertEqual(pep8.normalize_paths(''), []) - self.assertEqual(pep8.normalize_paths([]), []) - self.assertEqual(pep8.normalize_paths(None), []) - self.assertEqual(pep8.normalize_paths(['foo']), ['foo']) - self.assertEqual(pep8.normalize_paths('foo'), ['foo']) - self.assertEqual(pep8.normalize_paths('foo,bar'), ['foo', 'bar']) - self.assertEqual(pep8.normalize_paths('foo, bar '), ['foo', 'bar']) - self.assertEqual(pep8.normalize_paths('/foo/bar,baz/../bat'), + self.assertEqual(normalize_paths(''), []) + self.assertEqual(normalize_paths([]), []) + self.assertEqual(normalize_paths(None), []) + self.assertEqual(normalize_paths(['foo']), ['foo']) + self.assertEqual(normalize_paths('foo'), ['foo']) + self.assertEqual(normalize_paths('foo,bar'), ['foo', 'bar']) + self.assertEqual(normalize_paths('foo, bar '), ['foo', 'bar']) + self.assertEqual(normalize_paths('/foo/bar,baz/../bat'), ['/foo/bar', cwd + '/bat']) - self.assertEqual(pep8.normalize_paths(".pyc,\n build/*"), + self.assertEqual(normalize_paths(".pyc,\n build/*"), ['.pyc', cwd + '/build/*']) -- cgit v1.2.1 From f39501c475338b23021245fa9df7b4e8cea3a875 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Sun, 29 May 2016 14:39:21 +0100 Subject: Convert readthedocs link for their .org -> .io migration for hosted projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per their email ‘Changes to project subdomains’: > Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard. Test Plan: Manually visited all the links I’ve modified. --- CHANGES.txt | 2 +- CONTRIBUTING.rst | 2 +- README.rst | 2 +- docs/index.rst | 2 +- docs/intro.rst | 2 +- setup.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d22214f..ebb00e8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -406,7 +406,7 @@ Bug fixes: (Issue #93 and #141) * Add the Sphinx-based documentation, and publish it - on http://pep8.readthedocs.org/. (Issue #105) + on https://pep8.readthedocs.io/. (Issue #105) 1.3.4 (2012-12-18) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 158dc6f..0500f0b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing to ``pep8`` ======================== -Please see the `developer notes `_ +Please see the `developer notes `_ diff --git a/README.rst b/README.rst index 3ba1619..f82589d 100644 --- a/README.rst +++ b/README.rst @@ -91,6 +91,6 @@ Links :target: https://pypi.python.org/pypi/pycodestyle :alt: Wheel Status -* `Read the documentation `_ +* `Read the documentation `_ * `Fork me on GitHub `_ diff --git a/docs/index.rst b/docs/index.rst index d977022..6ae3815 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Contents: API developer -* Online documentation: http://pep8.readthedocs.org/ +* Online documentation: https://pep8.readthedocs.io/ * Source code and issue tracker: https://github.com/pycqa/pycodestyle diff --git a/docs/intro.rst b/docs/intro.rst index 5f7e9cd..91cfbfe 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -429,7 +429,7 @@ Note: most errors can be listed with such one-liner:: Related tools ------------- -The `flake8 checker `_ is a wrapper around +The `flake8 checker `_ is a wrapper around ``pep8`` and similar tools. It supports plugins. Other tools which use ``pep8`` are referenced in the Wiki: `list of related diff --git a/setup.py b/setup.py index c0afd37..7c0cf84 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', - url='http://pep8.readthedocs.org/', + url='https://pep8.readthedocs.io/', license='Expat license', py_modules=['pycodestyle'], namespace_packages=[], -- cgit v1.2.1 From 6fd9e6f29303ca150f407c2b24e99c9336954346 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 10:58:51 -0700 Subject: Finished updating rtd links Updated references to pep8.readthedocs.io -> pycodestyle.readthedocs.io --- CHANGES.txt | 2 +- CONTRIBUTING.rst | 2 +- docs/index.rst | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ebb00e8..4f8e28d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -406,7 +406,7 @@ Bug fixes: (Issue #93 and #141) * Add the Sphinx-based documentation, and publish it - on https://pep8.readthedocs.io/. (Issue #105) + on https://pycodestyle.readthedocs.io/. (Issue #105) 1.3.4 (2012-12-18) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0500f0b..61c2822 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing to ``pep8`` ======================== -Please see the `developer notes `_ +Please see the `developer notes `_ diff --git a/docs/index.rst b/docs/index.rst index 6ae3815..d6ece44 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Contents: API developer -* Online documentation: https://pep8.readthedocs.io/ +* Online documentation: https://pycodestyle.readthedocs.io/ * Source code and issue tracker: https://github.com/pycqa/pycodestyle diff --git a/setup.py b/setup.py index 7c0cf84..9e6dd68 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', - url='https://pep8.readthedocs.io/', + url='https://pycodestyle.readthedocs.io/', license='Expat license', py_modules=['pycodestyle'], namespace_packages=[], -- cgit v1.2.1 From dde7b56ba8cf1a721bbff43f39d428235807c74a Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 16:36:37 -0700 Subject: Added maintainer metadata to package --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 9e6dd68..93a1337 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,8 @@ setup( keywords='pycodestyle, pep8, PEP 8, PEP-8, PEP8', author='Johann C. Rocholl', author_email='johann@rocholl.net', + maintainer='Ian Lee', + maintainer_email='IanLee1521@gmail.com', url='https://pycodestyle.readthedocs.io/', license='Expat license', py_modules=['pycodestyle'], -- cgit v1.2.1 From 23d95cd4246bbf71e5ce2df1e70bd915de43496d Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 17:04:49 -0700 Subject: Updated project name in docs Updated from references to pep8 -> pycodestyle, this fixes the sphinx build errors caught by readthedocs. --- docs/Makefile | 8 ++++---- docs/advanced.rst | 26 +++++++++++++------------- docs/api.rst | 10 +++++----- docs/conf.py | 20 ++++++++++---------- docs/developer.rst | 18 +++++++++--------- docs/index.rst | 12 ++++++------ docs/intro.rst | 45 ++++++++++++++++++++------------------------- docs/make.bat | 4 ++-- 8 files changed, 69 insertions(+), 74 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 1952db4..96c920f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -77,17 +77,17 @@ qthelp: @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pep8.qhcp" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pycodestyle.qhcp" @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pep8.qhc" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pycodestyle.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/pep8" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pep8" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pycodestyle" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pycodestyle" @echo "# devhelp" epub: diff --git a/docs/advanced.rst b/docs/advanced.rst index 4f72e6e..6adb2cb 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1,4 +1,4 @@ -.. currentmodule:: pep8 +.. currentmodule:: pycodestyle ============== Advanced usage @@ -8,7 +8,7 @@ Advanced usage Automated tests --------------- -You can also execute ``pep8`` tests from Python code. For example, this +You can also execute ``pycodestyle`` tests from Python code. For example, this can be highly useful for automated testing of coding style conformance in your project:: @@ -18,10 +18,10 @@ in your project:: class TestCodeFormat(unittest.TestCase): - def test_pep8_conformance(self): - """Test that we conform to PEP8.""" - pep8style = pycodestyle.StyleGuide(quiet=True) - result = pep8style.check_files(['file1.py', 'file2.py']) + def test_conformance(self): + """Test that we conform to PEP-8.""" + style = pycodestyle.StyleGuide(quiet=True) + result = style.check_files(['file1.py', 'file2.py']) self.assertEqual(result.total_errors, 0, "Found code style errors (and warnings).") @@ -41,18 +41,18 @@ There's also a shortcut for checking a single file:: Configuring tests ----------------- -You can configure automated ``pep8`` tests in a variety of ways. +You can configure automated ``pycodestyle`` tests in a variety of ways. -For example, you can pass in a path to a configuration file that ``pep8`` +For example, you can pass in a path to a configuration file that ``pycodestyle`` should use:: import pycodestyle - pep8style = pycodestyle.StyleGuide(config_file='/path/to/tox.ini') + style = pycodestyle.StyleGuide(config_file='/path/to/tox.ini') You can also set specific options explicitly:: - pep8style = pycodestyle.StyleGuide(ignore=['E501']) + style = pycodestyle.StyleGuide(ignore=['E501']) Skip file header @@ -81,8 +81,8 @@ through a custom wrapper for the PEP 8 library:: filename, lines=lines, expected=expected, line_offset=line_offset) if __name__ == '__main__': - pep8style = PEP8(parse_argv=True, config_file=True) - report = pep8style.check_files() + style = PEP8(parse_argv=True, config_file=True) + report = style.check_files() if report.total_errors: raise SystemExit(1) @@ -91,4 +91,4 @@ and 20 lines at the end. If there's no line to skip at the end, it could be changed with ``LINES_SLICE = slice(14, None)`` for example. You can save it in a file and use it with the same options as the -original ``pep8``. +original ``pycodestyle``. diff --git a/docs/api.rst b/docs/api.rst index b346aba..f9c7066 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,8 +1,8 @@ -======== -pep8 API -======== +=============== +pycodestyle API +=============== -.. module:: pep8 +.. module:: pycodestyle The library provides classes which are usable by third party tools. @@ -84,5 +84,5 @@ Utilities .. autofunction:: stdin_get_value() .. autofunction:: parse_udiff(diff, patterns=None, parent='.') .. autofunction:: filename_match(filename, patterns, default=True) - .. autofunction:: get_parser(prog='pep8', version=pep8.__version__) + .. autofunction:: get_parser(prog='pycodestyle', version=pycodestyle.__version__) .. autofunction:: init_checks_registry() diff --git a/docs/conf.py b/docs/conf.py index 5f990f3..7935b0b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# pep8 documentation build configuration file, created by +# pycodestyle documentation build configuration file, created by # sphinx-quickstart on Tue Aug 21 09:47:49 2012. # # This file is execfile()d with the current directory set to its @@ -44,7 +44,7 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'pep8' +project = u'pycodestyle' authors = u'Johann C. Rocholl, Florent Xicluna, Ian Lee' copyright = u'2006-2016, %s' % (authors) @@ -53,11 +53,11 @@ copyright = u'2006-2016, %s' % (authors) # built documents. # -pep8_version = __import__('pep8').__version__.split('.') +pkg_version = __import__('pycodestyle').__version__.split('.') # The short X.Y version. -version = '.'.join(pep8_version[:2]) +version = '.'.join(pkg_version[:2]) # The full version, including alpha/beta/rc tags. -release = '.'.join(pep8_version) +release = '.'.join(pkg_version) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -176,7 +176,7 @@ if not on_rtd: # only import and set the theme if we're building docs locally #html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'pep8doc' +htmlhelp_basename = 'pycodestyledoc' # -- Options for LaTeX output ------------------------------------------------- @@ -196,7 +196,7 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto/manual]). latex_documents = [ - ('index', 'pep8.tex', u'pep8 documentation', + ('index', 'pycodestyle.tex', u'pycodestyle documentation', authors, 'manual'), ] @@ -226,7 +226,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pep8', u'pep8 documentation', + ('index', 'pycodestyle', u'pycodestyle documentation', [authors], 1) ] @@ -240,8 +240,8 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'pep8', u'pep8 documentation', authors, - 'pep8', 'One line description of project.', + ('index', 'pycodestyle', u'pycodestyle documentation', authors, + 'pycodestyle', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/developer.rst b/docs/developer.rst index bbafb80..0aefbed 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -1,4 +1,4 @@ -.. currentmodule:: pep8 +.. currentmodule:: pycodestyle ================= Developer's notes @@ -13,7 +13,7 @@ conditions of the :ref:`Expat license `. Fork away! * `Source code `_ and `issue tracker `_ on GitHub. -* `Continuous tests `_ against Python +* `Continuous tests `_ against Python 2.6 through 3.4 and PyPy, on `Travis-CI platform `_. @@ -25,14 +25,14 @@ Direction Some high-level aims and directions to bear in mind for contributions: -* ``pep8`` is intended to be as fast as possible. +* ``pycodestyle`` is intended to be as fast as possible. Using the ``ast`` module defeats that purpose. The `pep8-naming `_ plugin exists for this sort of functionality. * If you want to provide extensibility / plugins, please see `flake8 `_ - - ``pep8`` doesn't want or need a plugin architecture. + ``pycodestyle`` doesn't want or need a plugin architecture. * Python 2.6 support is still deemed important. -* ``pep8`` aims to have no external dependencies. +* ``pycodestyle`` aims to have no external dependencies. Contribute @@ -89,7 +89,7 @@ Several docstrings contain examples directly from the `PEP 8`_ document. Okay: spam(ham[1], {eggs: 2}) E201: spam( ham[1], {eggs: 2}) -These examples are verified automatically when pep8.py is run with the +These examples are verified automatically when pycodestyle.py is run with the ``--doctest`` option. You can add examples for your own check functions. The format is simple: ``"Okay"`` or error/warning code followed by colon and space, the rest of the line is example source code. If you put ``'r'`` @@ -97,9 +97,9 @@ before the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: - $ python pep8.py --testsuite testsuite - $ python pep8.py --doctest - $ python pep8.py --verbose pep8.py + $ python pycodestyle.py --testsuite testsuite + $ python pycodestyle.py --doctest + $ python pycodestyle.py --verbose pycodestyle.py When contributing to pycodestyle, please observe our `Code of Conduct`_. diff --git a/docs/index.rst b/docs/index.rst index d6ece44..0764e9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,12 +1,12 @@ -.. pep8 documentation master file +.. pycodestyle documentation master file -pep8's documentation -==================== +pycodestyle's documentation +=========================== *Python style guide checker* -pep8 is a tool to check your Python code against some of the style -conventions in `PEP 8`_. +pycodestyle (formerly pep8) is a tool to check your Python code against some of +the style conventions in `PEP 8`_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ @@ -45,7 +45,7 @@ Maintained by Florent Xicluna and Ian Lee. License ======= -The ``pep8`` library is provided under the terms and conditions of the +The ``pycodestyle`` library is provided under the terms and conditions of the Expat license:: # Permission is hereby granted, free of charge, to any person diff --git a/docs/intro.rst b/docs/intro.rst index 91cfbfe..d3cd755 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -1,9 +1,9 @@ -.. currentmodule:: pep8 +.. currentmodule:: pycodestyle Introduction ============ -pep8 is a tool to check your Python code against some of the style +pycodestyle is a tool to check your Python code against some of the style conventions in `PEP 8`_. .. contents:: @@ -18,7 +18,7 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pep8.py file for this purpose. + the pycodestyle.py file for this purpose. * Comes with a comprehensive test suite. @@ -40,7 +40,7 @@ Always remember this statement from `PEP 8`_: Among other things, these features are currently not in the scope of -the ``pep8`` library: +the ``pycodestyle`` library: * **naming conventions**: this kind of feature is supported through plugins. Install `flake8 `_ and the @@ -55,16 +55,11 @@ the ``pep8`` library: Installation ------------ -You can install, upgrade, uninstall pep8.py with these commands:: +You can install, upgrade, uninstall pycodestyle.py with these commands:: - $ pip install pep8 - $ pip install --upgrade pep8 - $ pip uninstall pep8 - -There's also a package for Debian/Ubuntu, but it's not always the -latest version:: - - $ sudo apt-get install pep8 + $ pip install pycodestyle + $ pip install --upgrade pycodestyle + $ pip uninstall pycodestyle Example usage and output @@ -72,7 +67,7 @@ Example usage and output :: - $ pep8 --first optparse.py + $ pycodestyle --first optparse.py optparse.py:69:11: E401 multiple imports on one line optparse.py:77:1: E302 expected 2 blank lines, found 1 optparse.py:88:5: E301 expected 1 blank line, found 0 @@ -82,10 +77,10 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pep8.py show the source code for each error, and +You can also make pycodestyle.py show the source code for each error, and even the relevant text from PEP 8:: - $ pep8 --show-source --show-pep8 testsuite/E40.py + $ pycodestyle --show-source --show-pep8 testsuite/E40.py testsuite/E40.py:2:10: E401 multiple imports on one line import os, sys ^ @@ -97,7 +92,7 @@ even the relevant text from PEP 8:: Or you can display how often each error was found:: - $ pep8 --statistics -qq Python-2.5/Lib + $ pycodestyle --statistics -qq Python-2.5/Lib 232 E201 whitespace after '[' 599 E202 whitespace before ')' 631 E203 whitespace before ',' @@ -113,13 +108,13 @@ Or you can display how often each error was found:: You can also make pep8.py show the error text in different formats by using --format having options default/pylint/custom:: - $ pep8 testsuite/E40.py --format=default + $ pycodestyle testsuite/E40.py --format=default testsuite/E40.py:2:10: E401 multiple imports on one line - $ pep8 testsuite/E40.py --format=pylint + $ pycodestyle testsuite/E40.py --format=pylint testsuite/E40.py:2: [E401] multiple imports on one line - $ pep8 testsuite/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' + $ pycodestyle testsuite/E40.py --format='%(path)s|%(row)d|%(col)d| %(code)s %(text)s' testsuite/E40.py|2|10| E401 multiple imports on one line Variables in the ``custom`` format option @@ -140,8 +135,8 @@ Variables in the ``custom`` format option Quick help is available on the command line:: - $ pep8 -h - Usage: pep8 [options] input ... + $ pycodestyle -h + Usage: pycodestyle [options] input ... Options: --version show program's version number and exit @@ -421,7 +416,7 @@ special comment. This possibility should be reserved for special cases. Note: most errors can be listed with such one-liner:: - $ python pep8.py --first --select E,W testsuite/ --format '%(code)s: %(text)s' + $ python pycodestyle.py --first --select E,W testsuite/ --format '%(code)s: %(text)s' .. _related-tools: @@ -430,9 +425,9 @@ Related tools ------------- The `flake8 checker `_ is a wrapper around -``pep8`` and similar tools. It supports plugins. +``pycodestyle`` and similar tools. It supports plugins. -Other tools which use ``pep8`` are referenced in the Wiki: `list of related +Other tools which use ``pycodestyle`` are referenced in the Wiki: `list of related tools `_. .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ diff --git a/docs/make.bat b/docs/make.bat index efa0e94..1ecae62 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -99,9 +99,9 @@ if "%1" == "qthelp" ( echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pep8.qhcp + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pycodestyle.qhcp echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pep8.ghc + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pycodestyle.ghc goto end ) -- cgit v1.2.1 From eee59ff17de5fb341b3c05de51df6c8377ef88d0 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Sun, 29 May 2016 17:13:21 -0700 Subject: Updated two more references to old name --- CONTRIBUTING.rst | 4 ++-- docs/intro.rst | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 61c2822..9771176 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ -Contributing to ``pep8`` -======================== +Contributing to ``pycodestyle`` +=============================== Please see the `developer notes `_ diff --git a/docs/intro.rst b/docs/intro.rst index d3cd755..2281ef1 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -106,7 +106,8 @@ Or you can display how often each error was found:: 612 W601 .has_key() is deprecated, use 'in' 1188 W602 deprecated form of raising exception -You can also make pep8.py show the error text in different formats by using --format having options default/pylint/custom:: +You can also make pycodestyle.py show the error text in different formats by +using --format having options default/pylint/custom:: $ pycodestyle testsuite/E40.py --format=default testsuite/E40.py:2:10: E401 multiple imports on one line -- cgit v1.2.1 From c40150a7d06cbe1047502b836cc0d769e26364d6 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 30 May 2016 15:17:43 -0700 Subject: Updated deprecation warning to actually emit a warning --- pycodestyle.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pycodestyle.py b/pycodestyle.py index 5e90dcf..2ed42cf 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -55,6 +55,7 @@ import time import inspect import keyword import tokenize +import warnings from optparse import OptionParser from fnmatch import fnmatch try: @@ -2190,10 +2191,11 @@ def _main_pep8(): """ print( 'Deprecation Warning:\n' - 'pep8 has been renamed to pycodestyle and the use of the pep8 ' - 'executable will be removed in a future release. Please use ' - '`pycodestyle` instead.\n' + 'Use of the pep8 tool will be removed in a future release.\n' + 'Please install and use `pycodestyle` instead.\n' ) + warnings.warn('pep8 has been renamed to pycodestyle (GitHub issue #466)') + _main() -- cgit v1.2.1 From b86fa3d847bed1e7fe1e03cd3b7fba2f1b6b93d6 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Mon, 30 May 2016 16:42:08 -0700 Subject: Updated version string to PEP-440 format --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index 2ed42cf..e3dc05f 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.8.0-dev' +__version__ = '1.8.0.dev0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' -- cgit v1.2.1 From 3f00bed57ccc1b33f6872021e25f60d4ddba4760 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 09:47:49 -0700 Subject: Fixed typo in changelog --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4f8e28d..fe2665c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,7 +7,7 @@ Changelog Announcements: -* Repository renamed to `pycodestyle`; Issue #466 / $481. +* Repository renamed to `pycodestyle`; Issue #466 / #481. * Added joint Code of Conduct as member of PyCQA; #483 Changes: -- cgit v1.2.1 From a7362de1af81a2f1916f2d7e52f6af80e00c7aa1 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 10:14:36 -0700 Subject: Updated changelog with changes made since 1.7.0 --- CHANGES.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fe2665c..5c04216 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,11 +13,13 @@ Announcements: Changes: * Added tox test support for Python 3.5 and pypy3 +* Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 +* Added W503 to the list of codes ignored by default ignore list; #498 Bugs: -* Fixed bug with treating `~` operator as binary; #384 -* Identify binary operators as unary; #484 +* Fixed bug with treating `~` operator as binary; #383 / #384 +* Identify binary operators as unary; #484 / #485 1.7.0 (2016-01-12) ------------------ -- cgit v1.2.1 From 53da847c91b277491ef542ef53586e54cf7f4942 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 10:20:47 -0700 Subject: Update next version plan to 2.0.0 In order to better high-light the name change, and backwards incompatibility. This also allows dependent tools (like flake8) to pin to `pycodestyle < 2.0` as a dependency while transitioning to the new package. --- CHANGES.txt | 2 +- pycodestyle.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5c04216..4058206 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,7 +2,7 @@ Changelog ========= -1.8.0 (unreleased) +2.0.0 (unreleased) ------------------ Announcements: diff --git a/pycodestyle.py b/pycodestyle.py index e3dc05f..de40b07 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '1.8.0.dev0' +__version__ = '2.0.0.dev0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' -- cgit v1.2.1 From f8bd6939d42037287f17ac3b048bdbdf0f2dd1b5 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:10:52 -0700 Subject: Updated docs to remove another pep8 based reference --- docs/advanced.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 6adb2cb..fd3cf3e 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -68,7 +68,7 @@ through a custom wrapper for the PEP 8 library:: LINES_SLICE = slice(14, -20) - class PEP8(pycodestyle.StyleGuide): + class StyleGuide(pycodestyle.StyleGuide): """This subclass of pycodestyle.StyleGuide will skip the first and last lines of each file.""" @@ -77,11 +77,11 @@ through a custom wrapper for the PEP 8 library:: assert line_offset == 0 line_offset = LINES_SLICE.start or 0 lines = pycodestyle.readlines(filename)[LINES_SLICE] - return super(PEP8, self).input_file( + return super(StyleGuide, self).input_file( filename, lines=lines, expected=expected, line_offset=line_offset) if __name__ == '__main__': - style = PEP8(parse_argv=True, config_file=True) + style = StyleGuide(parse_argv=True, config_file=True) report = style.check_files() if report.total_errors: raise SystemExit(1) -- cgit v1.2.1 From 56a9882ec8bea2fe3dc10ab5477187e57468f970 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:16:32 -0700 Subject: Improved formatting of filename referencing in docs --- README.rst | 6 +++--- docs/developer.rst | 10 +++++----- docs/intro.rst | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index f82589d..466509b 100644 --- a/README.rst +++ b/README.rst @@ -20,14 +20,14 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pycodestyle.py file for this purpose. + the ``pycodestyle.py`` file for this purpose. * Comes with a comprehensive test suite. Installation ------------ -You can install, upgrade, uninstall pycodestyle.py with these commands:: +You can install, upgrade, uninstall ``pycodestyle.py`` with these commands:: $ pip install pycodestyle $ pip install --upgrade pycodestyle @@ -51,7 +51,7 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pycodestyle.py show the source code for each error, and +You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: $ pycodestyle --show-source --show-pep8 testsuite/E40.py diff --git a/docs/developer.rst b/docs/developer.rst index 0aefbed..3acf2a6 100644 --- a/docs/developer.rst +++ b/docs/developer.rst @@ -89,11 +89,11 @@ Several docstrings contain examples directly from the `PEP 8`_ document. Okay: spam(ham[1], {eggs: 2}) E201: spam( ham[1], {eggs: 2}) -These examples are verified automatically when pycodestyle.py is run with the -``--doctest`` option. You can add examples for your own check functions. -The format is simple: ``"Okay"`` or error/warning code followed by colon -and space, the rest of the line is example source code. If you put ``'r'`` -before the docstring, you can use ``\n`` for newline and ``\t`` for tab. +These examples are verified automatically when ``pycodestyle.py`` is run with +the ``--doctest`` option. You can add examples for your own check functions. +The format is simple: ``"Okay"`` or error/warning code followed by colon and +space, the rest of the line is example source code. If you put ``'r'`` before +the docstring, you can use ``\n`` for newline and ``\t`` for tab. Then be sure to pass the tests:: diff --git a/docs/intro.rst b/docs/intro.rst index 2281ef1..5a46a02 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -18,7 +18,7 @@ Features * Parseable output: Jump to error location in your editor. * Small: Just one Python file, requires only stdlib. You can use just - the pycodestyle.py file for this purpose. + the ``pycodestyle.py`` file for this purpose. * Comes with a comprehensive test suite. @@ -55,7 +55,7 @@ the ``pycodestyle`` library: Installation ------------ -You can install, upgrade, uninstall pycodestyle.py with these commands:: +You can install, upgrade, uninstall ``pycodestyle.py`` with these commands:: $ pip install pycodestyle $ pip install --upgrade pycodestyle @@ -77,7 +77,7 @@ Example usage and output optparse.py:472:29: E221 multiple spaces before operator optparse.py:544:21: W601 .has_key() is deprecated, use 'in' -You can also make pycodestyle.py show the source code for each error, and +You can also make ``pycodestyle.py`` show the source code for each error, and even the relevant text from PEP 8:: $ pycodestyle --show-source --show-pep8 testsuite/E40.py @@ -106,8 +106,8 @@ Or you can display how often each error was found:: 612 W601 .has_key() is deprecated, use 'in' 1188 W602 deprecated form of raising exception -You can also make pycodestyle.py show the error text in different formats by -using --format having options default/pylint/custom:: +You can also make ``pycodestyle.py`` show the error text in different formats by +using ``--format`` having options default/pylint/custom:: $ pycodestyle testsuite/E40.py --format=default testsuite/E40.py:2:10: E401 multiple imports on one line -- cgit v1.2.1 From f2ade60c2fd96dd7731d8ec62b3cbe6338823954 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:57:01 -0700 Subject: Removed project level `.pep8` config file Closes #364 --- CHANGES.txt | 1 + docs/intro.rst | 5 ++--- pycodestyle.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4058206..d4b63d0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,7 @@ Changes: * Added tox test support for Python 3.5 and pypy3 * Added check E275 for whitespace on `from ... import ...` lines; #489 / #491 * Added W503 to the list of codes ignored by default ignore list; #498 +* Removed use of project level `.pep8` configuration file; #364 Bugs: diff --git a/docs/intro.rst b/docs/intro.rst index 5a46a02..12f67d7 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -199,9 +199,8 @@ Example:: max-line-length = 160 At the project level, a ``setup.cfg`` file or a ``tox.ini`` file is read if -present (``.pep8`` file is also supported, but it is deprecated). If none of -these files have a ``[pep8]`` section, no project specific configuration is -loaded. +present. If none of these files have a ``[pep8]`` section, no project specific +configuration is loaded. Error codes diff --git a/pycodestyle.py b/pycodestyle.py index de40b07..dea67cd 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -79,7 +79,7 @@ try: except ImportError: USER_CONFIG = None -PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8') +PROJECT_CONFIG = ('setup.cfg', 'tox.ini') TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 REPORT_FORMAT = { -- cgit v1.2.1 From c7490b0f3ff1d18fae8c7f12b835885dc3bd19fc Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 11:59:34 -0700 Subject: Tagging 2.0.0 alpha 1 pre-release --- pycodestyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycodestyle.py b/pycodestyle.py index dea67cd..b764f89 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.0.0.dev0' +__version__ = '2.0.0a1' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' -- cgit v1.2.1 From fd4f1c286cdc4e0bc25822e10742adbc9062af15 Mon Sep 17 00:00:00 2001 From: Ian Lee Date: Tue, 31 May 2016 16:56:02 -0700 Subject: Tagged release 2.0.0 --- CHANGES.txt | 2 +- pycodestyle.py | 17 +---------------- setup.py | 1 - 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d4b63d0..e129dc8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,7 +2,7 @@ Changelog ========= -2.0.0 (unreleased) +2.0.0 (2016-05-31) ------------------ Announcements: diff --git a/pycodestyle.py b/pycodestyle.py index b764f89..7026396 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -64,7 +64,7 @@ try: except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.0.0a1' +__version__ = '2.0.0' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' @@ -2184,20 +2184,5 @@ def _main(): sys.exit(1) -def _main_pep8(): - """Entrypoint for pep8 commandline tool. - - Warn of deprecation and advise users to switch to pycodestyle. - """ - print( - 'Deprecation Warning:\n' - 'Use of the pep8 tool will be removed in a future release.\n' - 'Please install and use `pycodestyle` instead.\n' - ) - warnings.warn('pep8 has been renamed to pycodestyle (GitHub issue #466)') - - _main() - - if __name__ == '__main__': _main() diff --git a/setup.py b/setup.py index 93a1337..b77770e 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,6 @@ setup( entry_points={ 'console_scripts': [ 'pycodestyle = pycodestyle:_main', - 'pep8 = pycodestyle:_main_pep8', ], }, classifiers=[ -- cgit v1.2.1