blob: de5cac6b785cd8edbb6ba7a1ff9c722218249542 [file] [log] [blame]
Mihai Balinte0f2c502015-02-28 17:13:21 +02001# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4
2# -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4
Sylvain Thénaultac825be2013-06-19 13:58:46 +02003# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
Sylvain Thénault4b316ba2012-09-14 13:25:22 +02004# http://www.logilab.fr/ -- mailto:contact@logilab.fr
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of the GNU General Public License as published by the Free Software
8# Foundation; either version 2 of the License, or (at your option) any later
9# version.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
14#
15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc.,
Sylvain Thénault04c0ad72014-04-15 08:55:50 +020017# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Sylvain Thénault6a770632009-08-06 10:24:42 +020018"""Emacs and Flymake compatible Pylint.
Sylvain Thénault981405e2008-12-03 09:57:14 +010019
Sylvain Thénault6a770632009-08-06 10:24:42 +020020This script is for integration with emacs and is compatible with flymake mode.
21
Emile Anclin85f4f8a2010-12-15 19:14:21 +010022epylint walks out of python packages before invoking pylint. This avoids
23reporting import errors that occur when a module within a package uses the
24absolute import path to get another module within this package.
Sylvain Thénault6a770632009-08-06 10:24:42 +020025
26For example:
27 - Suppose a package is structured as
28
29 a/__init__.py
30 a/b/x.py
31 a/c/y.py
32
Sylvain Thénaultba979be2013-09-03 12:22:43 +020033 - Then if y.py imports x as "from a.b import x" the following produces pylint
34 errors
Sylvain Thénault6a770632009-08-06 10:24:42 +020035
36 cd a/c; pylint y.py
37
Sylvain Thénault8746b952009-11-23 15:15:26 +010038 - The following obviously doesn't
Sylvain Thénault6a770632009-08-06 10:24:42 +020039
40 pylint a/c/y.py
41
Emile Anclin85f4f8a2010-12-15 19:14:21 +010042 - As this script will be invoked by emacs within the directory of the file
43 we are checking we need to go out of it to avoid these false positives.
Sylvain Thénault6a770632009-08-06 10:24:42 +020044
Sylvain Thénault1c06c672009-12-18 11:34:04 +010045
Sylvain Thénaultba979be2013-09-03 12:22:43 +020046You may also use py_run to run pylint with desired options and get back (or not)
47its output.
Sylvain Thénault6a770632009-08-06 10:24:42 +020048"""
Brett Cannon29bb89f2014-08-29 11:16:29 -040049from __future__ import print_function
Sylvain Thénault6a770632009-08-06 10:24:42 +020050
Claudiu Popa28bd68d2015-10-02 18:09:09 +030051import os
Sylvain Thénault99196f22013-12-22 23:41:57 +010052import os.path as osp
Claudiu Popae87abe92015-11-25 15:12:59 +020053import sys
Sylvain Thénault6a770632009-08-06 10:24:42 +020054from subprocess import Popen, PIPE
55
Claudiu Popa1cdb8bc2016-03-28 18:46:48 +010056import six
57
58
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050059def _get_env():
60 '''Extracts the environment PYTHONPATH and appends the current sys.path to
61 those.'''
62 env = dict(os.environ)
Manuel Vázquez Acostaffb12302014-04-14 16:07:58 -040063 env['PYTHONPATH'] = os.pathsep.join(sys.path)
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050064 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']
Manuel Vázquez Acostadb303eb2014-04-14 16:25:34 -040096 cmd = [sys.executable, lint_path] + options + [
97 '--msg-template', '{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}',
98 '-r', 'n', child_path]
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050099 process = Popen(cmd, stdout=PIPE, cwd=parent_path, env=_get_env(),
Manuel Vázquez Acosta2da336b2014-01-06 15:31:19 -0500100 universal_newlines=True)
Sylvain Thénault6a770632009-08-06 10:24:42 +0200101
Sylvain Thénaultc8e20f72012-01-09 21:25:10 +0100102 for line in process.stdout:
Sylvain Thénault6a770632009-08-06 10:24:42 +0200103 # remove pylintrc warning
104 if line.startswith("No config file found"):
105 continue
Manuel Vázquez Acostaf8fdbc32014-04-14 16:57:25 -0400106
Sylvain Thénault6a770632009-08-06 10:24:42 +0200107 # modify the file name thats output to reverse the path traversal we made
108 parts = line.split(":")
Sylvain Thénault99196f22013-12-22 23:41:57 +0100109 if parts and parts[0] == child_path:
Sylvain Thénault6a770632009-08-06 10:24:42 +0200110 line = ":".join([filename] + parts[1:])
Brett Cannon29bb89f2014-08-29 11:16:29 -0400111 print(line, end=' ')
Sylvain Thénault6a770632009-08-06 10:24:42 +0200112
Sylvain Thénaultc8e20f72012-01-09 21:25:10 +0100113 process.wait()
114 return process.returncode
Sylvain Thénault981405e2008-12-03 09:57:14 +0100115
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100116
117def py_run(command_options='', return_std=False, stdout=None, stderr=None,
Emile Anclin85f4f8a2010-12-15 19:14:21 +0100118 script='epylint'):
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200119 """Run pylint from python
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100120
121 ``command_options`` is a string containing ``pylint`` command line options;
Jakob Normark962ade12014-11-06 18:56:20 +0100122 ``return_std`` (boolean) indicates return of created standard output
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100123 and error (see below);
Jakob Normark962ade12014-11-06 18:56:20 +0100124 ``stdout`` and ``stderr`` are 'file-like' objects in which standard output
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100125 could be written.
126
127 Calling agent is responsible for stdout/err management (creation, close).
Jakob Normark962ade12014-11-06 18:56:20 +0100128 Default standard output and error are those from sys,
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100129 or standalone ones (``subprocess.PIPE``) are used
130 if they are not set and ``return_std``.
131
132 If ``return_std`` is set to ``True``, this function returns a 2-uple
Jakob Normark962ade12014-11-06 18:56:20 +0100133 containing standard output and error related to created process,
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100134 as follows: ``(stdout, stderr)``.
135
136 A trivial usage could be as follows:
137 >>> py_run( '--version')
138 No config file found, using default configuration
139 pylint 0.18.1,
140 ...
141
Jakob Normark962ade12014-11-06 18:56:20 +0100142 To silently run Pylint on a module, and get its standard output and error:
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100143 >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True)
144 """
145 # Create command line to call pylint
146 if os.name == 'nt':
147 script += '.bat'
148 command_line = script + ' ' + command_options
Jakob Normark962ade12014-11-06 18:56:20 +0100149 # Providing standard output and/or error if not set
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100150 if stdout is None:
151 if return_std:
152 stdout = PIPE
153 else:
154 stdout = sys.stdout
155 if stderr is None:
156 if return_std:
157 stderr = PIPE
158 else:
159 stderr = sys.stderr
160 # Call pylint in a subprocess
Claudiu Popaf67d4322015-05-16 18:25:57 +0300161 process = Popen(command_line, shell=True, stdout=stdout, stderr=stderr,
162 env=_get_env(), universal_newlines=True)
Claudiu Popa1cdb8bc2016-03-28 18:46:48 +0100163 proc_stdout, proc_stderr = process.communicate()
Jakob Normark962ade12014-11-06 18:56:20 +0100164 # Return standard output and error
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100165 if return_std:
Claudiu Popa1cdb8bc2016-03-28 18:46:48 +0100166 return six.moves.StringIO(proc_stdout), six.moves.StringIO(proc_stderr)
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100167
168
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200169def Run():
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200170 if len(sys.argv) == 1:
Brett Cannon29bb89f2014-08-29 11:16:29 -0400171 print("Usage: %s <filename> [options]" % sys.argv[0])
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200172 sys.exit(1)
Sylvain Thénault99196f22013-12-22 23:41:57 +0100173 elif not osp.exists(sys.argv[1]):
Brett Cannon29bb89f2014-08-29 11:16:29 -0400174 print("%s does not exist" % sys.argv[1])
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200175 sys.exit(1)
176 else:
Manuel Vázquez Acostaf8fdbc32014-04-14 16:57:25 -0400177 sys.exit(lint(sys.argv[1], sys.argv[2:]))
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200178
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200179
Sylvain Thénault6a770632009-08-06 10:24:42 +0200180if __name__ == '__main__':
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200181 Run()