RadonMetrics/CyclomaticComplexityDialog.py

branch
eric7
changeset 94
725eaca7bc4b
parent 93
1ae73306422a
child 102
f7b964ea22a1
equal deleted inserted replaced
93:1ae73306422a 94:725eaca7bc4b
12 import fnmatch 12 import fnmatch
13 13
14 from PyQt6.QtCore import pyqtSlot, Qt, QTimer, QLocale 14 from PyQt6.QtCore import pyqtSlot, Qt, QTimer, QLocale
15 from PyQt6.QtGui import QColor 15 from PyQt6.QtGui import QColor
16 from PyQt6.QtWidgets import ( 16 from PyQt6.QtWidgets import (
17 QDialog, QDialogButtonBox, QAbstractButton, QHeaderView, QTreeWidgetItem, 17 QDialog,
18 QApplication, QMenu 18 QDialogButtonBox,
19 QAbstractButton,
20 QHeaderView,
21 QTreeWidgetItem,
22 QApplication,
23 QMenu,
19 ) 24 )
20 25
21 from .Ui_CyclomaticComplexityDialog import Ui_CyclomaticComplexityDialog 26 from .Ui_CyclomaticComplexityDialog import Ui_CyclomaticComplexityDialog
22 from EricWidgets.EricApplication import ericApp 27 from EricWidgets.EricApplication import ericApp
23 28
28 class CyclomaticComplexityDialog(QDialog, Ui_CyclomaticComplexityDialog): 33 class CyclomaticComplexityDialog(QDialog, Ui_CyclomaticComplexityDialog):
29 """ 34 """
30 Class implementing a dialog to show the cyclomatic complexity (McCabe 35 Class implementing a dialog to show the cyclomatic complexity (McCabe
31 complexity). 36 complexity).
32 """ 37 """
38
33 FilePathRole = Qt.ItemDataRole.UserRole + 1 39 FilePathRole = Qt.ItemDataRole.UserRole + 1
34 LineNumberRole = Qt.ItemDataRole.UserRole + 2 40 LineNumberRole = Qt.ItemDataRole.UserRole + 2
35 41
36 def __init__(self, radonService, isSingle=False, parent=None): 42 def __init__(self, radonService, isSingle=False, parent=None):
37 """ 43 """
38 Constructor 44 Constructor
39 45
40 @param radonService reference to the service 46 @param radonService reference to the service
41 @type RadonMetricsPlugin 47 @type RadonMetricsPlugin
42 @param isSingle flag indicating a single file dialog 48 @param isSingle flag indicating a single file dialog
43 @type bool 49 @type bool
44 @param parent reference to the parent widget 50 @param parent reference to the parent widget
45 @type QWidget 51 @type QWidget
46 """ 52 """
47 super().__init__(parent) 53 super().__init__(parent)
48 self.setupUi(self) 54 self.setupUi(self)
49 self.setWindowFlags(Qt.WindowType.Window) 55 self.setWindowFlags(Qt.WindowType.Window)
50 56
51 self.buttonBox.button( 57 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
52 QDialogButtonBox.StandardButton.Close).setEnabled(False) 58 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
53 self.buttonBox.button( 59
54 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
55
56 self.resultList.headerItem().setText(self.resultList.columnCount(), "") 60 self.resultList.headerItem().setText(self.resultList.columnCount(), "")
57 61
58 self.rankComboBox.addItems(["A", "B", "C", "D", "E", "F"]) 62 self.rankComboBox.addItems(["A", "B", "C", "D", "E", "F"])
59 self.rankComboBox.setCurrentIndex(self.rankComboBox.findText("D")) 63 self.rankComboBox.setCurrentIndex(self.rankComboBox.findText("D"))
60 self.__minimumRank = "D" 64 self.__minimumRank = "D"
61 65
62 self.radonService = radonService 66 self.radonService = radonService
63 self.radonService.complexityDone.connect(self.__processResult) 67 self.radonService.complexityDone.connect(self.__processResult)
64 self.radonService.error.connect(self.__processError) 68 self.radonService.error.connect(self.__processError)
65 self.radonService.batchFinished.connect(self.__batchFinished) 69 self.radonService.batchFinished.connect(self.__batchFinished)
66 70
67 self.__isSingle = isSingle 71 self.__isSingle = isSingle
68 self.cancelled = False 72 self.cancelled = False
69 73
70 self.__project = ericApp().getObject("Project") 74 self.__project = ericApp().getObject("Project")
71 self.__locale = QLocale() 75 self.__locale = QLocale()
72 self.__finished = True 76 self.__finished = True
73 self.__errorItem = None 77 self.__errorItem = None
74 78
75 self.__fileList = [] 79 self.__fileList = []
76 self.filterFrame.setVisible(False) 80 self.filterFrame.setVisible(False)
77 81
78 self.explanationLabel.setText(self.tr( 82 self.explanationLabel.setText(
79 "<table>" 83 self.tr(
80 "<tr><td colspan=3><b>Ranking:</b></td></tr>" 84 "<table>"
81 "<tr><td><b>A</b></td><td>1 - 5</td>" 85 "<tr><td colspan=3><b>Ranking:</b></td></tr>"
82 "<td>(low risk - simple block)</td></tr>" 86 "<tr><td><b>A</b></td><td>1 - 5</td>"
83 "<tr><td><b>B</b></td><td>6 - 10</td>" 87 "<td>(low risk - simple block)</td></tr>"
84 "<td>(low risk - well structured and stable block)</td></tr>" 88 "<tr><td><b>B</b></td><td>6 - 10</td>"
85 "<tr><td><b>C</b></td><td>11 - 20</td>" 89 "<td>(low risk - well structured and stable block)</td></tr>"
86 "<td>(moderate risk - slightly complex block)</td></tr>" 90 "<tr><td><b>C</b></td><td>11 - 20</td>"
87 "<tr><td><b>D</b></td><td>21 - 30</td>" 91 "<td>(moderate risk - slightly complex block)</td></tr>"
88 "<td>(more than moderate risk - more complex block)</td></tr>" 92 "<tr><td><b>D</b></td><td>21 - 30</td>"
89 "<tr><td><b>E</b></td><td>31 - 40</td>" 93 "<td>(more than moderate risk - more complex block)</td></tr>"
90 "<td>(high risk - complex block, alarming)</td></tr>" 94 "<tr><td><b>E</b></td><td>31 - 40</td>"
91 "<tr><td><b>F</b></td><td>&gt; 40</td>" 95 "<td>(high risk - complex block, alarming)</td></tr>"
92 "<td>(very high risk - error-prone, unstable block)</td></tr>" 96 "<tr><td><b>F</b></td><td>&gt; 40</td>"
93 "</table>" 97 "<td>(very high risk - error-prone, unstable block)</td></tr>"
94 )) 98 "</table>"
95 self.typeLabel.setText(self.tr( 99 )
96 "<table>" 100 )
97 "<tr><td colspan=2><b>Type:</b></td></tr>" 101 self.typeLabel.setText(
98 "<tr><td><b>C</b></td><td>Class</td></tr>" 102 self.tr(
99 "<tr><td><b>F</b></td><td>Function</td></tr>" 103 "<table>"
100 "<tr><td><b>M</b></td><td>Method</td></tr>" 104 "<tr><td colspan=2><b>Type:</b></td></tr>"
101 "</table>" 105 "<tr><td><b>C</b></td><td>Class</td></tr>"
102 )) 106 "<tr><td><b>F</b></td><td>Function</td></tr>"
103 107 "<tr><td><b>M</b></td><td>Method</td></tr>"
108 "</table>"
109 )
110 )
111
104 self.__mappedType = { 112 self.__mappedType = {
105 "class": "C", 113 "class": "C",
106 "function": "F", 114 "function": "F",
107 "method": "M", 115 "method": "M",
108 } 116 }
109 117
110 try: 118 try:
111 usesDarkPalette = ericApp().usesDarkPalette() 119 usesDarkPalette = ericApp().usesDarkPalette()
112 except AttributeError: 120 except AttributeError:
113 from PyQt6.QtGui import QPalette 121 from PyQt6.QtGui import QPalette
122
114 palette = ericApp().palette() 123 palette = ericApp().palette()
115 lightness = palette.color(QPalette.Window).lightness() 124 lightness = palette.color(QPalette.Window).lightness()
116 usesDarkPalette = lightness <= 128 125 usesDarkPalette = lightness <= 128
117 if usesDarkPalette: 126 if usesDarkPalette:
118 self.__typeColors = { 127 self.__typeColors = {
140 "C": QColor("#ffff00"), 149 "C": QColor("#ffff00"),
141 "D": QColor("#ffff00"), 150 "D": QColor("#ffff00"),
142 "E": QColor("#ff0000"), 151 "E": QColor("#ff0000"),
143 "F": QColor("#ff0000"), 152 "F": QColor("#ff0000"),
144 } 153 }
145 154
146 self.__menu = QMenu(self) 155 self.__menu = QMenu(self)
147 self.__menu.addAction(self.tr("Collapse all"), 156 self.__menu.addAction(self.tr("Collapse all"), self.__resultCollapse)
148 self.__resultCollapse)
149 self.__menu.addAction(self.tr("Expand all"), self.__resultExpand) 157 self.__menu.addAction(self.tr("Expand all"), self.__resultExpand)
150 self.resultList.setContextMenuPolicy( 158 self.resultList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
151 Qt.ContextMenuPolicy.CustomContextMenu) 159 self.resultList.customContextMenuRequested.connect(self.__showContextMenu)
152 self.resultList.customContextMenuRequested.connect( 160
153 self.__showContextMenu)
154
155 def __resizeResultColumns(self): 161 def __resizeResultColumns(self):
156 """ 162 """
157 Private method to resize the list columns. 163 Private method to resize the list columns.
158 """ 164 """
159 self.resultList.header().resizeSections( 165 self.resultList.header().resizeSections(QHeaderView.ResizeMode.ResizeToContents)
160 QHeaderView.ResizeMode.ResizeToContents)
161 self.resultList.header().setStretchLastSection(True) 166 self.resultList.header().setStretchLastSection(True)
162 167
163 def __createFileItem(self, filename): 168 def __createFileItem(self, filename):
164 """ 169 """
165 Private method to create a new file item in the result list. 170 Private method to create a new file item in the result list.
166 171
167 @param filename name of the file 172 @param filename name of the file
168 @type str 173 @type str
169 @return reference to the created item 174 @return reference to the created item
170 @rtype QTreeWidgetItem 175 @rtype QTreeWidgetItem
171 """ 176 """
172 itm = QTreeWidgetItem( 177 itm = QTreeWidgetItem([self.__project.getRelativePath(filename)])
173 [self.__project.getRelativePath(filename)])
174 itm.setData(0, self.FilePathRole, filename) 178 itm.setData(0, self.FilePathRole, filename)
175 itm.setData(0, self.LineNumberRole, 1) 179 itm.setData(0, self.LineNumberRole, 1)
176 return itm 180 return itm
177 181
178 def __createResultItem(self, parentItem, values): 182 def __createResultItem(self, parentItem, values):
179 """ 183 """
180 Private slot to create a new item in the result list. 184 Private slot to create a new item in the result list.
181 185
182 @param parentItem reference to the parent item 186 @param parentItem reference to the parent item
183 @type QTreeWidgetItem 187 @type QTreeWidgetItem
184 @param values values to be displayed 188 @param values values to be displayed
185 @type dict 189 @type dict
186 """ 190 """
187 if values["rank"] >= self.__minimumRank: 191 if values["rank"] >= self.__minimumRank:
188 itm = QTreeWidgetItem(parentItem, [ 192 itm = QTreeWidgetItem(
189 self.__mappedType[values["type"]], 193 parentItem,
190 values["fullname"], 194 [
191 "{0:3}".format(values["complexity"]), 195 self.__mappedType[values["type"]],
192 values["rank"], 196 values["fullname"],
193 "{0:6}".format(values["lineno"]), 197 "{0:3}".format(values["complexity"]),
194 ]) 198 values["rank"],
199 "{0:6}".format(values["lineno"]),
200 ],
201 )
195 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight) 202 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignRight)
196 itm.setTextAlignment(3, Qt.AlignmentFlag.AlignHCenter) 203 itm.setTextAlignment(3, Qt.AlignmentFlag.AlignHCenter)
197 itm.setTextAlignment(4, Qt.AlignmentFlag.AlignRight) 204 itm.setTextAlignment(4, Qt.AlignmentFlag.AlignRight)
198 if values["rank"] in self.__rankColors: 205 if values["rank"] in self.__rankColors:
199 itm.setBackground(3, self.__rankColors[values["rank"]]) 206 itm.setBackground(3, self.__rankColors[values["rank"]])
200 if values["type"] in self.__typeColors: 207 if values["type"] in self.__typeColors:
201 itm.setForeground(0, self.__typeColors[values["type"]]) 208 itm.setForeground(0, self.__typeColors[values["type"]])
202 itm.setData(0, self.FilePathRole, 209 itm.setData(0, self.FilePathRole, parentItem.data(0, self.FilePathRole))
203 parentItem.data(0, self.FilePathRole))
204 itm.setData(0, self.LineNumberRole, values["lineno"]) 210 itm.setData(0, self.LineNumberRole, values["lineno"])
205 211
206 if "methods" in values: 212 if "methods" in values:
207 for method in values["methods"]: 213 for method in values["methods"]:
208 self.__createResultItem(parentItem, method) 214 self.__createResultItem(parentItem, method)
209 215
210 if "closures" in values and values["closures"]: 216 if "closures" in values and values["closures"]:
211 for closure in values["closures"]: 217 for closure in values["closures"]:
212 self.__createResultItem(parentItem, closure) 218 self.__createResultItem(parentItem, closure)
213 219
214 def __createErrorItem(self, filename, message): 220 def __createErrorItem(self, filename, message):
215 """ 221 """
216 Private slot to create a new error item in the result list. 222 Private slot to create a new error item in the result list.
217 223
218 @param filename name of the file 224 @param filename name of the file
219 @type str 225 @type str
220 @param message error message 226 @param message error message
221 @type str 227 @type str
222 """ 228 """
223 if self.__errorItem is None: 229 if self.__errorItem is None:
224 self.__errorItem = QTreeWidgetItem(self.resultList, [ 230 self.__errorItem = QTreeWidgetItem(self.resultList, [self.tr("Errors")])
225 self.tr("Errors")])
226 self.__errorItem.setExpanded(True) 231 self.__errorItem.setExpanded(True)
227 self.__errorItem.setForeground(0, Qt.GlobalColor.red) 232 self.__errorItem.setForeground(0, Qt.GlobalColor.red)
228 233
229 msg = "{0} ({1})".format(self.__project.getRelativePath(filename), 234 msg = "{0} ({1})".format(self.__project.getRelativePath(filename), message)
230 message)
231 if not self.resultList.findItems(msg, Qt.MatchFlag.MatchExactly): 235 if not self.resultList.findItems(msg, Qt.MatchFlag.MatchExactly):
232 itm = QTreeWidgetItem(self.__errorItem, [msg]) 236 itm = QTreeWidgetItem(self.__errorItem, [msg])
233 itm.setForeground(0, Qt.GlobalColor.red) 237 itm.setForeground(0, Qt.GlobalColor.red)
234 itm.setFirstColumnSpanned(True) 238 itm.setFirstColumnSpanned(True)
235 239
236 def prepare(self, fileList, project): 240 def prepare(self, fileList, project):
237 """ 241 """
238 Public method to prepare the dialog with a list of filenames. 242 Public method to prepare the dialog with a list of filenames.
239 243
240 @param fileList list of filenames 244 @param fileList list of filenames
241 @type list of str 245 @type list of str
242 @param project reference to the project object 246 @param project reference to the project object
243 @type Project 247 @type Project
244 """ 248 """
245 self.__fileList = fileList[:] 249 self.__fileList = fileList[:]
246 self.__project = project 250 self.__project = project
247 251
248 self.buttonBox.button( 252 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
249 QDialogButtonBox.StandardButton.Close).setEnabled(True) 253 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
250 self.buttonBox.button( 254 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
251 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) 255
252 self.buttonBox.button(
253 QDialogButtonBox.StandardButton.Close).setDefault(True)
254
255 self.filterFrame.setVisible(True) 256 self.filterFrame.setVisible(True)
256 257
257 self.__data = self.__project.getData( 258 self.__data = self.__project.getData("OTHERTOOLSPARMS", "RadonCodeMetrics")
258 "OTHERTOOLSPARMS", "RadonCodeMetrics")
259 if self.__data is None or "ExcludeFiles" not in self.__data: 259 if self.__data is None or "ExcludeFiles" not in self.__data:
260 self.__data = {"ExcludeFiles": ""} 260 self.__data = {"ExcludeFiles": ""}
261 if "MinimumRank" not in self.__data: 261 if "MinimumRank" not in self.__data:
262 self.__data["MinimumRank"] = "D" 262 self.__data["MinimumRank"] = "D"
263 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"]) 263 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"])
264 self.__minimumRank = self.__data["MinimumRank"] 264 self.__minimumRank = self.__data["MinimumRank"]
265 self.rankComboBox.setCurrentIndex(self.rankComboBox.findText( 265 self.rankComboBox.setCurrentIndex(
266 self.__minimumRank)) 266 self.rankComboBox.findText(self.__minimumRank)
267 267 )
268
268 def start(self, fn, minRank="D"): 269 def start(self, fn, minRank="D"):
269 """ 270 """
270 Public slot to start the cyclomatic complexity determination. 271 Public slot to start the cyclomatic complexity determination.
271 272
272 @param fn file or list of files or directory to show 273 @param fn file or list of files or directory to show
273 the cyclomatic complexity for 274 the cyclomatic complexity for
274 @type str or list of str 275 @type str or list of str
275 @param minRank minimum rank of entries to be shown 276 @param minRank minimum rank of entries to be shown
276 @type str (one character out of A - F) 277 @type str (one character out of A - F)
278 self.cancelled = False 279 self.cancelled = False
279 self.__errorItem = None 280 self.__errorItem = None
280 self.resultList.clear() 281 self.resultList.clear()
281 self.summaryLabel.clear() 282 self.summaryLabel.clear()
282 QApplication.processEvents() 283 QApplication.processEvents()
283 284
284 self.buttonBox.button( 285 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
285 QDialogButtonBox.StandardButton.Close).setEnabled(False) 286 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
286 self.buttonBox.button( 287 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True)
287 QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
288 self.buttonBox.button(
289 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
290 self.rankComboBox.setEnabled(False) 288 self.rankComboBox.setEnabled(False)
291 QApplication.processEvents() 289 QApplication.processEvents()
292 290
293 if isinstance(fn, list): 291 if isinstance(fn, list):
294 self.files = fn 292 self.files = fn
295 elif os.path.isdir(fn): 293 elif os.path.isdir(fn):
296 self.files = [] 294 self.files = []
297 extensions = set(Preferences.getPython("Python3Extensions")) 295 extensions = set(Preferences.getPython("Python3Extensions"))
298 for ext in extensions: 296 for ext in extensions:
299 self.files.extend( 297 self.files.extend(Utilities.direntries(fn, True, "*{0}".format(ext), 0))
300 Utilities.direntries(fn, True, '*{0}'.format(ext), 0))
301 else: 298 else:
302 self.files = [fn] 299 self.files = [fn]
303 self.files.sort() 300 self.files.sort()
304 # check for missing files 301 # check for missing files
305 for f in self.files[:]: 302 for f in self.files[:]:
306 if not os.path.exists(f): 303 if not os.path.exists(f):
307 self.files.remove(f) 304 self.files.remove(f)
308 if self.__isSingle: 305 if self.__isSingle:
309 self.__fileList = self.files[:] 306 self.__fileList = self.files[:]
310 307
311 self.__summary = { 308 self.__summary = {
312 "A": 0, 309 "A": 0,
313 "B": 0, 310 "B": 0,
314 "C": 0, 311 "C": 0,
315 "D": 0, 312 "D": 0,
316 "E": 0, 313 "E": 0,
317 "F": 0, 314 "F": 0,
318 } 315 }
319 self.__ccSum = 0 316 self.__ccSum = 0
320 self.__ccCount = 0 317 self.__ccCount = 0
321 318
322 self.__minimumRank = self.rankComboBox.currentText() 319 self.__minimumRank = self.rankComboBox.currentText()
323 320
324 if len(self.files) > 0: 321 if len(self.files) > 0:
325 # disable updates of the list for speed 322 # disable updates of the list for speed
326 self.resultList.setUpdatesEnabled(False) 323 self.resultList.setUpdatesEnabled(False)
327 self.resultList.setSortingEnabled(False) 324 self.resultList.setSortingEnabled(False)
328 325
329 self.checkProgress.setMaximum(len(self.files)) 326 self.checkProgress.setMaximum(len(self.files))
330 self.checkProgress.setVisible(len(self.files) > 1) 327 self.checkProgress.setVisible(len(self.files) > 1)
331 QApplication.processEvents() 328 QApplication.processEvents()
332 329
333 # now go through all the files 330 # now go through all the files
334 self.progress = 0 331 self.progress = 0
335 if len(self.files) == 1: 332 if len(self.files) == 1:
336 self.__batch = False 333 self.__batch = False
337 self.cyclomaticComplexity() 334 self.cyclomaticComplexity()
338 else: 335 else:
339 self.__batch = True 336 self.__batch = True
340 self.cyclomaticComplexityBatch() 337 self.cyclomaticComplexityBatch()
341 338
342 def cyclomaticComplexity(self, codestring=''): 339 def cyclomaticComplexity(self, codestring=""):
343 """ 340 """
344 Public method to start a cyclomatic complexity calculation for one 341 Public method to start a cyclomatic complexity calculation for one
345 Python file. 342 Python file.
346 343
347 The results are reported to the __processResult slot. 344 The results are reported to the __processResult slot.
348 345
349 @param codestring optional sourcestring 346 @param codestring optional sourcestring
350 @type str 347 @type str
351 """ 348 """
352 if not self.files: 349 if not self.files:
353 self.checkProgress.setMaximum(1) 350 self.checkProgress.setMaximum(1)
354 self.checkProgress.setValue(1) 351 self.checkProgress.setValue(1)
355 self.__finish() 352 self.__finish()
356 return 353 return
357 354
358 self.filename = self.files.pop(0) 355 self.filename = self.files.pop(0)
359 self.checkProgress.setValue(self.progress) 356 self.checkProgress.setValue(self.progress)
360 QApplication.processEvents() 357 QApplication.processEvents()
361 358
362 if self.cancelled: 359 if self.cancelled:
363 return 360 return
364 361
365 try: 362 try:
366 self.source = Utilities.readEncodedFile(self.filename)[0] 363 self.source = Utilities.readEncodedFile(self.filename)[0]
367 self.source = Utilities.normalizeCode(self.source) 364 self.source = Utilities.normalizeCode(self.source)
368 except (UnicodeError, OSError) as msg: 365 except (UnicodeError, OSError) as msg:
369 self.__createErrorItem(self.filename, str(msg).rstrip()) 366 self.__createErrorItem(self.filename, str(msg).rstrip())
371 # Continue with next file 368 # Continue with next file
372 self.cyclomaticComplexity() 369 self.cyclomaticComplexity()
373 return 370 return
374 371
375 self.__finished = False 372 self.__finished = False
376 self.radonService.cyclomaticComplexity( 373 self.radonService.cyclomaticComplexity(None, self.filename, self.source)
377 None, self.filename, self.source)
378 374
379 def cyclomaticComplexityBatch(self): 375 def cyclomaticComplexityBatch(self):
380 """ 376 """
381 Public method to start a cyclomatic complexity calculation batch job. 377 Public method to start a cyclomatic complexity calculation batch job.
382 378
383 The results are reported to the __processResult slot. 379 The results are reported to the __processResult slot.
384 """ 380 """
385 self.__lastFileItem = None 381 self.__lastFileItem = None
386 382
387 argumentsList = [] 383 argumentsList = []
388 for progress, filename in enumerate(self.files, start=1): 384 for progress, filename in enumerate(self.files, start=1):
389 self.checkProgress.setValue(progress) 385 self.checkProgress.setValue(progress)
390 QApplication.processEvents() 386 QApplication.processEvents()
391 387
392 try: 388 try:
393 source = Utilities.readEncodedFile(filename)[0] 389 source = Utilities.readEncodedFile(filename)[0]
394 source = Utilities.normalizeCode(source) 390 source = Utilities.normalizeCode(source)
395 except (UnicodeError, OSError) as msg: 391 except (UnicodeError, OSError) as msg:
396 self.__createErrorItem(filename, str(msg).rstrip()) 392 self.__createErrorItem(filename, str(msg).rstrip())
397 continue 393 continue
398 394
399 argumentsList.append((filename, source)) 395 argumentsList.append((filename, source))
400 396
401 # reset the progress bar to the checked files 397 # reset the progress bar to the checked files
402 self.checkProgress.setValue(self.progress) 398 self.checkProgress.setValue(self.progress)
403 QApplication.processEvents() 399 QApplication.processEvents()
404 400
405 self.__finished = False 401 self.__finished = False
406 self.radonService.cyclomaticComplexityBatch(argumentsList) 402 self.radonService.cyclomaticComplexityBatch(argumentsList)
407 403
408 def __batchFinished(self, type_): 404 def __batchFinished(self, type_):
409 """ 405 """
410 Private slot handling the completion of a batch job. 406 Private slot handling the completion of a batch job.
411 407
412 @param type_ type of the calculated metrics 408 @param type_ type of the calculated metrics
413 @type str, one of ["raw", "mi", "cc"] 409 @type str, one of ["raw", "mi", "cc"]
414 """ 410 """
415 if type_ == "cc": 411 if type_ == "cc":
416 self.checkProgress.setMaximum(1) 412 self.checkProgress.setMaximum(1)
417 self.checkProgress.setValue(1) 413 self.checkProgress.setValue(1)
418 self.__finish() 414 self.__finish()
419 415
420 def __processError(self, type_, fn, msg): 416 def __processError(self, type_, fn, msg):
421 """ 417 """
422 Private slot to process an error indication from the service. 418 Private slot to process an error indication from the service.
423 419
424 @param type_ type of the calculated metrics 420 @param type_ type of the calculated metrics
425 @type str, one of ["raw", "mi", "cc"] 421 @type str, one of ["raw", "mi", "cc"]
426 @param fn filename of the file 422 @param fn filename of the file
427 @type str 423 @type str
428 @param msg error message 424 @param msg error message
429 @type str 425 @type str
430 """ 426 """
431 if type_ == "cc": 427 if type_ == "cc":
432 self.__createErrorItem(fn, msg) 428 self.__createErrorItem(fn, msg)
433 429
434 def __processResult(self, fn, result): 430 def __processResult(self, fn, result):
435 """ 431 """
436 Private slot called after perfoming a cyclomatic complexity calculation 432 Private slot called after perfoming a cyclomatic complexity calculation
437 on one file. 433 on one file.
438 434
439 @param fn filename of the file 435 @param fn filename of the file
440 @type str 436 @type str
441 @param result result dict 437 @param result result dict
442 @type dict 438 @type dict
443 """ 439 """
444 if self.__finished: 440 if self.__finished:
445 return 441 return
446 442
447 # Check if it's the requested file, otherwise ignore signal if not 443 # Check if it's the requested file, otherwise ignore signal if not
448 # in batch mode 444 # in batch mode
449 if not self.__batch and fn != self.filename: 445 if not self.__batch and fn != self.filename:
450 return 446 return
451 447
452 QApplication.processEvents() 448 QApplication.processEvents()
453 449
454 if "error" in result: 450 if "error" in result:
455 self.__createErrorItem(fn, result["error"]) 451 self.__createErrorItem(fn, result["error"])
456 else: 452 else:
457 if result["result"]: 453 if result["result"]:
458 fitm = self.__createFileItem(fn) 454 fitm = self.__createFileItem(fn)
460 self.__createResultItem(fitm, resultDict) 456 self.__createResultItem(fitm, resultDict)
461 if fitm.childCount() > 0: 457 if fitm.childCount() > 0:
462 self.resultList.addTopLevelItem(fitm) 458 self.resultList.addTopLevelItem(fitm)
463 fitm.setExpanded(True) 459 fitm.setExpanded(True)
464 fitm.setFirstColumnSpanned(True) 460 fitm.setFirstColumnSpanned(True)
465 461
466 self.__ccCount += result["count"] 462 self.__ccCount += result["count"]
467 self.__ccSum += result["total_cc"] 463 self.__ccSum += result["total_cc"]
468 for rank in result["summary"]: 464 for rank in result["summary"]:
469 self.__summary[rank] += result["summary"][rank] 465 self.__summary[rank] += result["summary"][rank]
470 466
471 self.progress += 1 467 self.progress += 1
472 468
473 self.checkProgress.setValue(self.progress) 469 self.checkProgress.setValue(self.progress)
474 QApplication.processEvents() 470 QApplication.processEvents()
475 471
476 if not self.__batch: 472 if not self.__batch:
477 self.cyclomaticComplexity() 473 self.cyclomaticComplexity()
478 474
479 def __finish(self): 475 def __finish(self):
480 """ 476 """
481 Private slot called when the action or the user pressed the button. 477 Private slot called when the action or the user pressed the button.
482 """ 478 """
483 from radon.complexity import cc_rank 479 from radon.complexity import cc_rank
484 480
485 if not self.__finished: 481 if not self.__finished:
486 self.__finished = True 482 self.__finished = True
487 483
488 # re-enable updates of the list 484 # re-enable updates of the list
489 self.resultList.setSortingEnabled(True) 485 self.resultList.setSortingEnabled(True)
490 self.resultList.sortItems(0, Qt.SortOrder.AscendingOrder) 486 self.resultList.sortItems(0, Qt.SortOrder.AscendingOrder)
491 self.resultList.sortItems(1, Qt.SortOrder.AscendingOrder) 487 self.resultList.sortItems(1, Qt.SortOrder.AscendingOrder)
492 self.resultList.setUpdatesEnabled(True) 488 self.resultList.setUpdatesEnabled(True)
493 489
494 self.cancelled = True 490 self.cancelled = True
495 self.buttonBox.button( 491 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(
496 QDialogButtonBox.StandardButton.Close).setEnabled(True) 492 True
497 self.buttonBox.button( 493 )
498 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) 494 self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(
499 self.buttonBox.button( 495 False
500 QDialogButtonBox.StandardButton.Close).setDefault(True) 496 )
497 self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(
498 True
499 )
501 self.rankComboBox.setEnabled(True) 500 self.rankComboBox.setEnabled(True)
502 501
503 self.resultList.header().resizeSections( 502 self.resultList.header().resizeSections(
504 QHeaderView.ResizeMode.ResizeToContents) 503 QHeaderView.ResizeMode.ResizeToContents
504 )
505 self.resultList.header().setStretchLastSection(True) 505 self.resultList.header().setStretchLastSection(True)
506 self.resultList.header().setSectionResizeMode( 506 self.resultList.header().setSectionResizeMode(
507 QHeaderView.ResizeMode.Interactive) 507 QHeaderView.ResizeMode.Interactive
508 508 )
509
509 averageCC = float(self.__ccSum) / (self.__ccCount or 1) 510 averageCC = float(self.__ccSum) / (self.__ccCount or 1)
510 511
511 self.summaryLabel.setText(self.tr( 512 self.summaryLabel.setText(
512 "<b>Summary:</b><br/>" 513 self.tr(
513 "{0} blocks (classes, functions, methods) analyzed.<br/>" 514 "<b>Summary:</b><br/>"
514 "Average complexity: {7} ({8})" 515 "{0} blocks (classes, functions, methods) analyzed.<br/>"
515 "<table>" 516 "Average complexity: {7} ({8})"
516 "<tr><td width=30><b>A</b></td>" 517 "<table>"
517 "<td align='right'>{1} blocks</td></tr>" 518 "<tr><td width=30><b>A</b></td>"
518 "<tr><td width=30><b>B</b></td>" 519 "<td align='right'>{1} blocks</td></tr>"
519 "<td align='right'>{2} blocks</td></tr>" 520 "<tr><td width=30><b>B</b></td>"
520 "<tr><td width=30><b>C</b></td>" 521 "<td align='right'>{2} blocks</td></tr>"
521 "<td align='right'>{3} blocks</td></tr>" 522 "<tr><td width=30><b>C</b></td>"
522 "<tr><td width=30><b>D</b></td>" 523 "<td align='right'>{3} blocks</td></tr>"
523 "<td align='right'>{4} blocks</td></tr>" 524 "<tr><td width=30><b>D</b></td>"
524 "<tr><td width=30><b>E</b></td>" 525 "<td align='right'>{4} blocks</td></tr>"
525 "<td align='right'>{5} blocks</td></tr>" 526 "<tr><td width=30><b>E</b></td>"
526 "<tr><td width=30><b>F</b></td>" 527 "<td align='right'>{5} blocks</td></tr>"
527 "<td align='right'>{6} blocks</td></tr>" 528 "<tr><td width=30><b>F</b></td>"
528 "</table>" 529 "<td align='right'>{6} blocks</td></tr>"
529 ).format( 530 "</table>"
530 self.__locale.toString(self.__ccCount), 531 ).format(
531 self.__locale.toString(self.__summary["A"]), 532 self.__locale.toString(self.__ccCount),
532 self.__locale.toString(self.__summary["B"]), 533 self.__locale.toString(self.__summary["A"]),
533 self.__locale.toString(self.__summary["C"]), 534 self.__locale.toString(self.__summary["B"]),
534 self.__locale.toString(self.__summary["D"]), 535 self.__locale.toString(self.__summary["C"]),
535 self.__locale.toString(self.__summary["E"]), 536 self.__locale.toString(self.__summary["D"]),
536 self.__locale.toString(self.__summary["F"]), 537 self.__locale.toString(self.__summary["E"]),
537 cc_rank(averageCC), 538 self.__locale.toString(self.__summary["F"]),
538 self.__locale.toString(averageCC, "f", 1) 539 cc_rank(averageCC),
539 )) 540 self.__locale.toString(averageCC, "f", 1),
540 541 )
542 )
543
541 self.checkProgress.setVisible(False) 544 self.checkProgress.setVisible(False)
542 545
543 @pyqtSlot(QAbstractButton) 546 @pyqtSlot(QAbstractButton)
544 def on_buttonBox_clicked(self, button): 547 def on_buttonBox_clicked(self, button):
545 """ 548 """
546 Private slot called by a button of the button box clicked. 549 Private slot called by a button of the button box clicked.
547 550
548 @param button button that was clicked 551 @param button button that was clicked
549 @type QAbstractButton 552 @type QAbstractButton
550 """ 553 """
551 if button == self.buttonBox.button( 554 if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close):
552 QDialogButtonBox.StandardButton.Close
553 ):
554 self.close() 555 self.close()
555 elif button == self.buttonBox.button( 556 elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel):
556 QDialogButtonBox.StandardButton.Cancel
557 ):
558 if self.__batch: 557 if self.__batch:
559 self.radonService.cancelComplexityBatch() 558 self.radonService.cancelComplexityBatch()
560 QTimer.singleShot(1000, self.__finish) 559 QTimer.singleShot(1000, self.__finish)
561 else: 560 else:
562 self.__finish() 561 self.__finish()
563 562
564 @pyqtSlot() 563 @pyqtSlot()
565 def on_startButton_clicked(self): 564 def on_startButton_clicked(self):
566 """ 565 """
567 Private slot to start a cyclomatic complexity run. 566 Private slot to start a cyclomatic complexity run.
568 """ 567 """
569 fileList = self.__fileList[:] 568 fileList = self.__fileList[:]
570 dataChanged = False 569 dataChanged = False
571 570
572 filterString = self.excludeFilesEdit.text() 571 filterString = self.excludeFilesEdit.text()
573 if ( 572 if (
574 "ExcludeFiles" not in self.__data or 573 "ExcludeFiles" not in self.__data
575 filterString != self.__data["ExcludeFiles"] 574 or filterString != self.__data["ExcludeFiles"]
576 ): 575 ):
577 self.__data["ExcludeFiles"] = filterString 576 self.__data["ExcludeFiles"] = filterString
578 dataChanged = True 577 dataChanged = True
579 filterList = [f.strip() for f in filterString.split(",") 578 filterList = [f.strip() for f in filterString.split(",") if f.strip()]
580 if f.strip()]
581 if filterList: 579 if filterList:
582 for fileFilter in filterList: 580 for fileFilter in filterList:
583 fileList = [f for f in fileList 581 fileList = [f for f in fileList if not fnmatch.fnmatch(f, fileFilter)]
584 if not fnmatch.fnmatch(f, fileFilter)] 582
585
586 minimumRank = self.rankComboBox.currentText() 583 minimumRank = self.rankComboBox.currentText()
587 if ( 584 if (
588 "MinimumRank" not in self.__data or 585 "MinimumRank" not in self.__data
589 minimumRank != self.__data["MinimumRank"] 586 or minimumRank != self.__data["MinimumRank"]
590 ): 587 ):
591 self.__data["MinimumRank"] = minimumRank 588 self.__data["MinimumRank"] = minimumRank
592 dataChanged = True 589 dataChanged = True
593 590
594 if dataChanged: 591 if dataChanged:
595 self.__project.setData( 592 self.__project.setData("OTHERTOOLSPARMS", "RadonCodeMetrics", self.__data)
596 "OTHERTOOLSPARMS", "RadonCodeMetrics", self.__data) 593
597
598 self.start(fileList) 594 self.start(fileList)
599 595
600 def __showContextMenu(self, coord): 596 def __showContextMenu(self, coord):
601 """ 597 """
602 Private slot to show the context menu of the resultlist. 598 Private slot to show the context menu of the resultlist.
603 599
604 @param coord the position of the mouse pointer (QPoint) 600 @param coord the position of the mouse pointer (QPoint)
605 """ 601 """
606 if self.resultList.topLevelItemCount() > 0: 602 if self.resultList.topLevelItemCount() > 0:
607 self.__menu.popup(self.mapToGlobal(coord)) 603 self.__menu.popup(self.mapToGlobal(coord))
608 604
609 def __resultCollapse(self): 605 def __resultCollapse(self):
610 """ 606 """
611 Private slot to collapse all entries of the resultlist. 607 Private slot to collapse all entries of the resultlist.
612 """ 608 """
613 for index in range(self.resultList.topLevelItemCount()): 609 for index in range(self.resultList.topLevelItemCount()):
614 self.resultList.topLevelItem(index).setExpanded(False) 610 self.resultList.topLevelItem(index).setExpanded(False)
615 611
616 def __resultExpand(self): 612 def __resultExpand(self):
617 """ 613 """
618 Private slot to expand all entries of the resultlist. 614 Private slot to expand all entries of the resultlist.
619 """ 615 """
620 for index in range(self.resultList.topLevelItemCount()): 616 for index in range(self.resultList.topLevelItemCount()):
621 self.resultList.topLevelItem(index).setExpanded(True) 617 self.resultList.topLevelItem(index).setExpanded(True)
622 618
623 def clear(self): 619 def clear(self):
624 """ 620 """
625 Public method to clear all results. 621 Public method to clear all results.
626 """ 622 """
627 self.resultList.clear() 623 self.resultList.clear()
628 self.summaryLabel.clear() 624 self.summaryLabel.clear()
629 625
630 @pyqtSlot(QTreeWidgetItem, int) 626 @pyqtSlot(QTreeWidgetItem, int)
631 def on_resultList_itemActivated(self, item, column): 627 def on_resultList_itemActivated(self, item, column):
632 """ 628 """
633 Private slot to handle the activation of a result item. 629 Private slot to handle the activation of a result item.
634 630
635 @param item reference to the activated item 631 @param item reference to the activated item
636 @type QTreeWidgetItem 632 @type QTreeWidgetItem
637 @param column activated column 633 @param column activated column
638 @type int 634 @type int
639 """ 635 """
640 filename = item.data(0, self.FilePathRole) 636 filename = item.data(0, self.FilePathRole)
641 lineno = item.data(0, self.LineNumberRole) 637 lineno = item.data(0, self.LineNumberRole)
642 if filename: 638 if filename:
643 vm = ericApp().getObject("ViewManager") 639 vm = ericApp().getObject("ViewManager")
644 vm.openSourceFile(filename, lineno) 640 vm.openSourceFile(filename, lineno)
645 641
646 @pyqtSlot(str) 642 @pyqtSlot(str)
647 def on_rankComboBox_activated(self, rank): 643 def on_rankComboBox_activated(self, rank):
648 """ 644 """
649 Private slot to handle the selection of a minimum rank. 645 Private slot to handle the selection of a minimum rank.
650 646
651 @param rank selected minimum rank 647 @param rank selected minimum rank
652 @type str 648 @type str
653 """ 649 """
654 if self.__isSingle: 650 if self.__isSingle:
655 self.start(self.__fileList[:]) 651 self.start(self.__fileList[:])

eric ide

mercurial