src/eric7/DataViews/CodeMetricsDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8934
d3798915e0d2
child 9210
15743bae8a50
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a code metrics dialog.
8 """
9
10 import collections
11 import fnmatch
12 import os
13 import time
14
15 from PyQt6.QtCore import pyqtSlot, Qt, QLocale
16 from PyQt6.QtWidgets import (
17 QDialog, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem,
18 QApplication
19 )
20
21 from .Ui_CodeMetricsDialog import Ui_CodeMetricsDialog
22 from . import CodeMetrics
23
24 import Utilities
25
26
27 class CodeMetricsDialog(QDialog, Ui_CodeMetricsDialog):
28 """
29 Class implementing a dialog to display the code metrics.
30 """
31 def __init__(self, parent=None):
32 """
33 Constructor
34
35 @param parent parent widget (QWidget)
36 """
37 super().__init__(parent)
38 self.setupUi(self)
39 self.setWindowFlags(Qt.WindowType.Window)
40
41 self.buttonBox.button(
42 QDialogButtonBox.StandardButton.Close).setEnabled(False)
43 self.buttonBox.button(
44 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
45
46 self.summaryList.headerItem().setText(
47 self.summaryList.columnCount(), "")
48 self.summaryList.header().resizeSection(0, 200)
49 self.summaryList.header().resizeSection(1, 100)
50
51 self.resultList.headerItem().setText(self.resultList.columnCount(), "")
52
53 self.cancelled = False
54
55 self.__menu = QMenu(self)
56 self.__menu.addAction(self.tr("Collapse All"),
57 self.__resultCollapse)
58 self.__menu.addAction(self.tr("Expand All"), self.__resultExpand)
59 self.resultList.setContextMenuPolicy(
60 Qt.ContextMenuPolicy.CustomContextMenu)
61 self.resultList.customContextMenuRequested.connect(
62 self.__showContextMenu)
63
64 self.__fileList = []
65 self.__project = None
66 self.filterFrame.setVisible(False)
67
68 def __resizeResultColumns(self):
69 """
70 Private method to resize the list columns.
71 """
72 self.resultList.header().resizeSections(
73 QHeaderView.ResizeMode.ResizeToContents)
74 self.resultList.header().setStretchLastSection(True)
75
76 def __createResultItem(self, parent, values):
77 """
78 Private slot to create a new item in the result list.
79
80 @param parent parent of the new item (QTreeWidget or QTreeWidgetItem)
81 @param values values to be displayed (list)
82 @return the generated item
83 """
84 data = [values[0]]
85 for value in values[1:]:
86 try:
87 data.append("{0:5}".format(int(value)))
88 except ValueError:
89 data.append(value)
90 itm = QTreeWidgetItem(parent, data)
91 for col in range(1, 7):
92 itm.setTextAlignment(
93 col, Qt.AlignmentFlag.AlignRight)
94 return itm
95
96 def __resizeSummaryColumns(self):
97 """
98 Private method to resize the list columns.
99 """
100 self.summaryList.header().resizeSections(
101 QHeaderView.ResizeMode.ResizeToContents)
102 self.summaryList.header().setStretchLastSection(True)
103
104 def __createSummaryItem(self, col0, col1):
105 """
106 Private slot to create a new item in the summary list.
107
108 @param col0 string for column 0 (string)
109 @param col1 string for column 1 (string)
110 """
111 itm = QTreeWidgetItem(self.summaryList, [col0, col1])
112 itm.setTextAlignment(1, Qt.AlignmentFlag.AlignRight)
113
114 def prepare(self, fileList, project):
115 """
116 Public method to prepare the dialog with a list of filenames.
117
118 @param fileList list of filenames (list of strings)
119 @param project reference to the project object (Project)
120 """
121 self.__fileList = fileList[:]
122 self.__project = project
123
124 self.buttonBox.button(
125 QDialogButtonBox.StandardButton.Close).setEnabled(True)
126 self.buttonBox.button(
127 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
128 self.buttonBox.button(
129 QDialogButtonBox.StandardButton.Close).setDefault(True)
130
131 self.filterFrame.setVisible(True)
132
133 self.__data = self.__project.getData("OTHERTOOLSPARMS", "CodeMetrics")
134 if self.__data is None or "ExcludeFiles" not in self.__data:
135 self.__data = {"ExcludeFiles": ""}
136 self.excludeFilesEdit.setText(self.__data["ExcludeFiles"])
137
138 def start(self, fn):
139 """
140 Public slot to start the code metrics determination.
141
142 @param fn file or list of files or directory to show
143 the code metrics for (string or list of strings)
144 """
145 self.cancelled = False
146 self.buttonBox.button(
147 QDialogButtonBox.StandardButton.Close).setEnabled(False)
148 self.buttonBox.button(
149 QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
150 self.buttonBox.button(
151 QDialogButtonBox.StandardButton.Cancel).setDefault(True)
152 QApplication.processEvents()
153
154 loc = QLocale()
155 if isinstance(fn, list):
156 files = fn
157 elif os.path.isdir(fn):
158 files = Utilities.direntries(fn, True, '*.py', False)
159 else:
160 files = [fn]
161 files.sort()
162 # check for missing files
163 for f in files[:]:
164 if not os.path.exists(f):
165 files.remove(f)
166
167 self.checkProgress.setMaximum(len(files))
168 QApplication.processEvents()
169
170 total = collections.defaultdict(int)
171 CodeMetrics.summarize(total, 'files', len(files))
172
173 try:
174 # disable updates of the list for speed
175 self.resultList.setUpdatesEnabled(False)
176 self.resultList.setSortingEnabled(False)
177
178 # now go through all the files
179 now = time.monotonic()
180 for progress, file in enumerate(files, start=1):
181 if self.cancelled:
182 return
183
184 stats = CodeMetrics.analyze(file, total)
185
186 v = self.__getValues(loc, stats, 'TOTAL ')
187 fitm = self.__createResultItem(self.resultList, [file] + v)
188
189 identifiers = stats.identifiers
190 for identifier in identifiers:
191 v = self.__getValues(loc, stats, identifier)
192
193 self.__createResultItem(fitm, [identifier] + v)
194 self.resultList.expandItem(fitm)
195
196 self.checkProgress.setValue(progress)
197 if time.monotonic() - now > 0.01:
198 QApplication.processEvents()
199 now = time.monotonic()
200 finally:
201 # reenable updates of the list
202 self.resultList.setSortingEnabled(True)
203 self.resultList.setUpdatesEnabled(True)
204 self.__resizeResultColumns()
205
206 # now do the summary stuff
207 self.__createSummaryItem(self.tr("files"),
208 loc.toString(total['files']))
209 self.__createSummaryItem(self.tr("lines"),
210 loc.toString(total['lines']))
211 self.__createSummaryItem(self.tr("bytes"),
212 loc.toString(total['bytes']))
213 self.__createSummaryItem(self.tr("comments"),
214 loc.toString(total['comments']))
215 self.__createSummaryItem(self.tr("comment lines"),
216 loc.toString(total['commentlines']))
217 self.__createSummaryItem(self.tr("empty lines"),
218 loc.toString(total['empty lines']))
219 self.__createSummaryItem(self.tr("non-commentary lines"),
220 loc.toString(total['non-commentary lines']))
221 self.__resizeSummaryColumns()
222 self.__finish()
223
224 def __getValues(self, loc, stats, identifier):
225 """
226 Private method to extract the code metric values.
227
228 @param loc reference to the locale object (QLocale)
229 @param stats reference to the code metric statistics object
230 @param identifier identifier to get values for
231 @return list of values suitable for display (list of strings)
232 """
233 counters = stats.counters.get(identifier, {})
234 v = []
235 for key in ('start', 'end', 'lines', 'nloc', 'commentlines', 'empty'):
236 if counters.get(key, 0):
237 v.append(loc.toString(counters[key]))
238 else:
239 v.append('')
240 return v
241
242 def __finish(self):
243 """
244 Private slot called when the action finished or the user pressed the
245 button.
246 """
247 self.cancelled = True
248 self.buttonBox.button(
249 QDialogButtonBox.StandardButton.Close).setEnabled(True)
250 self.buttonBox.button(
251 QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
252 self.buttonBox.button(
253 QDialogButtonBox.StandardButton.Close).setDefault(True)
254
255 self.resultList.header().setSectionResizeMode(
256 QHeaderView.ResizeMode.Interactive)
257 self.summaryList.header().setSectionResizeMode(
258 QHeaderView.ResizeMode.Interactive)
259
260 def on_buttonBox_clicked(self, button):
261 """
262 Private slot called by a button of the button box clicked.
263
264 @param button button that was clicked (QAbstractButton)
265 """
266 if button == self.buttonBox.button(
267 QDialogButtonBox.StandardButton.Close
268 ):
269 self.close()
270 elif button == self.buttonBox.button(
271 QDialogButtonBox.StandardButton.Cancel
272 ):
273 self.__finish()
274
275 @pyqtSlot()
276 def on_startButton_clicked(self):
277 """
278 Private slot to start a code metrics run.
279 """
280 fileList = self.__fileList[:]
281
282 filterString = self.excludeFilesEdit.text()
283 if ("ExcludeFiles" not in self.__data or
284 filterString != self.__data["ExcludeFiles"]):
285 self.__data["ExcludeFiles"] = filterString
286 self.__project.setData("OTHERTOOLSPARMS", "CodeMetrics",
287 self.__data)
288 filterList = filterString.split(",")
289 if filterList:
290 for filterString in filterList:
291 fileList = [f for f in fileList
292 if not fnmatch.fnmatch(f, filterString.strip())]
293
294 self.resultList.clear()
295 self.summaryList.clear()
296 self.start(fileList)
297
298 def __showContextMenu(self, coord):
299 """
300 Private slot to show the context menu of the listview.
301
302 @param coord the position of the mouse pointer (QPoint)
303 """
304 if self.resultList.topLevelItemCount() > 0:
305 self.__menu.popup(self.mapToGlobal(coord))
306
307 def __resultCollapse(self):
308 """
309 Private slot to collapse all entries of the resultlist.
310 """
311 for index in range(self.resultList.topLevelItemCount()):
312 self.resultList.topLevelItem(index).setExpanded(False)
313
314 def __resultExpand(self):
315 """
316 Private slot to expand all entries of the resultlist.
317 """
318 for index in range(self.resultList.topLevelItemCount()):
319 self.resultList.topLevelItem(index).setExpanded(True)

eric ide

mercurial