PluginPyLint.py

changeset 0
1c1ac27f3cf1
child 3
78fc974034dc
equal deleted inserted replaced
-1:000000000000 0:1c1ac27f3cf1
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2011 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the PyLint plug-in.
8 """
9
10 import os
11 import sys
12 import copy
13
14 from PyQt4.QtCore import QObject, QTranslator, QCoreApplication
15 from PyQt4.QtGui import QDialog
16
17 from E5Gui.E5Application import e5App
18 from E5Gui.E5Action import E5Action
19 from E5Gui import E5MessageBox
20
21 from PyLint.PyLintConfigDialog import PyLintConfigDialog
22 from PyLint.PyLintExecDialog import PyLintExecDialog
23
24 import Utilities
25
26 # Start-of-Header
27 name = "PyLint Plugin"
28 author = "Detlev Offenbach <detlev@die-offenbachs.de>"
29 autoactivate = True
30 deactivateable = True
31 version = "5.0.0"
32 className = "PyLintPlugin"
33 packageName = "PyLint"
34 shortDescription = "Show the PyLint dialogs."
35 longDescription = """This plug-in implements the PyLint dialogs.""" \
36 """ PyLint is used to check Python source files according to various rules."""
37 needsRestart = False
38 pyqtApi = 2
39 # End-of-Header
40
41 error = ""
42
43
44 def exeDisplayData():
45 """
46 Public method to support the display of some executable info.
47
48 @return dictionary containing the data to query the presence of
49 the executable
50 """
51 data = {
52 "programEntry": True,
53 "header": QCoreApplication.translate("PyLintPlugin",
54 "Checkers - Pylint"),
55 "exe": 'dummypylint',
56 "versionCommand": '--version',
57 "versionStartsWith": 'dummypylint',
58 "versionPosition": -1,
59 "version": "",
60 "versionCleanup": (0, -1),
61 }
62 exe = _findExecutable()
63 if exe:
64 data["exe"] = exe
65 data["versionStartsWith"] = 'pylint'
66
67 return data
68
69
70 def _findExecutable():
71 """
72 Restricted function to determine the name and path of the executable.
73
74 @return path name of the executable (string)
75 """
76 if Utilities.isWindowsPlatform():
77 #
78 # Windows
79 #
80 exe = 'pylint.bat'
81 if Utilities.isinpath(exe):
82 return exe
83 try:
84 #only since python 3.2
85 import sysconfig
86 scripts = sysconfig.get_path('scripts', 'nt')
87 return os.path.join(scripts, exe)
88 except ImportError:
89 try:
90 import winreg
91 except ImportError:
92 # give up ...
93 return None
94
95 def getExePath(branch):
96 version = str(sys.version_info.major) + '.' + \
97 str(sys.version_info.minor)
98 try:
99 software = winreg.OpenKey(branch, 'Software')
100 python = winreg.OpenKey(software, 'Python')
101 pcore = winreg.OpenKey(python, 'PythonCore')
102 version = winreg.OpenKey(pcore, version)
103 installpath = winreg.QueryValue(version, 'InstallPath')
104 return os.path.join(installpath, 'Scripts', exe)
105 except WindowsError: # __IGNORE_WARNING__
106 return None
107
108 exePath = getExePath(winreg.HKEY_CURRENT_USER)
109 if not exePath:
110 exePath = getExePath(winreg.HKEY_LOCAL_MACHINE)
111 return exePath
112 else:
113 #
114 # Linux, Unix ...
115 pylintScript = 'pylint'
116 # There could be multiple pylint executables in the path
117 # e.g. for different python variants
118 path = Utilities.getEnvironmentEntry('PATH')
119 # environment variable not defined
120 if path is None:
121 return None
122
123 # step 1: determine possible candidates
124 exes = []
125 dirs = path.split(os.pathsep)
126 for dir in dirs:
127 exe = os.path.join(dir, pylintScript)
128 if os.access(exe, os.X_OK):
129 exes.append(exe)
130
131 # step 2: determine the Python 3 variant
132 found = False
133 for exe in exes:
134 try:
135 f = open(exe, "r")
136 line0 = f.readline()
137 if "python3" in line0.lower():
138 found = True
139 finally:
140 f.close()
141 if found:
142 return exe
143
144 return None
145
146
147 def _checkProgram():
148 """
149 Restricted function to check the availability of pylint.
150
151 @return flag indicating availability (boolean)
152 """
153 global error
154
155 if _findExecutable() is None:
156 error = QCoreApplication.translate("PyLintPlugin",
157 "The pylint executable could not be found.")
158 return False
159 else:
160 return True
161
162
163 class PyLintPlugin(QObject):
164 """
165 Class implementing the PyLint plug-in.
166 """
167 def __init__(self, ui):
168 """
169 Constructor
170
171 @param ui reference to the user interface object (UI.UserInterface)
172 """
173 QObject.__init__(self, ui)
174 self.__ui = ui
175 self.__initialize()
176
177 self.__translator = None
178 self.__loadTranslator()
179
180 def __initialize(self):
181 """
182 Private slot to (re)initialize the plugin.
183 """
184 self.__projectAct = None
185 self.__projectShowAct = None
186 self.__pylintPDialog = None
187
188 self.__projectBrowserAct = None
189 self.__projectBrowserShowAct = None
190 self.__projectBrowserMenu = None
191 self.__pylintPsbDialog = None
192
193 self.__editors = []
194 self.__editorAct = None
195 self.__editorPylintDialog = None
196 self.__editorParms = None
197
198 def activate(self):
199 """
200 Public method to activate this plugin.
201
202 @return tuple of None and activation status (boolean)
203 """
204 global error
205
206 # pylint is only activated if it is available
207 if not _checkProgram():
208 return None, False
209
210 try:
211 from pylint.__pkginfo__ import numversion
212 if numversion < (0, 23, 0):
213 error = self.trUtf8("PyLint version < 0.23.0.")
214 return None, False
215 except ImportError:
216 error = self.trUtf8("Cannot determine pylint version.")
217 return None, False
218
219 menu = e5App().getObject("Project").getMenu("Checks")
220 if menu:
221 self.__projectAct = E5Action(self.trUtf8('Run PyLint'),
222 self.trUtf8('Run &PyLint...'), 0, 0,
223 self, 'project_check_pylint')
224 self.__projectAct.setStatusTip(
225 self.trUtf8('Check project, packages or modules with pylint.'))
226 self.__projectAct.setWhatsThis(self.trUtf8(
227 """<b>Run PyLint...</b>"""
228 """<p>This checks the project, packages or modules using pylint.</p>"""
229 ))
230 self.__projectAct.triggered[()].connect(self.__projectPylint)
231 e5App().getObject("Project").addE5Actions([self.__projectAct])
232 menu.addAction(self.__projectAct)
233
234 self.__projectShowAct = E5Action(self.trUtf8('Show PyLint Dialog'),
235 self.trUtf8('Show Py&Lint Dialog...'), 0, 0,
236 self, 'project_check_pylintshow')
237 self.__projectShowAct.setStatusTip(
238 self.trUtf8('Show the PyLint dialog with the results of the last run.'))
239 self.__projectShowAct.setWhatsThis(self.trUtf8(
240 """<b>Show PyLint Dialog...</b>"""
241 """<p>This shows the PyLint dialog with the results"""
242 """ of the last run.</p>"""
243 ))
244 self.__projectShowAct.triggered[()].connect(self.__projectPylintShow)
245 e5App().getObject("Project").addE5Actions([self.__projectShowAct])
246 menu.addAction(self.__projectShowAct)
247
248 self.__editorAct = E5Action(self.trUtf8('Run PyLint'),
249 self.trUtf8('Run &PyLint...'), 0, 0,
250 self, "")
251 self.__editorAct.setWhatsThis(self.trUtf8(
252 """<b>Run PyLint...</b>"""
253 """<p>This checks the loaded module using pylint.</p>"""
254 ))
255 self.__editorAct.triggered[()].connect(self.__editorPylint)
256
257 e5App().getObject("Project").showMenu.connect(self.__projectShowMenu)
258 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\
259 .showMenu.connect(self.__projectBrowserShowMenu)
260 e5App().getObject("ViewManager").editorOpenedEd.connect(self.__editorOpened)
261 e5App().getObject("ViewManager").editorClosedEd.connect(self.__editorClosed)
262
263 for editor in e5App().getObject("ViewManager").getOpenEditors():
264 self.__editorOpened(editor)
265
266 error = ""
267 return None, True
268
269 def deactivate(self):
270 """
271 Public method to deactivate this plugin.
272 """
273 e5App().getObject("Project").showMenu.disconnect(self.__projectShowMenu)
274 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\
275 .showMenu.disconnect(self.__projectBrowserShowMenu)
276 e5App().getObject("ViewManager").editorOpenedEd.disconnect(self.__editorOpened)
277 e5App().getObject("ViewManager").editorClosedEd.disconnect(self.__editorClosed)
278
279 menu = e5App().getObject("Project").getMenu("Checks")
280 if menu:
281 if self.__projectAct:
282 menu.removeAction(self.__projectAct)
283 e5App().getObject("Project").removeE5Actions([self.__projectAct])
284 if self.__projectShowAct:
285 menu.removeAction(self.__projectShowAct)
286 e5App().getObject("Project").removeE5Actions([self.__projectShowAct])
287
288 if self.__projectBrowserMenu:
289 if self.__projectBrowserAct:
290 self.__projectBrowserMenu.removeAction(self.__projectBrowserAct)
291 if self.__projectBrowserShowAct:
292 self.__projectBrowserMenu.removeAction(self.__projectBrowserShowAct)
293
294 for editor in self.__editors:
295 editor.showMenu.disconnect(self.__editorShowMenu)
296 menu = editor.getMenu("Checks")
297 if menu is not None:
298 menu.removeAction(self.__editorAct)
299
300 self.__initialize()
301
302 def __loadTranslator(self):
303 """
304 Private method to load the translation file.
305 """
306 if self.__ui is not None:
307 loc = self.__ui.getLocale()
308 if loc and loc != "C":
309 locale_dir = \
310 os.path.join(os.path.dirname(__file__), "PyLint", "i18n")
311 translation = "pylint_{0}".format(loc)
312 translator = QTranslator(None)
313 loaded = translator.load(translation, locale_dir)
314 if loaded:
315 self.__translator = translator
316 e5App().installTranslator(self.__translator)
317 else:
318 print("Warning: translation file '{0}' could not be loaded."\
319 .format(translation))
320 print("Using default.")
321
322 def __projectShowMenu(self, menuName, menu):
323 """
324 Private slot called, when the the project menu or a submenu is
325 about to be shown.
326
327 @param menuName name of the menu to be shown (string)
328 @param menu reference to the menu (QMenu)
329 """
330 if menuName == "Checks":
331 if self.__projectAct is not None:
332 self.__projectAct.setEnabled(
333 e5App().getObject("Project").getProjectLanguage() == "Python3")
334 if self.__projectShowAct is not None:
335 self.__projectShowAct.setEnabled(
336 e5App().getObject("Project").getProjectLanguage() == "Python3")
337 self.__projectShowAct.setEnabled(self.__pylintPDialog is not None)
338
339 def __projectBrowserShowMenu(self, menuName, menu):
340 """
341 Private slot called, when the the project browser menu or a submenu is
342 about to be shown.
343
344 @param menuName name of the menu to be shown (string)
345 @param menu reference to the menu (QMenu)
346 """
347 if menuName == "Checks" and \
348 e5App().getObject("Project").getProjectLanguage() == "Python3":
349 self.__projectBrowserMenu = menu
350 if self.__projectBrowserAct is None:
351 self.__projectBrowserAct = E5Action(self.trUtf8('Run PyLint'),
352 self.trUtf8('Run &PyLint...'), 0, 0,
353 self, '')
354 self.__projectBrowserAct.setWhatsThis(self.trUtf8(
355 """<b>Run PyLint...</b>"""
356 """<p>This checks the project, packages or modules"""
357 """ using pylint.</p>"""
358 ))
359 self.__projectBrowserAct.triggered[()].connect(
360 self.__projectBrowserPylint)
361
362 if self.__projectBrowserShowAct is None:
363 self.__projectBrowserShowAct = \
364 E5Action(self.trUtf8('Show PyLint Dialog'),
365 self.trUtf8('Show Py&Lint Dialog...'), 0, 0,
366 self, '')
367 self.__projectBrowserShowAct.setWhatsThis(self.trUtf8(
368 """<b>Show PyLint Dialog...</b>"""
369 """<p>This shows the PyLint dialog with the results"""
370 """ of the last run.</p>"""
371 ))
372 self.__projectBrowserShowAct.triggered[()].connect(
373 self.__projectBrowserPylintShow)
374
375 if not self.__projectBrowserAct in menu.actions():
376 menu.addAction(self.__projectBrowserAct)
377 if not self.__projectBrowserShowAct in menu.actions():
378 menu.addAction(self.__projectBrowserShowAct)
379 self.__projectBrowserShowAct.setEnabled(self.__pylintPsbDialog is not None)
380
381 def __pyLint(self, project, mpName, forProject, forEditor=False):
382 """
383 Private method used to perform a PyLint run.
384
385 @param project reference to the Project object
386 @param mpName name of module or package to be checked (string)
387 @param forProject flag indicating a run for the project (boolean)
388 """
389 if forEditor:
390 parms = copy.deepcopy(self.__editorParms)
391 else:
392 parms = project.getData('CHECKERSPARMS', "PYLINT")
393 exe = _findExecutable()
394 if exe is None:
395 E5MessageBox.critical(None,
396 self.trUtf8("pylint"),
397 self.trUtf8("""The pylint executable could not be found."""))
398 return
399
400 dlg = PyLintConfigDialog(project.getProjectPath(), exe, parms)
401 if dlg.exec_() == QDialog.Accepted:
402 args, parms = dlg.generateParameters()
403 self.__editorParms = copy.deepcopy(parms)
404 if not forEditor:
405 project.setData('CHECKERSPARMS', "PYLINT", parms)
406
407 # now do the call
408 dlg2 = PyLintExecDialog()
409 try:
410 reportFile = parms['reportFile']
411 except KeyError:
412 reportFile = None
413 res = dlg2.start(args, mpName, reportFile, project.getProjectPath())
414 if res:
415 dlg2.show()
416 if forProject:
417 self.__pylintPDialog = dlg2
418 elif forEditor:
419 self.__editorPylintDialog = dlg2
420 else:
421 self.__pylintPsbDialog = dlg2
422
423 def __projectPylint(self):
424 """
425 Public slot used to check the project files with Pylint.
426 """
427 project = e5App().getObject("Project")
428 project.saveAllScripts()
429 self.__pyLint(project, project.getProjectPath(), True)
430
431 def __projectPylintShow(self):
432 """
433 Public slot to show the PyLint dialog with the results of the last run.
434 """
435 if self.__pylintPDialog is not None:
436 self.__pylintPDialog.show()
437
438 def __projectBrowserPylint(self):
439 """
440 Private method to handle the Pylint context menu action of the project
441 sources browser.
442 """
443 project = e5App().getObject("Project")
444 browser = e5App().getObject("ProjectBrowser").getProjectBrowser("sources")
445 itm = browser.model().item(browser.currentIndex())
446 try:
447 fn = itm.fileName()
448 except AttributeError:
449 fn = itm.dirName()
450 self.__pyLint(project, fn, False)
451
452 def __projectBrowserPylintShow(self):
453 """
454 Public slot to show the PyLint dialog with the results of the last run.
455 """
456 if self.__pylintPsbDialog is not None:
457 self.__pylintPsbDialog.show()
458
459 def __editorOpened(self, editor):
460 """
461 Private slot called, when a new editor was opened.
462
463 @param editor reference to the new editor (QScintilla.Editor)
464 """
465 menu = editor.getMenu("Checks")
466 if menu is not None:
467 menu.addAction(self.__editorAct)
468 editor.showMenu.connect(self.__editorShowMenu)
469 self.__editors.append(editor)
470
471 def __editorClosed(self, editor):
472 """
473 Private slot called, when an editor was closed.
474
475 @param editor reference to the editor (QScintilla.Editor)
476 """
477 try:
478 self.__editors.remove(editor)
479 except ValueError:
480 pass
481
482 def __editorShowMenu(self, menuName, menu, editor):
483 """
484 Private slot called, when the the editor context menu or a submenu is
485 about to be shown.
486
487 @param menuName name of the menu to be shown (string)
488 @param menu reference to the menu (QMenu)
489 @param editor reference to the editor (QScintilla.Editor)
490 """
491 if menuName == "Checks":
492 if not self.__editorAct in menu.actions():
493 menu.addAction(self.__editorAct)
494 self.__editorAct.setEnabled(editor.isPy3File())
495
496 def __editorPylint(self):
497 """
498 Private slot to handle the Pylint context menu action of the editors.
499 """
500 editor = e5App().getObject("ViewManager").activeWindow()
501 if editor is not None:
502 if not editor.checkDirty():
503 return
504
505 fn = editor.getFileName()
506 project = e5App().getObject("Project")
507 self.__pyLint(project, fn, False, True)

eric ide

mercurial