OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-
offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 |
| 3 """Emacs and Flymake compatible Pylint. |
| 4 |
| 5 This script is for integration with emacs and is compatible with flymake mode. |
| 6 |
| 7 epylint walks out of python packages before invoking pylint. This avoids |
| 8 reporting import errors that occur when a module within a package uses the |
| 9 absolute import path to get another module within this package. |
| 10 |
| 11 For example: |
| 12 - Suppose a package is structured as |
| 13 |
| 14 a/__init__.py |
| 15 a/b/x.py |
| 16 a/c/y.py |
| 17 |
| 18 - Then if y.py imports x as "from a.b import x" the following produces pylint
errors |
| 19 |
| 20 cd a/c; pylint y.py |
| 21 |
| 22 - The following obviously doesn't |
| 23 |
| 24 pylint a/c/y.py |
| 25 |
| 26 - As this script will be invoked by emacs within the directory of the file |
| 27 we are checking we need to go out of it to avoid these false positives. |
| 28 |
| 29 |
| 30 You may also use py_run to run pylint with desired options and get back (or not)
its output. |
| 31 """ |
| 32 |
| 33 import sys, os, re |
| 34 from subprocess import Popen, PIPE |
| 35 |
| 36 |
| 37 def lint(filename): |
| 38 """Pylint the given file. |
| 39 |
| 40 When run from emacs we will be in the directory of a file, and passed its fi
lename. |
| 41 If this file is part of a package and is trying to import other modules from
within |
| 42 its own package or another package rooted in a directory below it, pylint wi
ll classify |
| 43 it as a failed import. |
| 44 |
| 45 To get around this, we traverse down the directory tree to find the root of
the package this |
| 46 module is in. We then invoke pylint from this directory. |
| 47 |
| 48 Finally, we must correct the filenames in the output generated by pylint so
Emacs doesn't |
| 49 become confused (it will expect just the original filename, while pylint may
extend it with |
| 50 extra directories if we've traversed down the tree) |
| 51 """ |
| 52 # traverse downwards until we are out of a python package |
| 53 fullPath = os.path.abspath(filename) |
| 54 parentPath, childPath = os.path.dirname(fullPath), os.path.basename(fullPath
) |
| 55 |
| 56 while parentPath != "/" and os.path.exists(os.path.join(parentPath, '__init_
_.py')): |
| 57 childPath = os.path.join(os.path.basename(parentPath), childPath) |
| 58 parentPath = os.path.dirname(parentPath) |
| 59 |
| 60 # Start pylint |
| 61 process = Popen('pylint -f parseable -r n --disable=C,R,I "%s"' % |
| 62 childPath, shell=True, stdout=PIPE, stderr=PIPE, |
| 63 cwd=parentPath) |
| 64 p = process.stdout |
| 65 |
| 66 # The parseable line format is '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)
s' |
| 67 # NOTE: This would be cleaner if we added an Emacs reporter to pylint.report
ers.text .. |
| 68 regex = re.compile(r"\[(?P<type>[WE])(?P<remainder>.*?)\]") |
| 69 |
| 70 def _replacement(mObj): |
| 71 "Alter to include 'Error' or 'Warning'" |
| 72 if mObj.group("type") == "W": |
| 73 replacement = "Warning" |
| 74 else: |
| 75 replacement = "Error" |
| 76 # replace as "Warning (W0511, funcName): Warning Text" |
| 77 return "%s (%s%s):" % (replacement, mObj.group("type"), mObj.group("rema
inder")) |
| 78 |
| 79 for line in p: |
| 80 # remove pylintrc warning |
| 81 if line.startswith("No config file found"): |
| 82 continue |
| 83 line = regex.sub(_replacement, line, 1) |
| 84 # modify the file name thats output to reverse the path traversal we mad
e |
| 85 parts = line.split(":") |
| 86 if parts and parts[0] == childPath: |
| 87 line = ":".join([filename] + parts[1:]) |
| 88 print line, |
| 89 |
| 90 p.close() |
| 91 |
| 92 def Run(): |
| 93 lint(sys.argv[1]) |
| 94 |
| 95 |
| 96 def py_run(command_options='', return_std=False, stdout=None, stderr=None, |
| 97 script='epylint'): |
| 98 """Run pylint from python (needs Python >= 2.4). |
| 99 |
| 100 ``command_options`` is a string containing ``pylint`` command line options; |
| 101 ``return_std`` (boolean) indicates return of created standart output |
| 102 and error (see below); |
| 103 ``stdout`` and ``stderr`` are 'file-like' objects in which standart output |
| 104 could be written. |
| 105 |
| 106 Calling agent is responsible for stdout/err management (creation, close). |
| 107 Default standart output and error are those from sys, |
| 108 or standalone ones (``subprocess.PIPE``) are used |
| 109 if they are not set and ``return_std``. |
| 110 |
| 111 If ``return_std`` is set to ``True``, this function returns a 2-uple |
| 112 containing standart output and error related to created process, |
| 113 as follows: ``(stdout, stderr)``. |
| 114 |
| 115 A trivial usage could be as follows: |
| 116 >>> py_run( '--version') |
| 117 No config file found, using default configuration |
| 118 pylint 0.18.1, |
| 119 ... |
| 120 |
| 121 To silently run Pylint on a module, and get its standart output and error: |
| 122 >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True) |
| 123 """ |
| 124 # Create command line to call pylint |
| 125 if os.name == 'nt': |
| 126 script += '.bat' |
| 127 command_line = script + ' ' + command_options |
| 128 # Providing standart output and/or error if not set |
| 129 if stdout is None: |
| 130 if return_std: |
| 131 stdout = PIPE |
| 132 else: |
| 133 stdout = sys.stdout |
| 134 if stderr is None: |
| 135 if return_std: |
| 136 stderr = PIPE |
| 137 else: |
| 138 stderr = sys.stderr |
| 139 # Call pylint in a subprocess |
| 140 p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr) |
| 141 p.wait() |
| 142 # Return standart output and error |
| 143 if return_std: |
| 144 return (p.stdout, p.stderr) |
| 145 |
| 146 |
| 147 if __name__ == '__main__': |
| 148 lint(sys.argv[1]) |
| 149 |
OLD | NEW |