OLD | NEW |
| 1 #!/usr/bin/env python |
1 # -*- 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 | 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 |
2 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). | |
3 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
4 # | |
5 # This program is free software; you can redistribute it and/or modify it under | |
6 # the terms of the GNU General Public License as published by the Free Software | |
7 # Foundation; either version 2 of the License, or (at your option) any later | |
8 # version. | |
9 # | |
10 # This program is distributed in the hope that it will be useful, but WITHOUT | |
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
12 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details | |
13 # | |
14 # You should have received a copy of the GNU General Public License along with | |
15 # this program; if not, write to the Free Software Foundation, Inc., | |
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 """Emacs and Flymake compatible Pylint. | 3 """Emacs and Flymake compatible Pylint. |
18 | 4 |
19 This script is for integration with emacs and is compatible with flymake mode. | 5 This script is for integration with emacs and is compatible with flymake mode. |
20 | 6 |
21 epylint walks out of python packages before invoking pylint. This avoids | 7 epylint walks out of python packages before invoking pylint. This avoids |
22 reporting import errors that occur when a module within a package uses the | 8 reporting import errors that occur when a module within a package uses the |
23 absolute import path to get another module within this package. | 9 absolute import path to get another module within this package. |
24 | 10 |
25 For example: | 11 For example: |
26 - Suppose a package is structured as | 12 - Suppose a package is structured as |
27 | 13 |
28 a/__init__.py | 14 a/__init__.py |
29 a/b/x.py | 15 a/b/x.py |
30 a/c/y.py | 16 a/c/y.py |
31 | 17 |
32 - Then if y.py imports x as "from a.b import x" the following produces pylint | 18 - Then if y.py imports x as "from a.b import x" the following produces pylint
errors |
33 errors | |
34 | 19 |
35 cd a/c; pylint y.py | 20 cd a/c; pylint y.py |
36 | 21 |
37 - The following obviously doesn't | 22 - The following obviously doesn't |
38 | 23 |
39 pylint a/c/y.py | 24 pylint a/c/y.py |
40 | 25 |
41 - As this script will be invoked by emacs within the directory of the file | 26 - As this script will be invoked by emacs within the directory of the file |
42 we are checking we need to go out of it to avoid these false positives. | 27 we are checking we need to go out of it to avoid these false positives. |
43 | 28 |
44 | 29 |
45 You may also use py_run to run pylint with desired options and get back (or not) | 30 You may also use py_run to run pylint with desired options and get back (or not)
its output. |
46 its output. | |
47 """ | 31 """ |
48 | 32 |
49 import sys, os | 33 import sys, os, re |
50 import os.path as osp | |
51 from subprocess import Popen, PIPE | 34 from subprocess import Popen, PIPE |
52 | 35 |
53 def _get_env(): | |
54 '''Extracts the environment PYTHONPATH and appends the current sys.path to | |
55 those.''' | |
56 env = dict(os.environ) | |
57 env['PYTHONPATH'] = os.pathsep.join(sys.path) | |
58 return env | |
59 | 36 |
60 def lint(filename, options=None): | 37 def lint(filename): |
61 """Pylint the given file. | 38 """Pylint the given file. |
62 | 39 |
63 When run from emacs we will be in the directory of a file, and passed its | 40 When run from emacs we will be in the directory of a file, and passed its fi
lename. |
64 filename. If this file is part of a package and is trying to import other | 41 If this file is part of a package and is trying to import other modules from
within |
65 modules from within its own package or another package rooted in a directory | 42 its own package or another package rooted in a directory below it, pylint wi
ll classify |
66 below it, pylint will classify it as a failed import. | 43 it as a failed import. |
67 | 44 |
68 To get around this, we traverse down the directory tree to find the root of | 45 To get around this, we traverse down the directory tree to find the root of
the package this |
69 the package this module is in. We then invoke pylint from this directory. | 46 module is in. We then invoke pylint from this directory. |
70 | 47 |
71 Finally, we must correct the filenames in the output generated by pylint so | 48 Finally, we must correct the filenames in the output generated by pylint so
Emacs doesn't |
72 Emacs doesn't become confused (it will expect just the original filename, | 49 become confused (it will expect just the original filename, while pylint may
extend it with |
73 while pylint may extend it with extra directories if we've traversed down | 50 extra directories if we've traversed down the tree) |
74 the tree) | |
75 """ | 51 """ |
76 # traverse downwards until we are out of a python package | 52 # traverse downwards until we are out of a python package |
77 full_path = osp.abspath(filename) | 53 fullPath = os.path.abspath(filename) |
78 parent_path = osp.dirname(full_path) | 54 parentPath, childPath = os.path.dirname(fullPath), os.path.basename(fullPath
) |
79 child_path = osp.basename(full_path) | |
80 | 55 |
81 while parent_path != "/" and osp.exists(osp.join(parent_path, '__init__.py')
): | 56 while parentPath != "/" and os.path.exists(os.path.join(parentPath, '__init_
_.py')): |
82 child_path = osp.join(osp.basename(parent_path), child_path) | 57 childPath = os.path.join(os.path.basename(parentPath), childPath) |
83 parent_path = osp.dirname(parent_path) | 58 parentPath = os.path.dirname(parentPath) |
84 | 59 |
85 # Start pylint | 60 # Start pylint |
86 # Ensure we use the python and pylint associated with the running epylint | 61 process = Popen('pylint -f parseable -r n --disable=C,R,I "%s"' % |
87 from pylint import lint as lint_mod | 62 childPath, shell=True, stdout=PIPE, stderr=PIPE, |
88 lint_path = lint_mod.__file__ | 63 cwd=parentPath) |
89 options = options or ['--disable=C,R,I'] | 64 p = process.stdout |
90 cmd = [sys.executable, lint_path] + options + [ | |
91 '--msg-template', '{path}:{line}: {category} ({msg_id}, {symbol}, {obj})
{msg}', | |
92 '-r', 'n', child_path] | |
93 process = Popen(cmd, stdout=PIPE, cwd=parent_path, env=_get_env(), | |
94 universal_newlines=True) | |
95 | 65 |
96 for line in process.stdout: | 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: |
97 # remove pylintrc warning | 80 # remove pylintrc warning |
98 if line.startswith("No config file found"): | 81 if line.startswith("No config file found"): |
99 continue | 82 continue |
100 | 83 line = regex.sub(_replacement, line, 1) |
101 # modify the file name thats output to reverse the path traversal we mad
e | 84 # modify the file name thats output to reverse the path traversal we mad
e |
102 parts = line.split(":") | 85 parts = line.split(":") |
103 if parts and parts[0] == child_path: | 86 if parts and parts[0] == childPath: |
104 line = ":".join([filename] + parts[1:]) | 87 line = ":".join([filename] + parts[1:]) |
105 print line, | 88 print line, |
106 | 89 |
107 process.wait() | 90 p.close() |
108 return process.returncode | 91 |
| 92 def Run(): |
| 93 lint(sys.argv[1]) |
109 | 94 |
110 | 95 |
111 def py_run(command_options='', return_std=False, stdout=None, stderr=None, | 96 def py_run(command_options='', return_std=False, stdout=None, stderr=None, |
112 script='epylint'): | 97 script='epylint'): |
113 """Run pylint from python | 98 """Run pylint from python (needs Python >= 2.4). |
114 | 99 |
115 ``command_options`` is a string containing ``pylint`` command line options; | 100 ``command_options`` is a string containing ``pylint`` command line options; |
116 ``return_std`` (boolean) indicates return of created standart output | 101 ``return_std`` (boolean) indicates return of created standart output |
117 and error (see below); | 102 and error (see below); |
118 ``stdout`` and ``stderr`` are 'file-like' objects in which standart output | 103 ``stdout`` and ``stderr`` are 'file-like' objects in which standart output |
119 could be written. | 104 could be written. |
120 | 105 |
121 Calling agent is responsible for stdout/err management (creation, close). | 106 Calling agent is responsible for stdout/err management (creation, close). |
122 Default standart output and error are those from sys, | 107 Default standart output and error are those from sys, |
123 or standalone ones (``subprocess.PIPE``) are used | 108 or standalone ones (``subprocess.PIPE``) are used |
(...skipping 21 matching lines...) Expand all Loading... |
145 if return_std: | 130 if return_std: |
146 stdout = PIPE | 131 stdout = PIPE |
147 else: | 132 else: |
148 stdout = sys.stdout | 133 stdout = sys.stdout |
149 if stderr is None: | 134 if stderr is None: |
150 if return_std: | 135 if return_std: |
151 stderr = PIPE | 136 stderr = PIPE |
152 else: | 137 else: |
153 stderr = sys.stderr | 138 stderr = sys.stderr |
154 # Call pylint in a subprocess | 139 # Call pylint in a subprocess |
155 p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, | 140 p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr) |
156 env=_get_env(), universal_newlines=True) | |
157 p.wait() | 141 p.wait() |
158 # Return standart output and error | 142 # Return standart output and error |
159 if return_std: | 143 if return_std: |
160 return (p.stdout, p.stderr) | 144 return (p.stdout, p.stderr) |
161 | 145 |
162 | 146 |
163 def Run(): | 147 if __name__ == '__main__': |
164 if len(sys.argv) == 1: | 148 lint(sys.argv[1]) |
165 print "Usage: %s <filename> [options]" % sys.argv[0] | |
166 sys.exit(1) | |
167 elif not osp.exists(sys.argv[1]): | |
168 print "%s does not exist" % sys.argv[1] | |
169 sys.exit(1) | |
170 else: | |
171 sys.exit(lint(sys.argv[1], sys.argv[2:])) | |
172 | 149 |
173 | |
174 if __name__ == '__main__': | |
175 Run() | |
OLD | NEW |