Mihai Balint | e0f2c50 | 2015-02-28 17:13:21 +0200 | [diff] [blame] | 1 | # -*- 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énault | ac825be | 2013-06-19 13:58:46 +0200 | [diff] [blame] | 3 | # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
Sylvain Thénault | 4b316ba | 2012-09-14 13:25:22 +0200 | [diff] [blame] | 4 | # 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énault | 04c0ad7 | 2014-04-15 08:55:50 +0200 | [diff] [blame] | 17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 18 | """Emacs and Flymake compatible Pylint. |
Sylvain Thénault | 981405e | 2008-12-03 09:57:14 +0100 | [diff] [blame] | 19 | |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 20 | This script is for integration with emacs and is compatible with flymake mode. |
| 21 | |
Emile Anclin | 85f4f8a | 2010-12-15 19:14:21 +0100 | [diff] [blame] | 22 | epylint walks out of python packages before invoking pylint. This avoids |
| 23 | reporting import errors that occur when a module within a package uses the |
| 24 | absolute import path to get another module within this package. |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 25 | |
| 26 | For 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énault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 33 | - Then if y.py imports x as "from a.b import x" the following produces pylint |
| 34 | errors |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 35 | |
| 36 | cd a/c; pylint y.py |
| 37 | |
Sylvain Thénault | 8746b95 | 2009-11-23 15:15:26 +0100 | [diff] [blame] | 38 | - The following obviously doesn't |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 39 | |
| 40 | pylint a/c/y.py |
| 41 | |
Emile Anclin | 85f4f8a | 2010-12-15 19:14:21 +0100 | [diff] [blame] | 42 | - 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énault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 44 | |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 45 | |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 46 | You may also use py_run to run pylint with desired options and get back (or not) |
| 47 | its output. |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 48 | """ |
Brett Cannon | 29bb89f | 2014-08-29 11:16:29 -0400 | [diff] [blame] | 49 | from __future__ import print_function |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 50 | |
Claudiu Popa | 28bd68d | 2015-10-02 18:09:09 +0300 | [diff] [blame] | 51 | import os |
Sylvain Thénault | 99196f2 | 2013-12-22 23:41:57 +0100 | [diff] [blame] | 52 | import os.path as osp |
Claudiu Popa | e87abe9 | 2015-11-25 15:12:59 +0200 | [diff] [blame] | 53 | import sys |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 54 | from subprocess import Popen, PIPE |
| 55 | |
Claudiu Popa | 1cdb8bc | 2016-03-28 18:46:48 +0100 | [diff] [blame^] | 56 | import six |
| 57 | |
| 58 | |
Manuel Vázquez Acosta | 2aecb5d | 2014-01-06 16:07:33 -0500 | [diff] [blame] | 59 | def _get_env(): |
| 60 | '''Extracts the environment PYTHONPATH and appends the current sys.path to |
| 61 | those.''' |
| 62 | env = dict(os.environ) |
Manuel Vázquez Acosta | ffb1230 | 2014-04-14 16:07:58 -0400 | [diff] [blame] | 63 | env['PYTHONPATH'] = os.pathsep.join(sys.path) |
Manuel Vázquez Acosta | 2aecb5d | 2014-01-06 16:07:33 -0500 | [diff] [blame] | 64 | return env |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 65 | |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 66 | def lint(filename, options=None): |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 67 | """Pylint the given file. |
| 68 | |
Sylvain Thénault | 177e7e5 | 2013-10-07 12:17:27 +0200 | [diff] [blame] | 69 | 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énault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 73 | |
Sylvain Thénault | 177e7e5 | 2013-10-07 12:17:27 +0200 | [diff] [blame] | 74 | 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énault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 76 | |
Sylvain Thénault | 177e7e5 | 2013-10-07 12:17:27 +0200 | [diff] [blame] | 77 | 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énault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 81 | """ |
| 82 | # traverse downwards until we are out of a python package |
Sylvain Thénault | 99196f2 | 2013-12-22 23:41:57 +0100 | [diff] [blame] | 83 | full_path = osp.abspath(filename) |
| 84 | parent_path = osp.dirname(full_path) |
| 85 | child_path = osp.basename(full_path) |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 86 | |
Sylvain Thénault | 99196f2 | 2013-12-22 23:41:57 +0100 | [diff] [blame] | 87 | 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énault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 90 | |
| 91 | # Start pylint |
Sylvain Thénault | c8e20f7 | 2012-01-09 21:25:10 +0100 | [diff] [blame] | 92 | # Ensure we use the python and pylint associated with the running epylint |
Sylvain Thénault | 99196f2 | 2013-12-22 23:41:57 +0100 | [diff] [blame] | 93 | from pylint import lint as lint_mod |
| 94 | lint_path = lint_mod.__file__ |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 95 | options = options or ['--disable=C,R,I'] |
Manuel Vázquez Acosta | db303eb | 2014-04-14 16:25:34 -0400 | [diff] [blame] | 96 | 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 Acosta | 2aecb5d | 2014-01-06 16:07:33 -0500 | [diff] [blame] | 99 | process = Popen(cmd, stdout=PIPE, cwd=parent_path, env=_get_env(), |
Manuel Vázquez Acosta | 2da336b | 2014-01-06 15:31:19 -0500 | [diff] [blame] | 100 | universal_newlines=True) |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 101 | |
Sylvain Thénault | c8e20f7 | 2012-01-09 21:25:10 +0100 | [diff] [blame] | 102 | for line in process.stdout: |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 103 | # remove pylintrc warning |
| 104 | if line.startswith("No config file found"): |
| 105 | continue |
Manuel Vázquez Acosta | f8fdbc3 | 2014-04-14 16:57:25 -0400 | [diff] [blame] | 106 | |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 107 | # modify the file name thats output to reverse the path traversal we made |
| 108 | parts = line.split(":") |
Sylvain Thénault | 99196f2 | 2013-12-22 23:41:57 +0100 | [diff] [blame] | 109 | if parts and parts[0] == child_path: |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 110 | line = ":".join([filename] + parts[1:]) |
Brett Cannon | 29bb89f | 2014-08-29 11:16:29 -0400 | [diff] [blame] | 111 | print(line, end=' ') |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 112 | |
Sylvain Thénault | c8e20f7 | 2012-01-09 21:25:10 +0100 | [diff] [blame] | 113 | process.wait() |
| 114 | return process.returncode |
Sylvain Thénault | 981405e | 2008-12-03 09:57:14 +0100 | [diff] [blame] | 115 | |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 116 | |
| 117 | def py_run(command_options='', return_std=False, stdout=None, stderr=None, |
Emile Anclin | 85f4f8a | 2010-12-15 19:14:21 +0100 | [diff] [blame] | 118 | script='epylint'): |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 119 | """Run pylint from python |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 120 | |
| 121 | ``command_options`` is a string containing ``pylint`` command line options; |
Jakob Normark | 962ade1 | 2014-11-06 18:56:20 +0100 | [diff] [blame] | 122 | ``return_std`` (boolean) indicates return of created standard output |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 123 | and error (see below); |
Jakob Normark | 962ade1 | 2014-11-06 18:56:20 +0100 | [diff] [blame] | 124 | ``stdout`` and ``stderr`` are 'file-like' objects in which standard output |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 125 | could be written. |
| 126 | |
| 127 | Calling agent is responsible for stdout/err management (creation, close). |
Jakob Normark | 962ade1 | 2014-11-06 18:56:20 +0100 | [diff] [blame] | 128 | Default standard output and error are those from sys, |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 129 | 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 Normark | 962ade1 | 2014-11-06 18:56:20 +0100 | [diff] [blame] | 133 | containing standard output and error related to created process, |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 134 | 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 Normark | 962ade1 | 2014-11-06 18:56:20 +0100 | [diff] [blame] | 142 | To silently run Pylint on a module, and get its standard output and error: |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 143 | >>> (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 Normark | 962ade1 | 2014-11-06 18:56:20 +0100 | [diff] [blame] | 149 | # Providing standard output and/or error if not set |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 150 | 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 Popa | f67d432 | 2015-05-16 18:25:57 +0300 | [diff] [blame] | 161 | process = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, |
| 162 | env=_get_env(), universal_newlines=True) |
Claudiu Popa | 1cdb8bc | 2016-03-28 18:46:48 +0100 | [diff] [blame^] | 163 | proc_stdout, proc_stderr = process.communicate() |
Jakob Normark | 962ade1 | 2014-11-06 18:56:20 +0100 | [diff] [blame] | 164 | # Return standard output and error |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 165 | if return_std: |
Claudiu Popa | 1cdb8bc | 2016-03-28 18:46:48 +0100 | [diff] [blame^] | 166 | return six.moves.StringIO(proc_stdout), six.moves.StringIO(proc_stderr) |
Sylvain Thénault | 1c06c67 | 2009-12-18 11:34:04 +0100 | [diff] [blame] | 167 | |
| 168 | |
Sylvain Thénault | 4b316ba | 2012-09-14 13:25:22 +0200 | [diff] [blame] | 169 | def Run(): |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 170 | if len(sys.argv) == 1: |
Brett Cannon | 29bb89f | 2014-08-29 11:16:29 -0400 | [diff] [blame] | 171 | print("Usage: %s <filename> [options]" % sys.argv[0]) |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 172 | sys.exit(1) |
Sylvain Thénault | 99196f2 | 2013-12-22 23:41:57 +0100 | [diff] [blame] | 173 | elif not osp.exists(sys.argv[1]): |
Brett Cannon | 29bb89f | 2014-08-29 11:16:29 -0400 | [diff] [blame] | 174 | print("%s does not exist" % sys.argv[1]) |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 175 | sys.exit(1) |
| 176 | else: |
Manuel Vázquez Acosta | f8fdbc3 | 2014-04-14 16:57:25 -0400 | [diff] [blame] | 177 | sys.exit(lint(sys.argv[1], sys.argv[2:])) |
Sylvain Thénault | ba979be | 2013-09-03 12:22:43 +0200 | [diff] [blame] | 178 | |
Sylvain Thénault | 4b316ba | 2012-09-14 13:25:22 +0200 | [diff] [blame] | 179 | |
Sylvain Thénault | 6a77063 | 2009-08-06 10:24:42 +0200 | [diff] [blame] | 180 | if __name__ == '__main__': |
Sylvain Thénault | 4b316ba | 2012-09-14 13:25:22 +0200 | [diff] [blame] | 181 | Run() |