Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

epylint.py 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. # -*- coding: utf-8;
  2. # mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4
  3. # -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4
  4. # Copyright (c) 2008-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  5. # Copyright (c) 2014 Jakob Normark <jakobnormark@gmail.com>
  6. # Copyright (c) 2014 Brett Cannon <brett@python.org>
  7. # Copyright (c) 2014 Manuel Vázquez Acosta <mva.led@gmail.com>
  8. # Copyright (c) 2014 Derek Harland <derek.harland@finq.co.nz>
  9. # Copyright (c) 2014 Arun Persaud <arun@nubati.net>
  10. # Copyright (c) 2015-2017 Claudiu Popa <pcmanticore@gmail.com>
  11. # Copyright (c) 2015 Mihai Balint <balint.mihai@gmail.com>
  12. # Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
  13. # Copyright (c) 2017 hippo91 <guillaume.peillex@gmail.com>
  14. # Copyright (c) 2017 Daniela Plascencia <daplascen@gmail.com>
  15. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  16. # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
  17. """Emacs and Flymake compatible Pylint.
  18. This script is for integration with emacs and is compatible with flymake mode.
  19. epylint walks out of python packages before invoking pylint. This avoids
  20. reporting import errors that occur when a module within a package uses the
  21. absolute import path to get another module within this package.
  22. For example:
  23. - Suppose a package is structured as
  24. a/__init__.py
  25. a/b/x.py
  26. a/c/y.py
  27. - Then if y.py imports x as "from a.b import x" the following produces pylint
  28. errors
  29. cd a/c; pylint y.py
  30. - The following obviously doesn't
  31. pylint a/c/y.py
  32. - As this script will be invoked by emacs within the directory of the file
  33. we are checking we need to go out of it to avoid these false positives.
  34. You may also use py_run to run pylint with desired options and get back (or not)
  35. its output.
  36. """
  37. from __future__ import print_function
  38. import os
  39. import os.path as osp
  40. import sys
  41. import shlex
  42. from subprocess import Popen, PIPE
  43. import six
  44. def _get_env():
  45. '''Extracts the environment PYTHONPATH and appends the current sys.path to
  46. those.'''
  47. env = dict(os.environ)
  48. env['PYTHONPATH'] = os.pathsep.join(sys.path)
  49. return env
  50. def lint(filename, options=None):
  51. """Pylint the given file.
  52. When run from emacs we will be in the directory of a file, and passed its
  53. filename. If this file is part of a package and is trying to import other
  54. modules from within its own package or another package rooted in a directory
  55. below it, pylint will classify it as a failed import.
  56. To get around this, we traverse down the directory tree to find the root of
  57. the package this module is in. We then invoke pylint from this directory.
  58. Finally, we must correct the filenames in the output generated by pylint so
  59. Emacs doesn't become confused (it will expect just the original filename,
  60. while pylint may extend it with extra directories if we've traversed down
  61. the tree)
  62. """
  63. # traverse downwards until we are out of a python package
  64. full_path = osp.abspath(filename)
  65. parent_path = osp.dirname(full_path)
  66. child_path = osp.basename(full_path)
  67. while parent_path != "/" and osp.exists(osp.join(parent_path, '__init__.py')):
  68. child_path = osp.join(osp.basename(parent_path), child_path)
  69. parent_path = osp.dirname(parent_path)
  70. # Start pylint
  71. # Ensure we use the python and pylint associated with the running epylint
  72. run_cmd = "import sys; from pylint.lint import Run; Run(sys.argv[1:])"
  73. options = options or ['--disable=C,R,I']
  74. cmd = [sys.executable, "-c", run_cmd] + [
  75. '--msg-template', '{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}',
  76. '-r', 'n', child_path] + options
  77. process = Popen(cmd, stdout=PIPE, cwd=parent_path, env=_get_env(),
  78. universal_newlines=True)
  79. for line in process.stdout:
  80. # remove pylintrc warning
  81. if line.startswith("No config file found"):
  82. continue
  83. # modify the file name thats output to reverse the path traversal we made
  84. parts = line.split(":")
  85. if parts and parts[0] == child_path:
  86. line = ":".join([filename] + parts[1:])
  87. print(line, end=' ')
  88. process.wait()
  89. return process.returncode
  90. def py_run(command_options='', return_std=False, stdout=None, stderr=None):
  91. """Run pylint from python
  92. ``command_options`` is a string containing ``pylint`` command line options;
  93. ``return_std`` (boolean) indicates return of created standard output
  94. and error (see below);
  95. ``stdout`` and ``stderr`` are 'file-like' objects in which standard output
  96. could be written.
  97. Calling agent is responsible for stdout/err management (creation, close).
  98. Default standard output and error are those from sys,
  99. or standalone ones (``subprocess.PIPE``) are used
  100. if they are not set and ``return_std``.
  101. If ``return_std`` is set to ``True``, this function returns a 2-uple
  102. containing standard output and error related to created process,
  103. as follows: ``(stdout, stderr)``.
  104. A trivial usage could be as follows:
  105. >>> py_run( '--version')
  106. No config file found, using default configuration
  107. pylint 0.18.1,
  108. ...
  109. To silently run Pylint on a module, and get its standard output and error:
  110. >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True)
  111. """
  112. # Create command line to call pylint
  113. epylint_part = [sys.executable, "-c", "from pylint import epylint;epylint.Run()"]
  114. options = shlex.split(command_options)
  115. cli = epylint_part + options
  116. # Providing standard output and/or error if not set
  117. if stdout is None:
  118. if return_std:
  119. stdout = PIPE
  120. else:
  121. stdout = sys.stdout
  122. if stderr is None:
  123. if return_std:
  124. stderr = PIPE
  125. else:
  126. stderr = sys.stderr
  127. # Call pylint in a subprocess
  128. process = Popen(cli, shell=False, stdout=stdout, stderr=stderr,
  129. env=_get_env(), universal_newlines=True)
  130. proc_stdout, proc_stderr = process.communicate()
  131. # Return standard output and error
  132. if return_std:
  133. return six.moves.StringIO(proc_stdout), six.moves.StringIO(proc_stderr)
  134. return None
  135. def Run():
  136. if len(sys.argv) == 1:
  137. print("Usage: %s <filename> [options]" % sys.argv[0])
  138. sys.exit(1)
  139. elif not osp.exists(sys.argv[1]):
  140. print("%s does not exist" % sys.argv[1])
  141. sys.exit(1)
  142. else:
  143. sys.exit(lint(sys.argv[1], sys.argv[2:]))
  144. if __name__ == '__main__':
  145. Run()