← Back to team overview

divmod-dev team mailing list archive

[Merge] lp:~jml/divmod.org/pyflakes-reporter into lp:divmod.org

 

Jonathan Lange has proposed merging lp:~jml/divmod.org/pyflakes-reporter into lp:divmod.org.

Requested reviews:
  Divmod-dev (divmod-dev)

For more details, see:
https://code.launchpad.net/~jml/divmod.org/pyflakes-reporter/+merge/112336

A sketch for a reporter in pyflakes.  This moves pyflakes toward having an
interface that can be called from Python to gather errors without requiring
stderr and stdout to be captured.  It also separates the format of output
from the means of checking.

I was inspired to do this while trying to add a test to my own projects to 
guarantee that it is pyflakes-clean.  I didn't want to do stdout/err trapping,
and thought that something like this would be useful.

I'm not really proposing that this branch be merged as-is.  Clearly, it lacks 
unit tests for the change.

However, I do want to know whether a change along these lines would be considered
for inclusion.  If so, I'll spend the energy to toughen it up.

Thanks,
jml
-- 
https://code.launchpad.net/~jml/divmod.org/pyflakes-reporter/+merge/112336
Your team Divmod-dev is requested to review the proposed merge of lp:~jml/divmod.org/pyflakes-reporter into lp:divmod.org.
=== modified file 'Pyflakes/pyflakes/scripts/pyflakes.py'
--- Pyflakes/pyflakes/scripts/pyflakes.py	2010-04-13 14:53:04 +0000
+++ Pyflakes/pyflakes/scripts/pyflakes.py	2012-06-27 12:13:24 +0000
@@ -9,7 +9,45 @@
 
 checker = __import__('pyflakes.checker').checker
 
-def check(codeString, filename):
+
+class Reporter(object):
+
+    def __init__(self, stdout=None, stderr=None):
+        if stdout is None:
+            stdout = sys.stdout
+        self._stdout = stdout
+        if stderr is None:
+            stderr = sys.stderr
+        self._stderr = stderr
+
+
+    def _printError(self, msg):
+        self._stderr.write(msg)
+        self._stderr.write('\n')
+
+
+    def ioError(self, filename, msg):
+        self._printError("%s: %s" % (filename, msg.args[1]))
+
+
+    def problemDecodingSource(self, filename):
+        self._printError("%s: problem decoding source\n" % (filename,))
+
+
+    def syntaxError(self, filename, msg, lineno, offset, line):
+        self._printError('%s:%d: %s' % (filename, lineno, msg))
+        self._printError(line)
+        if offset is not None:
+            self._printError(" " * offset, "^")
+
+
+    def flake(self, warning):
+        self._stdout.write(warning)
+        self._stdout.write('\n')
+
+
+
+def check(codeString, filename, reporter=None):
     """
     Check the Python source given by C{codeString} for flakes.
 
@@ -23,6 +61,8 @@
     @return: The number of warnings emitted.
     @rtype: C{int}
     """
+    if reporter is None:
+        reporter = Reporter()
     # First, compile into an AST and handle syntax errors.
     try:
         tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
@@ -36,54 +76,72 @@
             # Avoid using msg, since for the only known case, it contains a
             # bogus message that claims the encoding the file declared was
             # unknown.
-            print >> sys.stderr, "%s: problem decoding source" % (filename, )
+            reporter.problem_decoding_source(filename)
         else:
             line = text.splitlines()[-1]
-
             if offset is not None:
                 offset = offset - (len(text) - len(line))
-
-            print >> sys.stderr, '%s:%d: %s' % (filename, lineno, msg)
-            print >> sys.stderr, line
-
-            if offset is not None:
-                print >> sys.stderr, " " * offset, "^"
-
+            reporter.syntax_error(filename, msg, lineno, offset, line)
         return 1
     else:
         # Okay, it's syntactically valid.  Now check it.
         w = checker.Checker(tree, filename)
         w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
         for warning in w.messages:
-            print warning
+            reporter.flake(warning)
         return len(w.messages)
 
 
-def checkPath(filename):
+
+def checkPath(filename, reporter=None):
     """
     Check the given path, printing out any warnings detected.
 
     @return: the number of warnings printed
     """
     try:
-        return check(file(filename, 'U').read() + '\n', filename)
+        return check(file(filename, 'U').read() + '\n', filename, reporter)
     except IOError, msg:
-        print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
+        reporter.ioError(filename, msg)
         return 1
 
 
+
+def iterSourceFiles(paths):
+    """
+    Iterate over source files listed in C{paths}.
+    """
+    for path in paths:
+        if os.path.isdir(path):
+            for dirpath, dirnames, filenames in os.walk(path):
+                for filename in filenames:
+                    if filename.endswith('.py'):
+                        yield os.path.join(dirpath, filename)
+        else:
+            yield path
+
+
+
+def checkRecursive(paths, reporter=None):
+    """
+    Check the given files and look recursively under any directories, looking
+    for Python files and checking them, printing out any warnings detected.
+
+    @param paths: A list of file and directory names.
+    @return: the number of warnings printed
+    """
+    warnings = 0
+    for sourcePath in iterSourceFiles(paths):
+        warnings += checkPath(sourcePath, reporter)
+    return warnings
+
+
+
 def main():
     warnings = 0
     args = sys.argv[1:]
     if args:
-        for arg in args:
-            if os.path.isdir(arg):
-                for dirpath, dirnames, filenames in os.walk(arg):
-                    for filename in filenames:
-                        if filename.endswith('.py'):
-                            warnings += checkPath(os.path.join(dirpath, filename))
-            else:
-                warnings += checkPath(arg)
+        warnings += checkRecursive(args)
     else:
         warnings += check(sys.stdin.read(), '<stdin>')
 


Follow ups