summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorent Xicluna <florent.xicluna@gmail.com>2012-06-13 01:09:06 +0200
committerFlorent Xicluna <florent.xicluna@gmail.com>2012-06-13 01:09:06 +0200
commitcdb36abcc9bd1e221d3ccf4a88d7e04fde3f2ed6 (patch)
tree33714f6001dcf63ec7e47b5f94224f5792a3e634
parent362c76e182a5a60d70ccc8c33a03dc18bc4b27e5 (diff)
downloadpep8-cdb36abcc9bd1e221d3ccf4a88d7e04fde3f2ed6.tar.gz
Implement `--diff` to receive a unidiff on STDIN and report errors on modified code only. (Closes #39)
-rw-r--r--CHANGES.txt4
-rw-r--r--README.rst2
-rwxr-xr-xpep8.py82
3 files changed, 73 insertions, 15 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 6094040..155f41f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -33,6 +33,10 @@ Changelog
* New option ``--format`` to customize the error format. (Issue #23)
+* New option ``--diff`` to check only modified code. The unified
+ diff is read from STDIN. Example: ``hg diff | pep8 --diff``
+ (Issue #39)
+
* Correctly report the count of failures and set the exit code to 1
when the ``--doctest`` or the ``--testsuite`` fails.
diff --git a/README.rst b/README.rst
index 2d47cd5..b212dad 100644
--- a/README.rst
+++ b/README.rst
@@ -115,6 +115,8 @@ Quick help is available on the command line::
--doctest run doctest on myself
--config=path config file location (default: /home/user/.config/pep8)
--format=format set the error format [default|pylint|<custom>]
+ --diff report only lines changed according to the unified diff
+ received on STDIN
Feedback
--------
diff --git a/pep8.py b/pep8.py
index aeb7952..0a7e155 100755
--- a/pep8.py
+++ b/pep8.py
@@ -144,6 +144,7 @@ KEYWORD_REGEX = re.compile(r'(?:[^\s])(\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+) @@.*$')
WHITESPACE = frozenset(' \t')
BINARY_OPERATORS = frozenset([
@@ -1150,6 +1151,29 @@ def mute_string(text):
return text[:start] + 'x' * (end - start) + text[end:]
+def parse_udiff(diff, patterns=None):
+ rv = {}
+ lastrow = path = None
+ for line in diff.splitlines():
+ if lastrow:
+ if line[:1] == '+':
+ rv[path].add(row)
+ if row == lastrow:
+ lastrow = None
+ elif line[:1] != '-':
+ row += 1
+ elif line[:3] == '+++':
+ path = line[4:].split('\t', 1)[0]
+ if path[:2] == 'b/':
+ path = path[2:]
+ rv[path] = set()
+ elif line[:3] == '@@ ':
+ row, nrows = [int(g) for g in HUNK_REGEX.match(line).groups()]
+ lastrow = nrows and (row + nrows - 1) or None
+ return dict([(path, rows) for (path, rows) in rv.items()
+ if rows and filename_match(path, patterns)])
+
+
##############################################################################
# Framework to run all checks
##############################################################################
@@ -1169,6 +1193,18 @@ def find_checks(argument_name):
yield name, codes, function, args
+def filename_match(filename, patterns):
+ """
+ Check if patterns contains a pattern that matches filename.
+ If patterns is unspecified, this always returns True.
+ """
+ if not patterns:
+ return True
+ for pattern in patterns:
+ if fnmatch(filename, pattern):
+ return True
+
+
class Checker(object):
"""
Load a Python source file, tokenize it, check coding style.
@@ -1508,6 +1544,18 @@ class StandardReport(BasicReport):
return code
+class DiffReport(StandardReport):
+
+ 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.
@@ -1584,6 +1632,7 @@ class StyleGuide(object):
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:
@@ -1595,14 +1644,9 @@ class StyleGuide(object):
dirs.remove(subdir)
files.sort()
for filename in files:
- if self.options.filename:
- # contain a pattern that matches?
- for pattern in self.options.filename:
- if fnmatch(filename, pattern):
- break
- else:
- continue
- if not self.excluded(filename):
+ # contain a pattern that matches?
+ if ((filename_match(filename, filepatterns) and
+ not self.excluded(filename))):
runner(os.path.join(root, filename))
def excluded(self, filename):
@@ -1848,15 +1892,27 @@ def process_options(arglist=None):
parser.add_option('--doctest', action='store_true',
help="run doctest on myself")
parser.add_option('--config', metavar='path', default=DEFAULT_CONFIG,
- help='config file location (default: %default)')
+ help="config file location (default: %default)")
parser.add_option('--format', metavar='format', default='default',
- help='set the error format [default|pylint|<custom>]')
+ help="set the error format [default|pylint|<custom>]")
+ parser.add_option('--diff', action='store_true',
+ help="report only lines changed according to the "
+ "unified diff received on STDIN")
options, args = parser.parse_args(arglist)
options.reporter = None
if options.show_pep8:
options.repeat = False
- if options.testsuite:
+ options.exclude = options.exclude.split(',')
+ if options.filename:
+ options.filename = options.filename.split(',')
+
+ if options.diff:
+ stdin = sys.stdin.read()
+ options.reporter = DiffReport
+ options.selected_lines = parse_udiff(stdin, options.filename)
+ args = list(options.selected_lines.keys())
+ elif options.testsuite:
args.append(options.testsuite)
elif not options.doctest:
if not args and arglist is None:
@@ -1868,10 +1924,6 @@ def process_options(arglist=None):
if options.quiet == 1 and arglist is None:
options.reporter = FileReport
- options.exclude = options.exclude.split(',')
- if options.filename:
- options.filename = options.filename.split(',')
-
if options.select:
options.select = options.select.split(',')
if options.ignore: