Added children, siblings, and parents as variables. Also % as for.

This commit is contained in:
sarah committed 2026-09-21 23:07:17 +02:00
1 parent 104198036a
commit 0cd98873e0
2 files changed
+109 -4

No files matched your search

+99 -2
View File
@@ -3,12 +3,13 @@ from __future__ import annotations
import re import re
from Core import NormalizeRelativePath, ResolvePath from Core import NormalizeRelativePath, ResolvePath
from Explorer import ClassifyFile, ListDirectory from Explorer import ClassifyFile, ListDirectory, NoteFilePrefix
from Locks import IsPathAccessible from Locks import IsPathAccessible
from Preview import ReadTextFile from Preview import ReadTextFile
PortalFileTypes = {"markdown", "text"} PortalFileTypes = {"markdown", "text"}
LoopSources = ("children", "siblings", "parents")
MarkerPattern = re.compile( MarkerPattern = re.compile(
r"\{(?:(?P<CrossPath>[^\s{}']+)\s+)?\*\*\*(?P<CrossName>[^\s*{}]+)" r"\{(?:(?P<CrossPath>[^\s{}']+)\s+)?\*\*\*(?P<CrossName>[^\s*{}]+)"
@@ -19,6 +20,11 @@ MarkerPattern = re.compile(
r"|\{\*(?P<BlockName>[^\s*{}]+)\}" r"|\{\*(?P<BlockName>[^\s*{}]+)\}"
) )
LoopMarkerPattern = re.compile(
r"\{(?P<LoopSource>children|siblings|parents)\s+%\s+(?P<LoopVar>[A-Za-z_][A-Za-z0-9_]*)\}"
r"|\{(?P<CloseSource>children|siblings|parents)\s+%\}"
)
def _LineBounds(Content: str, Position: int) -> tuple[int, int]: def _LineBounds(Content: str, Position: int) -> tuple[int, int]:
LineStart = Content.rfind("\n", 0, Position) + 1 LineStart = Content.rfind("\n", 0, Position) + 1
@@ -94,6 +100,95 @@ def _ResolveLocalPortals(Content: str) -> str:
return "".join(Output) return "".join(Output)
def _ListAccessibleEntryNames(
RootName: str, DirectoryRelativePath: str, Username: str, UserGroups: list[str], Exclude: str | None
) -> list[str]:
if not IsPathAccessible(RootName, DirectoryRelativePath, Username, UserGroups):
return []
try:
Entries = ListDirectory(RootName, DirectoryRelativePath, False, False)
except (ValueError, NotADirectoryError, FileNotFoundError):
return []
Names: list[str] = []
for Entry in Entries:
if Entry["Name"] == Exclude:
continue
EntryPath = (
f"{DirectoryRelativePath}/{Entry['Name']}" if DirectoryRelativePath else Entry["Name"]
)
if IsPathAccessible(RootName, EntryPath, Username, UserGroups):
Names.append(Entry["Name"])
return Names
def _ComputeLoopLists(
RootName: str, RelativePath: str, Username: str, UserGroups: list[str]
) -> dict[str, list[str]]:
FileName = RelativePath.rsplit("/", 1)[-1] if RelativePath else ""
ParentDirectory = RelativePath.rsplit("/", 1)[0] if "/" in RelativePath else ""
ParentBaseName = ParentDirectory.rsplit("/", 1)[-1] if ParentDirectory else ""
IsDirectoryNote = FileName.startswith(f"{NoteFilePrefix}{ParentBaseName}.")
Children = (
_ListAccessibleEntryNames(RootName, ParentDirectory, Username, UserGroups, FileName)
if IsDirectoryNote
else []
)
Siblings = _ListAccessibleEntryNames(RootName, ParentDirectory, Username, UserGroups, FileName)
Parents = [Segment for Segment in ParentDirectory.split("/") if Segment]
return {"children": Children, "siblings": Siblings, "parents": Parents}
def _ResolveLoops(Content: str, Lists: dict[str, list[str]]) -> str:
Matches = list(LoopMarkerPattern.finditer(Content))
Output: list[str] = []
Cursor = 0
Index = 0
while Index < len(Matches):
Match = Matches[Index]
if not Match.group("LoopSource"):
Index += 1
continue
Source = Match.group("LoopSource")
LoopVar = Match.group("LoopVar")
CloseMatch = None
for Candidate in Matches[Index + 1 :]:
if Candidate.group("CloseSource") == Source:
CloseMatch = Candidate
break
BodyEnd = CloseMatch.start() if CloseMatch is not None else len(Content)
BodyTemplate = Content[Match.end() : BodyEnd]
Items = Lists.get(Source, [])
VariablePattern = re.compile(rf"%{re.escape(LoopVar)}\b")
Rendered = "\n".join(VariablePattern.sub(Item, BodyTemplate) for Item in Items)
Output.append(Content[Cursor : Match.start()])
Output.append(Rendered)
Cursor = CloseMatch.end() if CloseMatch is not None else len(Content)
if CloseMatch is not None:
Index = Matches.index(CloseMatch) + 1
else:
break
Output.append(Content[Cursor:])
return "".join(Output)
def _ReadAccessiblePortalSource( def _ReadAccessiblePortalSource(
RootName: str, RelativePath: str, Username: str, UserGroups: list[str] RootName: str, RelativePath: str, Username: str, UserGroups: list[str]
) -> str | None: ) -> str | None:
@@ -224,7 +319,9 @@ def _ResolveLineNumberReference(
def ResolvePortals( def ResolvePortals(
RootName: str, RelativePath: str, Content: str, Username: str, UserGroups: list[str] RootName: str, RelativePath: str, Content: str, Username: str, UserGroups: list[str]
) -> str: ) -> str:
LocallyResolved = _ResolveLocalPortals(Content) LoopLists = _ComputeLoopLists(RootName, RelativePath, Username, UserGroups)
WithLoopsResolved = _ResolveLoops(Content, LoopLists)
LocallyResolved = _ResolveLocalPortals(WithLoopsResolved)
RefererDirectory = RelativePath.rsplit("/", 1)[0] if "/" in RelativePath else "" RefererDirectory = RelativePath.rsplit("/", 1)[0] if "/" in RelativePath else ""
Matches = list(MarkerPattern.finditer(LocallyResolved)) Matches = list(MarkerPattern.finditer(LocallyResolved))
+10 -2
View File
@@ -239,7 +239,15 @@ export default function App() {
const Created = await EnsureDirectoryNote(ActiveRoot, DirectoryPath); const Created = await EnsureDirectoryNote(ActiveRoot, DirectoryPath);
HandleSelectDirectoryNote(DirectoryPath, Created.Name, Created.FileType); HandleSelectDirectoryNote(DirectoryPath, Created.Name, Created.FileType);
} catch (Error) { } catch (Error) {
SetErrorMessage(Error.message); HandleNavigateDirectory(DirectoryPath);
}
}
function HandleBreadcrumbNavigate(Path) {
if (!Path) {
HandleNavigateDirectory("");
} else {
OpenDirectoryNote(Path, null);
} }
} }
@@ -333,7 +341,7 @@ export default function App() {
RootName={ActiveRoot} RootName={ActiveRoot}
FilePath={ViewPath} FilePath={ViewPath}
IsDirectory={ViewIsDirectory || Boolean(ViewNoteFilePath)} IsDirectory={ViewIsDirectory || Boolean(ViewNoteFilePath)}
OnNavigate={HandleNavigateDirectory} OnNavigate={HandleBreadcrumbNavigate}
/> />
) : ( ) : (
<span /> <span />