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