PluginPyLint.py

changeset 22
68beeb22dade
parent 20
8ca799e4b7e4
child 24
b01348dd84d5
equal deleted inserted replaced
21:f2144b9596a0 22:68beeb22dade
5 5
6 """ 6 """
7 Module implementing the PyLint plug-in. 7 Module implementing the PyLint plug-in.
8 """ 8 """
9 9
10 from __future__ import unicode_literals # __IGNORE_WARNING__
11 try:
12 str = unicode
13 except (NameError):
14 pass
15
16 import re
10 import os 17 import os
11 import sys
12 import copy 18 import copy
13 19 import platform
14 from PyQt4.QtCore import QObject, QTranslator, QCoreApplication 20
21 from PyQt4.QtCore import QObject, QTranslator, QCoreApplication, QProcess
15 from PyQt4.QtGui import QDialog 22 from PyQt4.QtGui import QDialog
16 23
17 from E5Gui.E5Application import e5App 24 try:
18 from E5Gui.E5Action import E5Action 25 from E5Gui.E5Application import e5App
19 from E5Gui import E5MessageBox 26 from E5Gui.E5Action import E5Action
20 27 from E5Gui import E5MessageBox
28 error = ""
29 except ImportError:
30 error = QCoreApplication.translate("PyLintPlugin",
31 """Your version of Eric5 is not supported."""
32 """ At least version 5.1.0 of Eric5 is needed.""")
33
34 import Preferences
21 import Utilities 35 import Utilities
22 36
23 # Start-of-Header 37 # Start-of-Header
24 name = "PyLint Plugin" 38 name = "PyLint Plugin"
25 author = "Detlev Offenbach <detlev@die-offenbachs.de>" 39 author = "Detlev Offenbach <detlev@die-offenbachs.de>"
26 autoactivate = True 40 autoactivate = True
27 deactivateable = True 41 deactivateable = True
28 version = "5.2.1" 42 version = "5.3.0"
29 className = "PyLintPlugin" 43 className = "PyLintPlugin"
30 packageName = "PyLint" 44 packageName = "PyLint"
31 shortDescription = "Show the PyLint dialogs." 45 shortDescription = "Show the PyLint dialogs."
32 longDescription = """This plug-in implements the PyLint dialogs.""" \ 46 longDescription = """This plug-in implements the PyLint dialogs.""" \
33 """ PyLint is used to check Python source files according to various rules.""" 47 """ PyLint is used to check Python source files according to various rules."""
34 needsRestart = False 48 needsRestart = False
35 pyqtApi = 2 49 pyqtApi = 2
36 # End-of-Header 50 # End-of-Header
37 51
38 error = "" 52 exePy2 = []
39 53 exePy3 = []
40 54
41 def exeDisplayData(): 55 def exeDisplayDataList():
42 """ 56 """
43 Public method to support the display of some executable info. 57 Public method to support the display of some executable info.
44 58
45 @return dictionary containing the data to query the presence of 59 @return dictionary containing the data to query the presence of
46 the executable 60 the executable
47 """ 61 """
62 dataList = []
48 data = { 63 data = {
49 "programEntry": True, 64 "programEntry": True,
50 "header": QCoreApplication.translate("PyLintPlugin", 65 "header": QCoreApplication.translate("PyLintPlugin",
51 "Checkers - Pylint"), 66 "Checkers - Pylint"),
52 "exe": 'dummypylint', 67 "exe": 'dummypylint',
54 "versionStartsWith": 'dummypylint', 69 "versionStartsWith": 'dummypylint',
55 "versionPosition": -1, 70 "versionPosition": -1,
56 "version": "", 71 "version": "",
57 "versionCleanup": (0, -1), 72 "versionCleanup": (0, -1),
58 } 73 }
59 exe = _findExecutable() 74 if _checkProgram():
60 if exe: 75 for exePath in (exePy2[0], exePy3[0]):
61 data["exe"] = exe 76 data["exe"] = exePath
62 data["versionStartsWith"] = 'pylint' 77 data["versionStartsWith"] = "pylint"
63 78 dataList.append(data.copy())
64 return data 79 else:
65 80 dataList.append(data)
66 81 return dataList
67 def _findExecutable(): 82
83 def __getProgramVersion(exe):
84 """
85 Private method to generate a program entry.
86
87 @param exe name of the executable program (string)
88 @return version string of detected version (string)
89 """
90 proc = QProcess()
91 proc.setProcessChannelMode(QProcess.MergedChannels)
92 proc.start(exe, ['--version'])
93 finished = proc.waitForFinished(10000)
94 if finished:
95 output = str(proc.readAllStandardOutput(),
96 Preferences.getSystem("IOEncoding"),
97 'replace')
98 versionRe = re.compile('^pylint', re.UNICODE)
99 for line in output.splitlines():
100 if versionRe.search(line):
101 version = line.split()[-1]
102 version = version[:-1]
103 break
104 else:
105 version = '0.0.0'
106 return version
107
108 def _findExecutable(majorVersion):
68 """ 109 """
69 Restricted function to determine the name and path of the executable. 110 Restricted function to determine the name and path of the executable.
70 111
112 @param majorVersion major python version of the executables (int)
71 @return path name of the executable (string) 113 @return path name of the executable (string)
72 """ 114 """
115 # Determine Python Version
116 if majorVersion == 3:
117 minorVersions = range(5)
118 elif majorVersion == 2:
119 minorVersions = range(5, 9)
120 else:
121 return []
122
123 executables = set()
73 if Utilities.isWindowsPlatform(): 124 if Utilities.isWindowsPlatform():
74 # 125 #
75 # Windows 126 # Windows
76 # 127 #
77 exe = 'pylint.bat'
78 if Utilities.isinpath(exe):
79 return exe
80 try: 128 try:
81 # only since python 3.2 129 import winreg
82 import sysconfig
83 scripts = sysconfig.get_path('scripts', 'nt')
84 return os.path.join(scripts, exe)
85 except ImportError: 130 except ImportError:
131 import _winreg as winreg # __IGNORE_WARNING__
132
133 def getExePath(branch, access, versionStr):
86 try: 134 try:
87 import winreg 135 software = winreg.OpenKey(branch, 'Software', 0, access)
88 except ImportError: 136 python = winreg.OpenKey(software, 'Python', 0, access)
89 # give up ... 137 pcore = winreg.OpenKey(python, 'PythonCore', 0, access)
138 version = winreg.OpenKey(pcore, versionStr, 0, access)
139 installpath = winreg.QueryValue(version, 'InstallPath')
140 exe = os.path.join(installpath, 'Scripts', 'pylint.bat')
141 if os.access(exe, os.X_OK):
142 return exe
143 except WindowsError: # __IGNORE_WARNING__
90 return None 144 return None
145 return None
146
147 for minorVersion in minorVersions:
148 versionStr = '{0}.{1}'.format(majorVersion, minorVersion)
149 exePath = getExePath(winreg.HKEY_CURRENT_USER,
150 winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr)
151
152 if exePath is not None:
153 executables.add(exePath)
154 exePath = getExePath(winreg.HKEY_LOCAL_MACHINE,
155 winreg.KEY_WOW64_32KEY | winreg.KEY_READ, versionStr)
91 156
92 def getExePath(branch): 157 # Even on Intel 64-bit machines it's 'AMD64'
93 version = str(sys.version_info.major) + '.' + \ 158 if platform.machine() == 'AMD64':
94 str(sys.version_info.minor) 159 if exePath is not None:
95 try: 160 executables.add(exePath)
96 software = winreg.OpenKey(branch, 'Software') 161 exePath = getExePath(winreg.HKEY_CURRENT_USER,
97 python = winreg.OpenKey(software, 'Python') 162 winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr)
98 pcore = winreg.OpenKey(python, 'PythonCore') 163
99 version = winreg.OpenKey(pcore, version) 164 if exePath is not None:
100 installpath = winreg.QueryValue(version, 'InstallPath') 165 executables.add(exePath)
101 return os.path.join(installpath, 'Scripts', exe) 166 exePath = getExePath(winreg.HKEY_LOCAL_MACHINE,
102 except WindowsError: # __IGNORE_WARNING__ 167 winreg.KEY_WOW64_64KEY | winreg.KEY_READ, versionStr)
103 return None 168
104 169 if exePath is not None:
105 exePath = getExePath(winreg.HKEY_CURRENT_USER) 170 executables.add(exePath)
106 if not exePath:
107 exePath = getExePath(winreg.HKEY_LOCAL_MACHINE)
108 return exePath
109 else: 171 else:
110 # 172 #
111 # Linux, Unix ... 173 # Linux, Unix ...
112 pylintScript = 'pylint' 174 pylintScript = 'pylint'
113 scriptSuffixes = ["", 175 scriptSuffixes = ["",
114 "-python{0}".format(sys.version[:1]), 176 "-python{0}".format(majorVersion)]
115 "-python{0}".format(sys.version[:3])] 177 for minorVersion in minorVersions:
178 scriptSuffixes.append(
179 "-python{0}.{1}".format(majorVersion, minorVersion))
116 # There could be multiple pylint executables in the path 180 # There could be multiple pylint executables in the path
117 # e.g. for different python variants 181 # e.g. for different python variants
118 path = Utilities.getEnvironmentEntry('PATH') 182 path = Utilities.getEnvironmentEntry('PATH')
119 # environment variable not defined 183 # environment variable not defined
120 if path is None: 184 if path is None:
121 return None 185 return []
122 186
123 # step 1: determine possible candidates 187 # step 1: determine possible candidates
124 exes = [] 188 exes = []
125 dirs = path.split(os.pathsep) 189 dirs = path.split(os.pathsep)
126 for dir in dirs: 190 for dir in dirs:
127 for suffix in scriptSuffixes: 191 for suffix in scriptSuffixes:
128 exe = os.path.join(dir, pylintScript + suffix) 192 exe = os.path.join(dir, pylintScript + suffix)
129 if os.access(exe, os.X_OK): 193 if os.access(exe, os.X_OK):
130 exes.append(exe) 194 exes.append(exe)
131 195
132 # step 2: determine the Python 3 variant 196 # step 2: determine the Python variant
133 found = False
134 if Utilities.isMacPlatform(): 197 if Utilities.isMacPlatform():
135 checkStr = "Python.framework/Versions/3".lower() 198 checkStrings = ["Python.framework/Versions/3".lower(),
199 "python3"]
136 else: 200 else:
137 checkStr = "python3" 201 checkStrings = ["python3"]
202
203 _exePy2 = set()
204 _exePy3 = set()
138 for exe in exes: 205 for exe in exes:
139 try: 206 try:
140 f = open(exe, "r") 207 f = open(exe, "r")
141 line0 = f.readline() 208 line0 = f.readline()
142 if checkStr in line0.lower(): 209 for checkStr in checkStrings:
143 found = True 210 if checkStr in line0.lower():
211 _exePy3.add(exe)
212 break
213 else:
214 _exePy2.add(exe)
144 finally: 215 finally:
145 f.close() 216 f.close()
146 if found: 217
147 return exe 218 executables = _exePy3 if majorVersion == 3 else _exePy2
148 219
149 return None 220 # Find the executable with the highest version number
150 221 maxVersion = '0.0.0'
222 maxExe = ''
223 for executable in list(executables):
224 version = __getProgramVersion(executable)
225 if version > maxVersion:
226 maxVersion = version
227 maxExe = executable
228
229 return maxExe, maxVersion
151 230
152 def _checkProgram(): 231 def _checkProgram():
153 """ 232 """
154 Restricted function to check the availability of pylint. 233 Restricted function to check the availability of pylint.
155 234
156 @return flag indicating availability (boolean) 235 @return flag indicating availability (boolean)
157 """ 236 """
158 global error 237 global error, exePy2, exePy3
159 238
160 if _findExecutable() is None: 239 exePy2 = _findExecutable(2)
240 exePy3 = _findExecutable(3)
241 if exePy2[0] == '' and exePy3[0] == '':
161 error = QCoreApplication.translate("PyLintPlugin", 242 error = QCoreApplication.translate("PyLintPlugin",
162 "The pylint executable could not be found.") 243 "The pylint executable could not be found.")
244 return False
245 elif exePy2[1] < '0.23.0' and exePy3[1] < '0.23.0':
246 error = QCoreApplication.translate("PyLintPlugin",
247 "PyLint version < 0.23.0.")
163 return False 248 return False
164 else: 249 else:
165 return True 250 return True
166 251
167 252
206 291
207 @return tuple of None and activation status (boolean) 292 @return tuple of None and activation status (boolean)
208 """ 293 """
209 global error 294 global error
210 295
296 # There is already an error, don't activate
297 if error:
298 return None, False
211 # pylint is only activated if it is available 299 # pylint is only activated if it is available
212 if not _checkProgram(): 300 if not _checkProgram():
213 return None, False
214
215 try:
216 from pylint.__pkginfo__ import numversion
217 if numversion < (0, 23, 0):
218 error = self.trUtf8("PyLint version < 0.23.0.")
219 return None, False
220 except ImportError:
221 error = self.trUtf8("Cannot determine pylint version.")
222 return None, False 301 return None, False
223 302
224 menu = e5App().getObject("Project").getMenu("Checks") 303 menu = e5App().getObject("Project").getMenu("Checks")
225 if menu: 304 if menu:
226 self.__projectAct = E5Action(self.trUtf8('Run PyLint'), 305 self.__projectAct = E5Action(self.trUtf8('Run PyLint'),
331 410
332 @param menuName name of the menu to be shown (string) 411 @param menuName name of the menu to be shown (string)
333 @param menu reference to the menu (QMenu) 412 @param menu reference to the menu (QMenu)
334 """ 413 """
335 if menuName == "Checks": 414 if menuName == "Checks":
415 lang = e5App().getObject("Project").getProjectLanguage()
336 if self.__projectAct is not None: 416 if self.__projectAct is not None:
337 self.__projectAct.setEnabled( 417 self.__projectAct.setEnabled(lang.startswith("Python"))
338 e5App().getObject("Project").getProjectLanguage() == "Python3")
339 if self.__projectShowAct is not None: 418 if self.__projectShowAct is not None:
340 self.__projectShowAct.setEnabled( 419 self.__projectShowAct.setEnabled(lang.startswith("Python"))
341 e5App().getObject("Project").getProjectLanguage() == "Python3")
342 self.__projectShowAct.setEnabled(self.__pylintPDialog is not None) 420 self.__projectShowAct.setEnabled(self.__pylintPDialog is not None)
343 421
344 def __projectBrowserShowMenu(self, menuName, menu): 422 def __projectBrowserShowMenu(self, menuName, menu):
345 """ 423 """
346 Private slot called, when the the project browser menu or a submenu is 424 Private slot called, when the the project browser menu or a submenu is
348 426
349 @param menuName name of the menu to be shown (string) 427 @param menuName name of the menu to be shown (string)
350 @param menu reference to the menu (QMenu) 428 @param menu reference to the menu (QMenu)
351 """ 429 """
352 if menuName == "Checks" and \ 430 if menuName == "Checks" and \
353 e5App().getObject("Project").getProjectLanguage() == "Python3": 431 e5App().getObject("Project").getProjectLanguage().startswith("Python"):
354 self.__projectBrowserMenu = menu 432 self.__projectBrowserMenu = menu
355 if self.__projectBrowserAct is None: 433 if self.__projectBrowserAct is None:
356 self.__projectBrowserAct = E5Action(self.trUtf8('Run PyLint'), 434 self.__projectBrowserAct = E5Action(self.trUtf8('Run PyLint'),
357 self.trUtf8('Run &PyLint...'), 0, 0, 435 self.trUtf8('Run &PyLint...'), 0, 0,
358 self, '') 436 self, '')
391 @param mpName name of module or package to be checked (string) 469 @param mpName name of module or package to be checked (string)
392 @param forProject flag indicating a run for the project (boolean) 470 @param forProject flag indicating a run for the project (boolean)
393 """ 471 """
394 if forEditor: 472 if forEditor:
395 parms = copy.deepcopy(self.__editorParms) 473 parms = copy.deepcopy(self.__editorParms)
474 editor = e5App().getObject("ViewManager").getOpenEditor(mpName)
475 majorVersionStr = editor.getLanguage()
396 else: 476 else:
397 parms = project.getData('CHECKERSPARMS', "PYLINT") 477 parms = project.getData('CHECKERSPARMS', "PYLINT")
398 exe = _findExecutable() 478 majorVersionStr = project.getProjectLanguage()
399 if exe is None: 479 exe, version = {"Python": exePy2, "Python2": exePy2,
480 "Python3": exePy3}.get(majorVersionStr)
481 if exe == '':
400 E5MessageBox.critical(None, 482 E5MessageBox.critical(None,
401 self.trUtf8("pylint"), 483 self.trUtf8("pylint"),
402 self.trUtf8("""The pylint executable could not be found.""")) 484 self.trUtf8("""The pylint executable could not be found."""))
403 return 485 return
486 elif version < '0.23.0':
487 E5MessageBox.critical(None,
488 self.trUtf8("pylint"),
489 self.trUtf8("PyLint version < 0.23.0."))
490 return
404 491
405 from PyLint.PyLintConfigDialog import PyLintConfigDialog 492 from PyLint.PyLintConfigDialog import PyLintConfigDialog
406 dlg = PyLintConfigDialog(project.getProjectPath(), exe, parms) 493 dlg = PyLintConfigDialog(project.getProjectPath(), exe, parms, version)
407 if dlg.exec_() == QDialog.Accepted: 494 if dlg.exec_() == QDialog.Accepted:
408 args, parms = dlg.generateParameters() 495 args, parms = dlg.generateParameters()
409 self.__editorParms = copy.deepcopy(parms) 496 self.__editorParms = copy.deepcopy(parms)
410 if not forEditor: 497 if not forEditor:
411 project.setData('CHECKERSPARMS', "PYLINT", parms) 498 project.setData('CHECKERSPARMS', "PYLINT", parms)
412 499
413 # now do the call 500 # now do the call
414 from PyLint.PyLintExecDialog import PyLintExecDialog 501 from PyLint.PyLintExecDialog import PyLintExecDialog
415 dlg2 = PyLintExecDialog() 502 dlg2 = PyLintExecDialog()
416 try: 503 reportFile = parms.get('reportFile', None)
417 reportFile = parms['reportFile']
418 except KeyError:
419 reportFile = None
420 res = dlg2.start(args, mpName, reportFile, project.getProjectPath()) 504 res = dlg2.start(args, mpName, reportFile, project.getProjectPath())
421 if res: 505 if res:
422 dlg2.show() 506 dlg2.show()
423 if forProject: 507 if forProject:
424 self.__pylintPDialog = dlg2 508 self.__pylintPDialog = dlg2
496 @param editor reference to the editor (QScintilla.Editor) 580 @param editor reference to the editor (QScintilla.Editor)
497 """ 581 """
498 if menuName == "Checks": 582 if menuName == "Checks":
499 if not self.__editorAct in menu.actions(): 583 if not self.__editorAct in menu.actions():
500 menu.addAction(self.__editorAct) 584 menu.addAction(self.__editorAct)
501 self.__editorAct.setEnabled(editor.isPy3File()) 585 self.__editorAct.setEnabled(editor.isPy3File() or editor.isPy2File())
502 586
503 def __editorPylint(self): 587 def __editorPylint(self):
504 """ 588 """
505 Private slot to handle the Pylint context menu action of the editors. 589 Private slot to handle the Pylint context menu action of the editors.
506 """ 590 """

eric ide

mercurial