Coverage for src/lilbee/data/store/locations.py: 100%
46 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-11-06 08:17 +0000
1"""Where a source is, and the name a person sees for it: the deepest registered root's label."""
3from __future__ import annotations
5from collections.abc import Mapping
6from pathlib import PurePath
8from .lance_helpers import escape_sql_string
9from .types import SourceType
12def root_location(root: str) -> str:
13 """The location of the registered root path *root*: its POSIX spelling."""
14 return PurePath(root).as_posix()
17def _escape_like_wildcards(value: str) -> str:
18 """*value* with the LIKE wildcards and the escape character escaped, matched literally."""
19 return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
22def under_sql(column: str, root: str) -> str:
23 """The predicate for *column* holding *root* or a key below it, in parentheses.
25 ``starts_with`` takes the key literally, so no character in a key is a wildcard.
26 """
27 literal = escape_sql_string(root)
28 return f"({column} = '{literal}' OR starts_with({column}, '{literal}/'))"
31class SourceLocations:
32 """The rule between a stored location and its shown name under one source registry.
34 A location below a registered root shows the label of the deepest such root
35 plus the rest of the path; any other location shows itself.
36 """
38 def __init__(self, roots: Mapping[str, str]) -> None:
39 self.roots = dict(roots)
40 self._at = {label: root_location(root) for label, root in roots.items()}
41 self._deepest_first = sorted(
42 ((root, label) for label, root in self._at.items()),
43 key=lambda pair: len(pair[0]),
44 reverse=True,
45 )
47 @property
48 def deepest_first(self) -> list[tuple[str, str]]:
49 """Each registered root's location and label, a root below another before it."""
50 return list(self._deepest_first)
52 def name(self, location: str) -> str:
53 """The name a person sees for the source at *location*."""
54 for root, label in self._deepest_first:
55 if location == root or location.startswith(f"{root}/"):
56 return label + location[len(root) :]
57 return location
59 def location(self, name: str) -> str:
60 """Where the source named *name* is: its root's location plus the rest, else *name*."""
61 label, _, rest = name.partition("/")
62 root = self._at.get(label)
63 if root is None:
64 return name
65 return f"{root}/{rest}" if rest else root
67 def key(self, name: str, source_type: SourceType) -> str:
68 """What ``_sources.path`` holds for *name*: a document's location, else the name."""
69 return self.location(name) if source_type == SourceType.DOCUMENT else name
71 def named_by_sql(self, column: str, root: str) -> str:
72 """The predicate for *column* holding a location whose name the registered *root* gives."""
73 deeper = [
74 f"NOT {under_sql(column, below)}"
75 for below, _ in self._deepest_first
76 if below.startswith(f"{root}/")
77 ]
78 return " AND ".join([under_sql(column, root), *deeper])
80 def search_filter(self, term: str, path: str, title: str) -> str:
81 """The predicate for a shown name in *path*, or a title in *title*, holding *term*.
83 Case-insensitive. Each root's arm leaves out the roots below it, so a
84 location matches by the one name it shows.
85 """
86 pattern = f"LIKE '%{escape_sql_string(_escape_like_wildcards(term.lower()))}%' ESCAPE '\\'"
87 arms: list[str] = []
88 for root, label in self._deepest_first:
89 shown = f"concat('{escape_sql_string(label)}', substr({path}, {len(root) + 1}))"
90 arms.append(f"{self.named_by_sql(path, root)} AND LOWER({shown}) {pattern}")
91 outside = [f"NOT {under_sql(path, root)}" for root, _ in self._deepest_first]
92 arms.append(" AND ".join([*outside, f"LOWER({path}) {pattern}"]))
93 arms.append(f"LOWER({title}) {pattern}")
94 return "(" + " OR ".join(f"({arm})" for arm in arms) + ")"