chore: cleanup comments

This commit is contained in:
2026-06-30 22:27:37 +02:00
parent 2b89c8e4a1
commit 865b5bda53
5 changed files with 11 additions and 164 deletions
+3 -62
View File
@@ -1,22 +1,3 @@
#!/usr/bin/env python3
"""Build-time settings index extractor for the settings settings search.
Parses the settings page QML files, PageRegistry.qml (page icons/labels) and
PageCompRegistry.qml (page ordering and sub-page nesting) to produce a search
index as JSON. Run at build time (see CMakeLists.txt); the shell loads the
result at runtime via SettingsSearcher.qml.
The output contains three parts:
- entries: forward index, one record per setting (title, anchor, nav path)
- inverted: token -> list of entry indices (classic inverted index)
- ranking: token -> {entry index: weight} precomputed match weights
Nothing here is hand-maintained per page: page metadata comes from
PageRegistry, the page tree from PageCompRegistry, and the directory layout is
discovered by walking the pages folder.
Usage: build-settings-index.py <settings-dir> <output-json>
"""
from __future__ import annotations
import json
@@ -29,7 +10,6 @@ from pathlib import Path
@lru_cache(maxsize=None)
def read_lines(path: Path) -> tuple[str, ...]:
"""Read a file's lines, cached so each page file is only read once."""
return tuple(path.read_text().splitlines())
@@ -37,18 +17,11 @@ ROW_RE = re.compile(
r'^\s*(ToggleRow|SliderRow|SelectRow|SpinRow|NavRow|InfoRow|PopupRow|OverlayRow|DefaultRow)\s*\{')
LABEL_RE = re.compile(r'^\s*(?:label|text):\s*qsTr\("([^"]+)"\)')
ANCHOR_RE = re.compile(r'^\s*settingAnchor:\s*"([^"]+)"')
# A ToggleRow whose value is a plain config property can be flipped straight from
# the search results. We capture the property path from `checked:` and require
# `onToggled:` to write the same path back (a symmetric binding), so reading and
# writing go through one path. Toggles bound to functions or multi-line handlers
# are left without a path and just deep-link as usual.
CHECKED_RE = re.compile(r'^\s*checked:\s*(?:Config)\.([\w.]+)\s*$')
ONTOGGLED_RE = re.compile(
r'^\s*onToggled:\s*(?:Config)\.([\w.]+)\s*=\s*checked\s*$')
ICON_RE = re.compile(r'^\s*icon:\s*"([^"]+)"')
SKIP_LABELS = {"Muted", "None"}
# Field weights for ranking: a token matching the title counts more than one
# matching the keywords blob.
FIELD_WEIGHT = {"title": 1.0, "keywords": 0.4}
STOPWORDS = {"the", "a", "an", "of", "and", "or", "to", "on", "in", "for"}
@@ -58,7 +31,6 @@ def find_pages_dir(settings: Path) -> Path:
def discover_files(settings: Path) -> dict[str, Path]:
"""component name -> file path, discovered by walking pages/."""
files: dict[str, Path] = {}
for p in find_pages_dir(settings).rglob("*.qml"):
files[p.stem] = p
@@ -159,12 +131,10 @@ def parse_block(lines: list[str], i: int) -> tuple[str, list[tuple[str, list]],
def collect_page_names(block: tuple[str, list[tuple[str, list]]]) -> list[str]:
name, children = block
# Outer array entry: Component { ... }
if name != "Component":
return [name]
for child_name, child_children in children:
# StackPage { Component { FooPage { } } ... }
if child_name == "StackPage":
out: list[str] = []
for grand_name, grand_children in child_children:
@@ -172,7 +142,6 @@ def collect_page_names(block: tuple[str, list[tuple[str, list]]]) -> list[str]:
out.extend(collect_page_names((grand_name, grand_children)))
return out
# Component { PlaceholderComp { } }
if child_name != "Component":
return [child_name]
@@ -212,8 +181,6 @@ def parse_page_comps(settings: Path) -> list[list[str]]:
def dedup_crumbs(labels: list[str], icons: list[str]) -> tuple[list[str], list[str]]:
"""Drop consecutive duplicate labels (e.g. a section header that repeats the
page name), keeping icons aligned."""
out_labels: list[str] = []
out_icons: list[str] = []
for lbl, ico in zip(labels, icons):
@@ -228,13 +195,10 @@ def build_nav_map(settings: Path, files: dict[str, Path]) -> dict[str, dict]:
comps = parse_page_comps(settings)
registry = parse_page_registry(settings)
# Top-level index -> (icon, label) from PageRegistry (same order as pageComps).
top_meta: dict[int, tuple[str, str]] = {}
for i, (icon, label) in enumerate(registry):
top_meta[i] = (icon, label)
# parentName -> {childPos: (icon, label, section)} from openSubPage() +
# nearby NavRow, remembering the section header the NavRow sits under.
nav_children: dict[str, dict[int, tuple[str, str, str]]] = {}
for names in comps:
for name in names:
@@ -242,8 +206,8 @@ def build_nav_map(settings: Path, files: dict[str, Path]) -> dict[str, dict]:
if not pf:
continue
pending_icon = pending_label = None
section = "" # text of the most recent SectionHeader
expect_section = False # next label line is that header's text
section = ""
expect_section = False
for ln in read_lines(pf):
if SECTION_RE.match(ln):
expect_section = True
@@ -275,24 +239,14 @@ def build_nav_map(settings: Path, files: dict[str, Path]) -> dict[str, dict]:
nav[main] = {"pageIdx": top_idx, "subPath": [],
"crumbIcons": [main_icon], "crumbLabels": [main_label]}
children = dict(nav_children.get(main, {}))
# Components that some other page opens via openSubPage. Those are reached
# through that page (e.g. the bar pages are opened from inside Taskbar's
# "Components" section), so they must not be linked directly here, which
# would give them a wrong, shorter breadcrumb and navigation path.
opened_via_subpage = set()
for owner, kids in nav_children.items():
# Find the group this owner component belongs to.
owner_group = next((ns for ns in comps if owner in ns), None)
if not owner_group:
continue
for kpos in kids:
if kpos < len(owner_group):
opened_via_subpage.add(owner_group[kpos])
# Fallback: a StackPage may list sub-pages (pos > 0) whose openSubPage()
# call lives in a separate component file we don't scan (e.g. the
# Ethernet detail page is opened from EthernetSection.qml). Link any such
# sub-page by its position, deriving a label from its component name -
# but skip ones already reached through another page.
for pos in range(1, len(names)):
if pos not in children and names[pos] not in opened_via_subpage:
label = re.sub(r"(Detail)?Page$", "", names[pos])
@@ -302,8 +256,6 @@ def build_nav_map(settings: Path, files: dict[str, Path]) -> dict[str, dict]:
if pos >= len(names):
continue
child = names[pos]
# Insert the section header (e.g. "Components") as a breadcrumb step
# between the parent page and the sub-page, when present.
labels = [main_label] + ([section] if section else []) + [label]
icons = [main_icon] + ([icon] if section else []) + [icon]
labels, icons = dedup_crumbs(labels, icons)
@@ -325,8 +277,6 @@ def build_nav_map(settings: Path, files: dict[str, Path]) -> dict[str, dict]:
def tokenize(text: str) -> list[str]:
toks: list[str] = []
# Process word by word (split on whitespace) so we only collapse separators
# inside a single word like "Wi-Fi" -> "wifi", not across a whole phrase.
for word in text.lower().split():
parts = [p for p in re.split(r"[^a-z0-9]+", word) if p]
for p in parts:
@@ -350,10 +300,9 @@ def extract_settings(files: dict[str, Path], nav: dict[str, dict]) -> list[dict]
if not pf:
continue
lines = read_lines(pf)
section = "" # text of the most recent SectionHeader
section = ""
i = 0
while i < len(lines):
# Track the current section header so its words are searchable too.
if SECTION_RE.match(lines[i]):
for j in range(i + 1, min(i + 4, len(lines))):
m = LABEL_RE.match(lines[j])
@@ -386,15 +335,12 @@ def extract_settings(files: dict[str, Path], nav: dict[str, dict]) -> list[dict]
tg = ONTOGGLED_RE.match(lines[j])
if tg:
toggled_path = tg.group(1)
# Only expose a toggle path when read and write target the same
# property (symmetric), so flipping from search is safe.
toggle_path = (
checked_path
if row_type == "ToggleRow" and checked_path and checked_path == toggled_path
else ""
)
if label and label not in SKIP_LABELS and anchor:
# keyword sources: breadcrumb path, section header, subtext.
extra = " ".join(meta["crumbLabels"]) + \
" " + section + " " + (subtext or "")
entries.append({
@@ -412,7 +358,6 @@ def extract_settings(files: dict[str, Path], nav: dict[str, dict]) -> list[dict]
def build_inverted_and_ranking(entries: list[dict]):
"""Classic inverted index + precomputed per-token ranking weights."""
inverted: dict[str, list[int]] = defaultdict(list)
ranking: dict[str, dict[int, float]] = defaultdict(dict)
for idx, e in enumerate(entries):
@@ -423,10 +368,8 @@ def build_inverted_and_ranking(entries: list[dict]):
for tok in tokenize(text):
if idx not in inverted[tok]:
inverted[tok].append(idx)
# accumulate the strongest field weight for this token/entry
ranking[tok][idx] = max(ranking[tok].get(idx, 0.0), weight)
seen.add(tok)
# sort each posting list by descending rank so runtime can stop early
for tok, ids in inverted.items():
ids.sort(key=lambda i: ranking[tok][i], reverse=True)
return inverted, {t: {str(k): v for k, v in d.items()} for t, d in ranking.items()}
@@ -442,8 +385,6 @@ def main() -> int:
nav = build_nav_map(settings, files)
entries = extract_settings(files, nav)
inverted, ranking = build_inverted_and_ranking(entries)
# keywords were only needed to build the inverted index; the runtime reads
# the index, not the per-entry keyword blob, so drop it to shrink the JSON.
for e in entries:
e.pop("keywords", None)
out.write_text(json.dumps({