OLD | NEW |
(Empty) | |
| 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. |
| 17 """Emacs and Flymake compatible Pylint. |
| 18 |
| 19 This script is for integration with emacs and is compatible with flymake mode. |
| 20 |
| 21 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 |
| 23 absolute import path to get another module within this package. |
| 24 |
| 25 For example: |
| 26 - Suppose a package is structured as |
| 27 |
| 28 a/__init__.py |
| 29 a/b/x.py |
| 30 a/c/y.py |
| 31 |
| 32 - Then if y.py imports x as "from a.b import x" the following produces pylint |
| 33 errors |
| 34 |
| 35 cd a/c; pylint y.py |
| 36 |
| 37 - The following obviously doesn't |
| 38 |
| 39 pylint a/c/y.py |
| 40 |
| 41 - 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. |
| 43 |
| 44 |
| 45 You may also use py_run to run pylint with desired options and get back (or not) |
| 46 its output. |
| 47 """ |
| 48 from __future__ import print_function |
| 49 |
| 50 import sys, os |
| 51 import os.path as osp |
| 52 from subprocess import Popen, PIPE |
| 53 |
| 54 def _get_env(): |
| 55 '''Extracts the environment PYTHONPATH and appends the current sys.path to |
| 56 those.''' |
| 57 env = dict(os.environ) |
| 58 env['PYTHONPATH'] = os.pathsep.join(sys.path) |
| 59 return env |
| 60 |
| 61 def lint(filename, options=None): |
| 62 """Pylint the given file. |
| 63 |
| 64 When run from emacs we will be in the directory of a file, and passed its |
| 65 filename. If this file is part of a package and is trying to import other |
| 66 modules from within its own package or another package rooted in a directory |
| 67 below it, pylint will classify it as a failed import. |
| 68 |
| 69 To get around this, we traverse down the directory tree to find the root of |
| 70 the package this module is in. We then invoke pylint from this directory. |
| 71 |
| 72 Finally, we must correct the filenames in the output generated by pylint so |
| 73 Emacs doesn't become confused (it will expect just the original filename, |
| 74 while pylint may extend it with extra directories if we've traversed down |
| 75 the tree) |
| 76 """ |
| 77 # traverse downwards until we are out of a python package |
| 78 full_path = osp.abspath(filename) |
| 79 parent_path = osp.dirname(full_path) |
| 80 child_path = osp.basename(full_path) |
| 81 |
| 82 while parent_path != "/" and osp.exists(osp.join(parent_path, '__init__.py')
): |
| 83 child_path = osp.join(osp.basename(parent_path), child_path) |
| 84 parent_path = osp.dirname(parent_path) |
| 85 |
| 86 # Start pylint |
| 87 # Ensure we use the python and pylint associated with the running epylint |
| 88 from pylint import lint as lint_mod |
| 89 lint_path = lint_mod.__file__ |
| 90 options = options or ['--disable=C,R,I'] |
| 91 cmd = [sys.executable, lint_path] + options + [ |
| 92 '--msg-template', '{path}:{line}: {category} ({msg_id}, {symbol}, {obj})
{msg}', |
| 93 '-r', 'n', child_path] |
| 94 process = Popen(cmd, stdout=PIPE, cwd=parent_path, env=_get_env(), |
| 95 universal_newlines=True) |
| 96 |
| 97 for line in process.stdout: |
| 98 # remove pylintrc warning |
| 99 if line.startswith("No config file found"): |
| 100 continue |
| 101 |
| 102 # modify the file name thats output to reverse the path traversal we mad
e |
| 103 parts = line.split(":") |
| 104 if parts and parts[0] == child_path: |
| 105 line = ":".join([filename] + parts[1:]) |
| 106 print(line, end=' ') |
| 107 |
| 108 process.wait() |
| 109 return process.returncode |
| 110 |
| 111 |
| 112 def py_run(command_options='', return_std=False, stdout=None, stderr=None, |
| 113 script='epylint'): |
| 114 """Run pylint from python |
| 115 |
| 116 ``command_options`` is a string containing ``pylint`` command line options; |
| 117 ``return_std`` (boolean) indicates return of created standard output |
| 118 and error (see below); |
| 119 ``stdout`` and ``stderr`` are 'file-like' objects in which standard output |
| 120 could be written. |
| 121 |
| 122 Calling agent is responsible for stdout/err management (creation, close). |
| 123 Default standard output and error are those from sys, |
| 124 or standalone ones (``subprocess.PIPE``) are used |
| 125 if they are not set and ``return_std``. |
| 126 |
| 127 If ``return_std`` is set to ``True``, this function returns a 2-uple |
| 128 containing standard output and error related to created process, |
| 129 as follows: ``(stdout, stderr)``. |
| 130 |
| 131 A trivial usage could be as follows: |
| 132 >>> py_run( '--version') |
| 133 No config file found, using default configuration |
| 134 pylint 0.18.1, |
| 135 ... |
| 136 |
| 137 To silently run Pylint on a module, and get its standard output and error: |
| 138 >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True) |
| 139 """ |
| 140 # Create command line to call pylint |
| 141 if os.name == 'nt': |
| 142 script += '.bat' |
| 143 command_line = script + ' ' + command_options |
| 144 # Providing standard output and/or error if not set |
| 145 if stdout is None: |
| 146 if return_std: |
| 147 stdout = PIPE |
| 148 else: |
| 149 stdout = sys.stdout |
| 150 if stderr is None: |
| 151 if return_std: |
| 152 stderr = PIPE |
| 153 else: |
| 154 stderr = sys.stderr |
| 155 # Call pylint in a subprocess |
| 156 p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, |
| 157 env=_get_env(), universal_newlines=True) |
| 158 p.wait() |
| 159 # Return standard output and error |
| 160 if return_std: |
| 161 return (p.stdout, p.stderr) |
| 162 |
| 163 |
| 164 def Run(): |
| 165 if len(sys.argv) == 1: |
| 166 print("Usage: %s <filename> [options]" % sys.argv[0]) |
| 167 sys.exit(1) |
| 168 elif not osp.exists(sys.argv[1]): |
| 169 print("%s does not exist" % sys.argv[1]) |
| 170 sys.exit(1) |
| 171 else: |
| 172 sys.exit(lint(sys.argv[1], sys.argv[2:])) |
| 173 |
| 174 |
| 175 if __name__ == '__main__': |
| 176 Run() |
OLD | NEW |