32 className = "RadonMetricsPlugin" |
32 className = "RadonMetricsPlugin" |
33 packageName = "RadonMetrics" |
33 packageName = "RadonMetrics" |
34 shortDescription = "Code metrics plugin using radon package" |
34 shortDescription = "Code metrics plugin using radon package" |
35 longDescription = ( |
35 longDescription = ( |
36 """This plug-in implements dialogs to show various code metrics. These""" |
36 """This plug-in implements dialogs to show various code metrics. These""" |
37 """ are determined using the radon code metrics package.""" |
37 """ are determined using the radon code metrics package. 'Raw code""" |
|
38 """ metrics', 'Maintainability Index' and 'McCabe Complexity' can be""" |
|
39 """ requested through different dialogs for one file or the whole""" |
|
40 """ project.""" |
38 ) |
41 ) |
39 needsRestart = False |
42 needsRestart = False |
40 pyqtApi = 2 |
43 pyqtApi = 2 |
41 python2Compatible = True |
44 python2Compatible = True |
42 # End-Of-Header |
45 # End-Of-Header |
108 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3( |
111 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3( |
109 "mi", fx, lang, fn, msg), |
112 "mi", fx, lang, fn, msg), |
110 onBatchDone=lambda fx, lang: self.batchJobDone( |
113 onBatchDone=lambda fx, lang: self.batchJobDone( |
111 "mi", fx, lang)) |
114 "mi", fx, lang)) |
112 |
115 |
|
116 # cyclomatic complexity |
|
117 self.backgroundService.serviceConnect( |
|
118 'radon_cc', 'Python2', path, 'CyclomaticComplexityCalculator', |
|
119 lambda fn, res: self.metricsCalculationDone("cc", fn, res), |
|
120 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy2( |
|
121 "cc", fx, lang, fn, msg), |
|
122 onBatchDone=lambda fx, lang: self.batchJobDone( |
|
123 "c", fx, lang)) |
|
124 self.backgroundService.serviceConnect( |
|
125 'radon_cc', 'Python3', path, 'CyclomaticComplexityCalculator', |
|
126 lambda fn, res: self.metricsCalculationDone("cc", fn, res), |
|
127 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3( |
|
128 "cc", fx, lang, fn, msg), |
|
129 onBatchDone=lambda fx, lang: self.batchJobDone( |
|
130 "cc", fx, lang)) |
|
131 |
113 self.hasBatch = True |
132 self.hasBatch = True |
114 except TypeError: |
133 except TypeError: |
115 # backward compatibility for eric 6.0 |
134 # backward compatibility for eric 6.0 |
116 # raw code metrics calculation |
135 # raw code metrics calculation |
117 self.backgroundService.serviceConnect( |
136 self.backgroundService.serviceConnect( |
134 self.backgroundService.serviceConnect( |
153 self.backgroundService.serviceConnect( |
135 'radon_mi', 'Python3', path, 'MaintainabilityIndexCalculator', |
154 'radon_mi', 'Python3', path, 'MaintainabilityIndexCalculator', |
136 lambda fn, res: self.metricsCalculationDone("mi", fn, res), |
155 lambda fn, res: self.metricsCalculationDone("mi", fn, res), |
137 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3( |
156 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3( |
138 "mi", fx, lang, fn, msg)) |
157 "mi", fx, lang, fn, msg)) |
|
158 |
|
159 # cyclomatic complexity |
|
160 self.backgroundService.serviceConnect( |
|
161 'radon_cc', 'Python2', path, 'CyclomaticComplexityCalculator', |
|
162 lambda fn, res: self.metricsCalculationDone("cc", fn, res), |
|
163 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy2( |
|
164 "cc", fx, lang, fn, msg)) |
|
165 self.backgroundService.serviceConnect( |
|
166 'radon_cc', 'Python3', path, 'CyclomaticComplexityCalculator', |
|
167 lambda fn, res: self.metricsCalculationDone("cc", fn, res), |
|
168 onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3( |
|
169 "cc", fx, lang, fn, msg)) |
139 |
170 |
140 self.hasBatch = False |
171 self.hasBatch = False |
141 |
172 |
142 self.queuedBatches = { |
173 self.queuedBatches = { |
143 "raw": [], |
174 "raw": [], |
261 """ |
292 """ |
262 Private slot to (re)initialize the plugin. |
293 Private slot to (re)initialize the plugin. |
263 """ |
294 """ |
264 self.__projectRawMetricsDialog = None |
295 self.__projectRawMetricsDialog = None |
265 self.__projectMIDialog = None |
296 self.__projectMIDialog = None |
|
297 self.__projectCCDialog = None |
266 self.__projectMetricsActs = [] |
298 self.__projectMetricsActs = [] |
267 self.__projectSeparatorActs = [] |
299 self.__projectSeparatorActs = [] |
268 |
300 |
269 self.__projectBrowserRawMetricsDialog = None |
301 self.__projectBrowserRawMetricsDialog = None |
270 self.__projectBrowserMIDialog = None |
302 self.__projectBrowserMIDialog = None |
|
303 self.__projectBrowserCCDialog = None |
271 self.__projectBrowserMenu = None |
304 self.__projectBrowserMenu = None |
272 self.__projectBrowserMetricsActs = [] |
305 self.__projectBrowserMetricsActs = [] |
273 self.__projectBrowserSeparatorActs = [] |
306 self.__projectBrowserSeparatorActs = [] |
274 |
307 |
275 self.__editors = [] |
308 self.__editors = [] |
276 self.__editorRawMetricsDialog = None |
309 self.__editorRawMetricsDialog = None |
277 self.__editorMIDialog = None |
310 self.__editorMIDialog = None |
|
311 self.__editorCCDialog = None |
278 self.__editorMetricsActs = [] |
312 self.__editorMetricsActs = [] |
279 self.__editorSeparatorActs = [] |
313 self.__editorSeparatorActs = [] |
280 |
314 |
281 def rawMetrics(self, lang, filename, source): |
315 def rawMetrics(self, lang, filename, source): |
282 """ |
316 """ |
388 Public method to cancel all batch jobs. |
422 Public method to cancel all batch jobs. |
389 """ |
423 """ |
390 for lang in ['Python2', 'Python3']: |
424 for lang in ['Python2', 'Python3']: |
391 self.backgroundService.requestCancel('batch_radon_mi', lang) |
425 self.backgroundService.requestCancel('batch_radon_mi', lang) |
392 |
426 |
|
427 def cyclomaticComplexity(self, lang, filename, source): |
|
428 """ |
|
429 Public method to prepare cyclomatic complexity calculation on one |
|
430 Python source file. |
|
431 |
|
432 @param lang language of the file or None to determine by internal |
|
433 algorithm |
|
434 @type str or None |
|
435 @param filename source filename |
|
436 @type str |
|
437 @param source string containing the code |
|
438 @type str |
|
439 """ |
|
440 if lang is None: |
|
441 lang = 'Python{0}'.format(determinePythonVersion(filename, source)) |
|
442 if lang not in ['Python2', 'Python3']: |
|
443 return |
|
444 |
|
445 self.backgroundService.enqueueRequest( |
|
446 'radon_cc', lang, filename, [source]) |
|
447 |
|
448 def cyclomaticComplexityBatch(self, argumentsList): |
|
449 """ |
|
450 Public method to prepare cyclomatic complexity calculation on multiple |
|
451 Python source files. |
|
452 |
|
453 @param argumentsList list of arguments tuples with each tuple |
|
454 containing filename and source |
|
455 @type (str, str) |
|
456 """ |
|
457 data = { |
|
458 "Python2": [], |
|
459 "Python3": [], |
|
460 } |
|
461 for filename, source in argumentsList: |
|
462 lang = 'Python{0}'.format(determinePythonVersion(filename, source)) |
|
463 if lang not in ['Python2', 'Python3']: |
|
464 continue |
|
465 else: |
|
466 data[lang].append((filename, source)) |
|
467 |
|
468 self.queuedBatches["raw"] = [] |
|
469 for lang in ['Python2', 'Python3']: |
|
470 if data[lang]: |
|
471 self.queuedBatches["cc"].append(lang) |
|
472 self.backgroundService.enqueueRequest('batch_radon_cc', lang, |
|
473 "", data[lang]) |
|
474 self.batchesFinished["cc"] = False |
|
475 |
|
476 def cancelComplexityBatch(self): |
|
477 """ |
|
478 Public method to cancel all batch jobs. |
|
479 """ |
|
480 for lang in ['Python2', 'Python3']: |
|
481 self.backgroundService.requestCancel('batch_radon_cc', lang) |
|
482 |
393 def activate(self): |
483 def activate(self): |
394 """ |
484 """ |
395 Public method to activate this plug-in. |
485 Public method to activate this plug-in. |
396 |
486 |
397 @return tuple of None and activation status |
487 @return tuple of None and activation status |
446 )) |
536 )) |
447 act.triggered.connect(self.__projectMaintainabilityIndex) |
537 act.triggered.connect(self.__projectMaintainabilityIndex) |
448 menu.addAction(act) |
538 menu.addAction(act) |
449 self.__projectMetricsActs.append(act) |
539 self.__projectMetricsActs.append(act) |
450 |
540 |
|
541 act = E5Action( |
|
542 self.tr('Cyclomatic Complexity'), |
|
543 self.tr('Cyclomatic &Complexity...'), 0, 0, |
|
544 self, 'project_show_radon_cc') |
|
545 act.setStatusTip( |
|
546 self.tr('Show the cyclomatic complexity for Python files.')) |
|
547 act.setWhatsThis(self.tr( |
|
548 """<b>Cyclomatic Complexity...</b>""" |
|
549 """<p>This calculates the cyclomatic complexity of Python""" |
|
550 """ files and shows it together with a ranking.</p>""" |
|
551 )) |
|
552 act.triggered.connect(self.__projectCyclomaticComplexity) |
|
553 menu.addAction(act) |
|
554 self.__projectMetricsActs.append(act) |
|
555 |
451 act = menu.addSeparator() |
556 act = menu.addSeparator() |
452 self.__projectSeparatorActs.append(act) |
557 self.__projectSeparatorActs.append(act) |
453 |
558 |
454 e5App().getObject("Project").addE5Actions( |
559 e5App().getObject("Project").addE5Actions( |
455 self.__projectMetricsActs[1:]) |
560 self.__projectMetricsActs[1:]) |
466 act = QAction(self.tr("Radon"), self) |
571 act = QAction(self.tr("Radon"), self) |
467 font = act.font() |
572 font = act.font() |
468 font.setBold(True) |
573 font.setBold(True) |
469 act.setFont(font) |
574 act.setFont(font) |
470 act.triggered.connect(self.__showRadonVersion) |
575 act.triggered.connect(self.__showRadonVersion) |
471 menu.addAction(act) |
|
472 self.__editorMetricsActs.append(act) |
576 self.__editorMetricsActs.append(act) |
473 |
577 |
474 act = E5Action( |
578 act = E5Action( |
475 self.tr('Code Metrics'), |
579 self.tr('Code Metrics'), |
476 self.tr('Code &Metrics...'), 0, 0, |
580 self.tr('Code &Metrics...'), 0, 0, |
497 """<b>Maintainability Index...</b>""" |
601 """<b>Maintainability Index...</b>""" |
498 """<p>This calculates the maintainability index of Python""" |
602 """<p>This calculates the maintainability index of Python""" |
499 """ files and shows it together with a ranking.</p>""" |
603 """ files and shows it together with a ranking.</p>""" |
500 )) |
604 )) |
501 act.triggered.connect(self.__editorMaintainabilityIndex) |
605 act.triggered.connect(self.__editorMaintainabilityIndex) |
|
606 self.__editorMetricsActs.append(act) |
|
607 |
|
608 act = E5Action( |
|
609 self.tr('Cyclomatic Complexity'), |
|
610 self.tr('Cyclomatic &Complexity...'), 0, 0, |
|
611 self, '') |
|
612 act.setStatusTip( |
|
613 self.tr('Show the cyclomatic complexity for Python files.')) |
|
614 act.setWhatsThis(self.tr( |
|
615 """<b>Cyclomatic Complexity...</b>""" |
|
616 """<p>This calculates the cyclomatic complexity of Python""" |
|
617 """ files and shows it together with a ranking.</p>""" |
|
618 )) |
|
619 act.triggered.connect(self.__editorCyclomaticComplexity) |
502 self.__editorMetricsActs.append(act) |
620 self.__editorMetricsActs.append(act) |
503 |
621 |
504 e5App().getObject("Project").showMenu.connect(self.__projectShowMenu) |
622 e5App().getObject("Project").showMenu.connect(self.__projectShowMenu) |
505 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\ |
623 e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\ |
506 .showMenu.connect(self.__projectBrowserShowMenu) |
624 .showMenu.connect(self.__projectBrowserShowMenu) |
616 self.__projectBrowserMetricsActs.append(act) |
734 self.__projectBrowserMetricsActs.append(act) |
617 |
735 |
618 act = E5Action( |
736 act = E5Action( |
619 self.tr('Code Metrics'), |
737 self.tr('Code Metrics'), |
620 self.tr('Code &Metrics...'), 0, 0, |
738 self.tr('Code &Metrics...'), 0, 0, |
621 self, '') |
739 self, '') |
622 act.setStatusTip( |
740 act.setStatusTip(self.tr( |
623 self.tr('Show raw code metrics.')) |
741 'Show raw code metrics.')) |
624 act.setWhatsThis(self.tr( |
742 act.setWhatsThis(self.tr( |
625 """<b>Code Metrics...</b>""" |
743 """<b>Code Metrics...</b>""" |
626 """<p>This calculates raw code metrics of Python files""" |
744 """<p>This calculates raw code metrics of Python files""" |
627 """ and shows the amount of lines of code, logical lines""" |
745 """ and shows the amount of lines of code, logical lines""" |
628 """ of code, source lines of code, comment lines,""" |
746 """ of code, source lines of code, comment lines,""" |
633 self.__projectBrowserMetricsActs.append(act) |
751 self.__projectBrowserMetricsActs.append(act) |
634 |
752 |
635 act = E5Action( |
753 act = E5Action( |
636 self.tr('Maintainability Index'), |
754 self.tr('Maintainability Index'), |
637 self.tr('Maintainability &Index...'), 0, 0, |
755 self.tr('Maintainability &Index...'), 0, 0, |
638 self, 'project_show_radon_mi') |
756 self, '') |
639 act.setStatusTip( |
757 act.setStatusTip(self.tr( |
640 self.tr('Show the maintainability index for Python' |
758 'Show the maintainability index for Python files.')) |
641 ' files.')) |
|
642 act.setWhatsThis(self.tr( |
759 act.setWhatsThis(self.tr( |
643 """<b>Maintainability Index...</b>""" |
760 """<b>Maintainability Index...</b>""" |
644 """<p>This calculates the maintainability index of""" |
761 """<p>This calculates the maintainability index of""" |
645 """ Python files and shows it together with a ranking.""" |
762 """ Python files and shows it together with a ranking.""" |
646 """</p>""" |
763 """</p>""" |
647 )) |
764 )) |
648 act.triggered.connect( |
765 act.triggered.connect( |
649 self.__projectBrowserMaintainabilityIndex) |
766 self.__projectBrowserMaintainabilityIndex) |
|
767 menu.addAction(act) |
|
768 self.__projectBrowserMetricsActs.append(act) |
|
769 |
|
770 act = E5Action( |
|
771 self.tr('Cyclomatic Complexity'), |
|
772 self.tr('Cyclomatic &Complexity...'), 0, 0, |
|
773 self, '') |
|
774 act.setStatusTip(self.tr( |
|
775 'Show the cyclomatic complexity for Python files.')) |
|
776 act.setWhatsThis(self.tr( |
|
777 """<b>Cyclomatic Complexity...</b>""" |
|
778 """<p>This calculates the cyclomatic complexity of""" |
|
779 """ Python files and shows it together with a ranking.""" |
|
780 """</p>""" |
|
781 )) |
|
782 act.triggered.connect( |
|
783 self.__projectBrowserCyclomaticComplexity) |
650 menu.addAction(act) |
784 menu.addAction(act) |
651 self.__projectBrowserMetricsActs.append(act) |
785 self.__projectBrowserMetricsActs.append(act) |
652 |
786 |
653 act = menu.addSeparator() |
787 act = menu.addSeparator() |
654 self.__projectBrowserSeparatorActs.append(act) |
788 self.__projectBrowserSeparatorActs.append(act) |
810 from RadonMetrics.MaintainabilityIndexDialog import \ |
944 from RadonMetrics.MaintainabilityIndexDialog import \ |
811 MaintainabilityIndexDialog |
945 MaintainabilityIndexDialog |
812 self.__editorMIDialog = MaintainabilityIndexDialog(self) |
946 self.__editorMIDialog = MaintainabilityIndexDialog(self) |
813 self.__editorMIDialog.show() |
947 self.__editorMIDialog.show() |
814 self.__editorMIDialog.start(editor.getFileName()) |
948 self.__editorMIDialog.start(editor.getFileName()) |
|
949 |
|
950 ################################################################## |
|
951 ## Cyclomatic complexity calculations |
|
952 ################################################################## |
|
953 |
|
954 def __projectCyclomaticComplexity(self): |
|
955 """ |
|
956 Private slot used to calculate the cyclomatic complexity for the |
|
957 project. |
|
958 """ |
|
959 project = e5App().getObject("Project") |
|
960 project.saveAllScripts() |
|
961 ppath = project.getProjectPath() |
|
962 files = [os.path.join(ppath, file) |
|
963 for file in project.pdata["SOURCES"] |
|
964 if file.endswith( |
|
965 tuple(Preferences.getPython("Python3Extensions")) + |
|
966 tuple(Preferences.getPython("PythonExtensions")))] |
|
967 |
|
968 if self.__projectCCDialog is None: |
|
969 from RadonMetrics.CyclomaticComplexityDialog import \ |
|
970 CyclomaticComplexityDialog |
|
971 self.__projectCCDialog = CyclomaticComplexityDialog(self) |
|
972 self.__projectCCDialog.show() |
|
973 self.__projectCCDialog.prepare(files, project) |
|
974 |
|
975 def __projectBrowserCyclomaticComplexity(self): |
|
976 """ |
|
977 Private method to handle the cyclomatic complexity context menu action |
|
978 of the project sources browser. |
|
979 """ |
|
980 browser = e5App().getObject("ProjectBrowser").getProjectBrowser( |
|
981 "sources") |
|
982 if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1: |
|
983 fn = [] |
|
984 for itm in browser.getSelectedItems([ProjectBrowserFileItem]): |
|
985 fn.append(itm.fileName()) |
|
986 else: |
|
987 itm = browser.model().item(browser.currentIndex()) |
|
988 try: |
|
989 fn = itm.fileName() |
|
990 except AttributeError: |
|
991 fn = itm.dirName() |
|
992 |
|
993 if self.__projectBrowserCCDialog is None: |
|
994 from RadonMetrics.CyclomaticComplexityDialog import \ |
|
995 CyclomaticComplexityDialog |
|
996 self.__projectBrowserCCDialog = CyclomaticComplexityDialog(self) |
|
997 self.__projectBrowserCCDialog.show() |
|
998 self.__projectBrowserCCDialog.start(fn) |
|
999 |
|
1000 def __editorCyclomaticComplexity(self): |
|
1001 """ |
|
1002 Private slot to handle the cyclomatic complexity action of the editor |
|
1003 show menu. |
|
1004 """ |
|
1005 editor = e5App().getObject("ViewManager").activeWindow() |
|
1006 if editor is not None: |
|
1007 if editor.checkDirty() and editor.getFileName() is not None: |
|
1008 if self.__editorCCDialog is None: |
|
1009 from RadonMetrics.CyclomaticComplexityDialog import \ |
|
1010 CyclomaticComplexityDialog |
|
1011 self.__editorCCDialog = CyclomaticComplexityDialog(self) |
|
1012 self.__editorCCDialog.show() |
|
1013 self.__editorCCDialog.start(editor.getFileName()) |
|
1014 |
|
1015 ################################################################## |
|
1016 ## Radon info display |
|
1017 ################################################################## |
815 |
1018 |
816 def __showRadonVersion(self): |
1019 def __showRadonVersion(self): |
817 """ |
1020 """ |
818 Private slot to show the version number of the used radon library. |
1021 Private slot to show the version number of the used radon library. |
819 """ |
1022 """ |