vibefpmgui.window.complexity

The Complexity panel: measuring the project's Fortran files (on a pool thread, while the panel is on view), again when one (or a file one includes) is saved; a file that hasn't changed since it was last measured, nor have the files it includes, isn't read again.

  1"""The Complexity panel: measuring the project's Fortran files (on a pool
  2thread, while the panel is on view), again when one (or a file one
  3includes) is saved; a file that hasn't changed since it was last measured,
  4nor have the files it includes, isn't read again.
  5"""
  6
  7from __future__ import annotations
  8
  9import time
 10from pathlib import Path
 11
 12from .. import complexity as cx
 13from ..background import call_in_background
 14from ..complexity_panel import ComplexityPanel
 15from ..project_search import Query, iter_files, search_roots
 16from ..project_tree import fpm_folders
 17
 18FORTRAN_GLOBS = ", ".join(f"*{ext}" for ext in sorted(cx.FORTRAN))
 19
 20
 21def _stamp(path: Path) -> tuple[int, int] | None:
 22    """A file's (modification time, size); None if it's gone."""
 23    try:
 24        stat = path.stat()
 25    except OSError:
 26        return None
 27    return stat.st_mtime_ns, stat.st_size
 28
 29
 30def _key(path: Path, metrics: cx.FileMetrics) -> tuple:
 31    return _stamp(path), tuple(_stamp(p) for p in metrics.includes)
 32
 33
 34def measure(roots, dependencies: bool, cache: dict) -> tuple[list[cx.FileMetrics], dict]:
 35    """Every Fortran file's metrics, and the cache for next time ({path:
 36    (key, metrics)}: a file isn't read again while it and the files it
 37    includes are unchanged, their modification times and sizes the same)."""
 38    files, kept = [], {}
 39    query = Query("", include=FORTRAN_GLOBS, dependencies=dependencies)
 40    for _root, path in iter_files(roots, query):
 41        if _stamp(path) is None:
 42            continue
 43        old = cache.get(path)
 44        if old is not None and old[0] == _key(path, old[1]):
 45            metrics = old[1]
 46        else:
 47            metrics = cx.analyze_file(path)
 48        kept[path] = (_key(path, metrics), metrics)
 49        files.append(metrics)
 50    return files, kept
 51
 52
 53class ComplexityMixin:
 54    """The Complexity panel (a part of `vibefpmgui.main_window.MainWindow`)."""
 55
 56    def _build_complexity_panel(self):
 57        """The Complexity panel."""
 58        self.complexity_panel = panel = ComplexityPanel()
 59        panel.icon_for = self.project_tree.icon_for
 60        panel.set_code_font(self._mono_font(editor=True))
 61        panel.tests_check.setChecked(self.settings.value("complexity_tests", False, type=bool))
 62        panel.tests_check.toggled.connect(lambda on: self.settings.setValue("complexity_tests", on))
 63        panel.dependencies_check.setChecked(self.settings.value("complexity_dependencies", False, type=bool))
 64        panel.dependencies_check.toggled.connect(lambda on: self.settings.setValue("complexity_dependencies", on))
 65        panel.refresh_requested.connect(self.refresh_complexity)
 66        panel.open_requested.connect(lambda path, line: self.navigate_to(path, line, 0, preview=True))
 67        self._complexity_cache: dict = {}
 68        self._complexity_request = 0
 69        self._complexity_stale = True
 70
 71    def refresh_complexity(self):
 72        """Measure the project's code again (or once the panel is on view)."""
 73        panel = self.complexity_panel
 74        if self.manifest_path is None:
 75            panel.show_message("Open a project to see the complexity of its Fortran code here.")
 76            return
 77        if not self.panel_in_front(panel):
 78            self._complexity_stale = True
 79            return
 80        self._complexity_stale = False
 81        self._complexity_request += 1
 82        request = self._complexity_request
 83        project = self.manifest_path.parent
 84        roots = search_roots(self.graph, project)
 85        dependencies = panel.dependencies_check.isChecked()
 86        cache = dict(self._complexity_cache)
 87        if not panel.files:
 88            panel.show_message("Measuring the code…")
 89        call_in_background(lambda: measure(roots, dependencies, cache),
 90                           lambda result: self._complexity_measured(request, roots, *result), self)
 91
 92    def _complexity_measured(self, request: int, roots, files: list[cx.FileMetrics], cache: dict):
 93        if request != self._complexity_request or self.manifest_path is None:
 94            return
 95        self._complexity_cache = cache
 96        project = self.manifest_path.parent
 97        tests = [Path(folder) for folder, role in fpm_folders(self.manifest_path).items() if role == "test"]
 98        dependency_dirs = [r.directory for r in roots if r.dependency]
 99        self.complexity_panel.show_metrics(files, project, tests, dependency_dirs, time.strftime("%H:%M"))
100
101    def _complexity_panel_shown(self, shown: bool):
102        if shown and self._complexity_stale:
103            self.refresh_complexity()
104
105    def _complexity_file_saved(self, path: str):
106        saved = Path(path)
107        if cx.is_fortran(saved) or any(saved.resolve() == p.resolve()
108                                       for f in self.complexity_panel.files for p in f.includes):
109            self.refresh_complexity()
FORTRAN_GLOBS = '*.f, *.f03, *.f08, *.f18, *.f23, *.f77, *.f90, *.f95, *.for, *.fpp, *.ftn'
def measure( roots, dependencies: bool, cache: dict) -> tuple[list[vibefpmgui.complexity.FileMetrics], dict]:
35def measure(roots, dependencies: bool, cache: dict) -> tuple[list[cx.FileMetrics], dict]:
36    """Every Fortran file's metrics, and the cache for next time ({path:
37    (key, metrics)}: a file isn't read again while it and the files it
38    includes are unchanged, their modification times and sizes the same)."""
39    files, kept = [], {}
40    query = Query("", include=FORTRAN_GLOBS, dependencies=dependencies)
41    for _root, path in iter_files(roots, query):
42        if _stamp(path) is None:
43            continue
44        old = cache.get(path)
45        if old is not None and old[0] == _key(path, old[1]):
46            metrics = old[1]
47        else:
48            metrics = cx.analyze_file(path)
49        kept[path] = (_key(path, metrics), metrics)
50        files.append(metrics)
51    return files, kept

Every Fortran file's metrics, and the cache for next time ({path: (key, metrics)}: a file isn't read again while it and the files it includes are unchanged, their modification times and sizes the same).

class ComplexityMixin:
 54class ComplexityMixin:
 55    """The Complexity panel (a part of `vibefpmgui.main_window.MainWindow`)."""
 56
 57    def _build_complexity_panel(self):
 58        """The Complexity panel."""
 59        self.complexity_panel = panel = ComplexityPanel()
 60        panel.icon_for = self.project_tree.icon_for
 61        panel.set_code_font(self._mono_font(editor=True))
 62        panel.tests_check.setChecked(self.settings.value("complexity_tests", False, type=bool))
 63        panel.tests_check.toggled.connect(lambda on: self.settings.setValue("complexity_tests", on))
 64        panel.dependencies_check.setChecked(self.settings.value("complexity_dependencies", False, type=bool))
 65        panel.dependencies_check.toggled.connect(lambda on: self.settings.setValue("complexity_dependencies", on))
 66        panel.refresh_requested.connect(self.refresh_complexity)
 67        panel.open_requested.connect(lambda path, line: self.navigate_to(path, line, 0, preview=True))
 68        self._complexity_cache: dict = {}
 69        self._complexity_request = 0
 70        self._complexity_stale = True
 71
 72    def refresh_complexity(self):
 73        """Measure the project's code again (or once the panel is on view)."""
 74        panel = self.complexity_panel
 75        if self.manifest_path is None:
 76            panel.show_message("Open a project to see the complexity of its Fortran code here.")
 77            return
 78        if not self.panel_in_front(panel):
 79            self._complexity_stale = True
 80            return
 81        self._complexity_stale = False
 82        self._complexity_request += 1
 83        request = self._complexity_request
 84        project = self.manifest_path.parent
 85        roots = search_roots(self.graph, project)
 86        dependencies = panel.dependencies_check.isChecked()
 87        cache = dict(self._complexity_cache)
 88        if not panel.files:
 89            panel.show_message("Measuring the code…")
 90        call_in_background(lambda: measure(roots, dependencies, cache),
 91                           lambda result: self._complexity_measured(request, roots, *result), self)
 92
 93    def _complexity_measured(self, request: int, roots, files: list[cx.FileMetrics], cache: dict):
 94        if request != self._complexity_request or self.manifest_path is None:
 95            return
 96        self._complexity_cache = cache
 97        project = self.manifest_path.parent
 98        tests = [Path(folder) for folder, role in fpm_folders(self.manifest_path).items() if role == "test"]
 99        dependency_dirs = [r.directory for r in roots if r.dependency]
100        self.complexity_panel.show_metrics(files, project, tests, dependency_dirs, time.strftime("%H:%M"))
101
102    def _complexity_panel_shown(self, shown: bool):
103        if shown and self._complexity_stale:
104            self.refresh_complexity()
105
106    def _complexity_file_saved(self, path: str):
107        saved = Path(path)
108        if cx.is_fortran(saved) or any(saved.resolve() == p.resolve()
109                                       for f in self.complexity_panel.files for p in f.includes):
110            self.refresh_complexity()

The Complexity panel (a part of vibefpmgui.main_window.MainWindow).

def refresh_complexity(self):
72    def refresh_complexity(self):
73        """Measure the project's code again (or once the panel is on view)."""
74        panel = self.complexity_panel
75        if self.manifest_path is None:
76            panel.show_message("Open a project to see the complexity of its Fortran code here.")
77            return
78        if not self.panel_in_front(panel):
79            self._complexity_stale = True
80            return
81        self._complexity_stale = False
82        self._complexity_request += 1
83        request = self._complexity_request
84        project = self.manifest_path.parent
85        roots = search_roots(self.graph, project)
86        dependencies = panel.dependencies_check.isChecked()
87        cache = dict(self._complexity_cache)
88        if not panel.files:
89            panel.show_message("Measuring the code…")
90        call_in_background(lambda: measure(roots, dependencies, cache),
91                           lambda result: self._complexity_measured(request, roots, *result), self)

Measure the project's code again (or once the panel is on view).