blob: cd8dd2625cd4c015d090ad2835287aa653d65c0f [file] [log] [blame]
Sylvain Thénault6a770632009-08-06 10:24:42 +02001# -*- 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
Sylvain Thénaultac825be2013-06-19 13:58:46 +02002# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
Sylvain Thénault4b316ba2012-09-14 13:25:22 +02003# 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# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Sylvain Thénault6a770632009-08-06 10:24:42 +020017"""Emacs and Flymake compatible Pylint.
Sylvain Thénault981405e2008-12-03 09:57:14 +010018
Sylvain Thénault6a770632009-08-06 10:24:42 +020019This script is for integration with emacs and is compatible with flymake mode.
20
Emile Anclin85f4f8a2010-12-15 19:14:21 +010021epylint walks out of python packages before invoking pylint. This avoids
22reporting import errors that occur when a module within a package uses the
23absolute import path to get another module within this package.
Sylvain Thénault6a770632009-08-06 10:24:42 +020024
25For example:
26 - Suppose a package is structured as
27
28 a/__init__.py
29 a/b/x.py
30 a/c/y.py
31
Sylvain Thénaultba979be2013-09-03 12:22:43 +020032 - Then if y.py imports x as "from a.b import x" the following produces pylint
33 errors
Sylvain Thénault6a770632009-08-06 10:24:42 +020034
35 cd a/c; pylint y.py
36
Sylvain Thénault8746b952009-11-23 15:15:26 +010037 - The following obviously doesn't
Sylvain Thénault6a770632009-08-06 10:24:42 +020038
39 pylint a/c/y.py
40
Emile Anclin85f4f8a2010-12-15 19:14:21 +010041 - 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.
Sylvain Thénault6a770632009-08-06 10:24:42 +020043
Sylvain Thénault1c06c672009-12-18 11:34:04 +010044
Sylvain Thénaultba979be2013-09-03 12:22:43 +020045You may also use py_run to run pylint with desired options and get back (or not)
46its output.
Sylvain Thénault6a770632009-08-06 10:24:42 +020047"""
48
49import sys, os, re
Sylvain Thénault99196f22013-12-22 23:41:57 +010050import os.path as osp
Sylvain Thénault6a770632009-08-06 10:24:42 +020051from subprocess import Popen, PIPE
52
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050053def _get_env():
54 '''Extracts the environment PYTHONPATH and appends the current sys.path to
55 those.'''
56 env = dict(os.environ)
57 pythonpath = env.get('PYTHONPATH', '')
58 currentpaths = os.pathsep.join(sys.path)
59 if pythonpath:
60 pythonpath += os.pathsep + currentpaths
61 else:
62 pythonpath = currentpaths
63 env['PYTHONPATH'] = pythonpath
64 return env
Sylvain Thénault6a770632009-08-06 10:24:42 +020065
Sylvain Thénaultba979be2013-09-03 12:22:43 +020066def lint(filename, options=None):
Sylvain Thénault6a770632009-08-06 10:24:42 +020067 """Pylint the given file.
68
Sylvain Thénault177e7e52013-10-07 12:17:27 +020069 When run from emacs we will be in the directory of a file, and passed its
70 filename. If this file is part of a package and is trying to import other
71 modules from within its own package or another package rooted in a directory
72 below it, pylint will classify it as a failed import.
Sylvain Thénault6a770632009-08-06 10:24:42 +020073
Sylvain Thénault177e7e52013-10-07 12:17:27 +020074 To get around this, we traverse down the directory tree to find the root of
75 the package this module is in. We then invoke pylint from this directory.
Sylvain Thénault6a770632009-08-06 10:24:42 +020076
Sylvain Thénault177e7e52013-10-07 12:17:27 +020077 Finally, we must correct the filenames in the output generated by pylint so
78 Emacs doesn't become confused (it will expect just the original filename,
79 while pylint may extend it with extra directories if we've traversed down
80 the tree)
Sylvain Thénault6a770632009-08-06 10:24:42 +020081 """
82 # traverse downwards until we are out of a python package
Sylvain Thénault99196f22013-12-22 23:41:57 +010083 full_path = osp.abspath(filename)
84 parent_path = osp.dirname(full_path)
85 child_path = osp.basename(full_path)
Sylvain Thénault6a770632009-08-06 10:24:42 +020086
Sylvain Thénault99196f22013-12-22 23:41:57 +010087 while parent_path != "/" and osp.exists(osp.join(parent_path, '__init__.py')):
88 child_path = osp.join(osp.basename(parent_path), child_path)
89 parent_path = osp.dirname(parent_path)
Sylvain Thénault6a770632009-08-06 10:24:42 +020090
91 # Start pylint
Sylvain Thénaultc8e20f72012-01-09 21:25:10 +010092 # Ensure we use the python and pylint associated with the running epylint
Sylvain Thénault99196f22013-12-22 23:41:57 +010093 from pylint import lint as lint_mod
94 lint_path = lint_mod.__file__
Sylvain Thénaultba979be2013-09-03 12:22:43 +020095 options = options or ['--disable=C,R,I']
Sylvain Thénault99196f22013-12-22 23:41:57 +010096 cmd = [sys.executable, lint_path] + options + ['--msg-template',
97 '{path}:{line}: [{symbol}, {obj}] {msg}', '-r', 'n', child_path]
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050098 process = Popen(cmd, stdout=PIPE, cwd=parent_path, env=_get_env(),
Manuel Vázquez Acosta2da336b2014-01-06 15:31:19 -050099 universal_newlines=True)
Sylvain Thénault6a770632009-08-06 10:24:42 +0200100
101 # The parseable line format is '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s'
102 # NOTE: This would be cleaner if we added an Emacs reporter to pylint.reporters.text ..
103 regex = re.compile(r"\[(?P<type>[WE])(?P<remainder>.*?)\]")
104
Sylvain Thénault99196f22013-12-22 23:41:57 +0100105 def _replacement(match_object):
Sylvain Thénault6a770632009-08-06 10:24:42 +0200106 "Alter to include 'Error' or 'Warning'"
Sylvain Thénault99196f22013-12-22 23:41:57 +0100107 if match_object.group("type") == "W":
Sylvain Thénault6a770632009-08-06 10:24:42 +0200108 replacement = "Warning"
109 else:
110 replacement = "Error"
111 # replace as "Warning (W0511, funcName): Warning Text"
Sylvain Thénault99196f22013-12-22 23:41:57 +0100112 return "%s (%s%s):" % (replacement, match_object.group("type"),
113 match_object.group("remainder"))
Sylvain Thénault6a770632009-08-06 10:24:42 +0200114
Sylvain Thénaultc8e20f72012-01-09 21:25:10 +0100115 for line in process.stdout:
Sylvain Thénault6a770632009-08-06 10:24:42 +0200116 # remove pylintrc warning
117 if line.startswith("No config file found"):
118 continue
119 line = regex.sub(_replacement, line, 1)
120 # modify the file name thats output to reverse the path traversal we made
121 parts = line.split(":")
Sylvain Thénault99196f22013-12-22 23:41:57 +0100122 if parts and parts[0] == child_path:
Sylvain Thénault6a770632009-08-06 10:24:42 +0200123 line = ":".join([filename] + parts[1:])
124 print line,
125
Sylvain Thénaultc8e20f72012-01-09 21:25:10 +0100126 process.wait()
127 return process.returncode
Sylvain Thénault981405e2008-12-03 09:57:14 +0100128
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100129
130def py_run(command_options='', return_std=False, stdout=None, stderr=None,
Emile Anclin85f4f8a2010-12-15 19:14:21 +0100131 script='epylint'):
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200132 """Run pylint from python
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100133
134 ``command_options`` is a string containing ``pylint`` command line options;
135 ``return_std`` (boolean) indicates return of created standart output
136 and error (see below);
137 ``stdout`` and ``stderr`` are 'file-like' objects in which standart output
138 could be written.
139
140 Calling agent is responsible for stdout/err management (creation, close).
141 Default standart output and error are those from sys,
142 or standalone ones (``subprocess.PIPE``) are used
143 if they are not set and ``return_std``.
144
145 If ``return_std`` is set to ``True``, this function returns a 2-uple
146 containing standart output and error related to created process,
147 as follows: ``(stdout, stderr)``.
148
149 A trivial usage could be as follows:
150 >>> py_run( '--version')
151 No config file found, using default configuration
152 pylint 0.18.1,
153 ...
154
155 To silently run Pylint on a module, and get its standart output and error:
156 >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True)
157 """
158 # Create command line to call pylint
159 if os.name == 'nt':
160 script += '.bat'
161 command_line = script + ' ' + command_options
162 # Providing standart output and/or error if not set
163 if stdout is None:
164 if return_std:
165 stdout = PIPE
166 else:
167 stdout = sys.stdout
168 if stderr is None:
169 if return_std:
170 stderr = PIPE
171 else:
172 stderr = sys.stderr
173 # Call pylint in a subprocess
Sylvain Thénault177e7e52013-10-07 12:17:27 +0200174 p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr,
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -0500175 env=_get_env(), universal_newlines=True)
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100176 p.wait()
177 # Return standart output and error
178 if return_std:
179 return (p.stdout, p.stderr)
180
181
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200182def Run():
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200183 if len(sys.argv) == 1:
184 print "Usage: %s <filename> [options]" % sys.argv[0]
185 sys.exit(1)
Sylvain Thénault99196f22013-12-22 23:41:57 +0100186 elif not osp.exists(sys.argv[1]):
Sylvain Thénault177e7e52013-10-07 12:17:27 +0200187 print "%s does not exist" % sys.argv[1]
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200188 sys.exit(1)
189 else:
Sylvain Thénault177e7e52013-10-07 12:17:27 +0200190 sys.exit(lint(sys.argv[1], sys.argv[1:]))
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200191
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200192
Sylvain Thénault6a770632009-08-06 10:24:42 +0200193if __name__ == '__main__':
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200194 Run()