chore: cleanup comments
This commit is contained in:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user