Files
org_roam/search-index.py
Zaine ccdd229a7d
All checks were successful
Build Roam Site / build (push) Successful in 32s
search key nav fixes
2026-05-14 14:51:09 +01:00

194 lines
5.0 KiB
Python
Executable File

import json
import re
from pathlib import Path
# ================================
# REGEX PATTERNS
# ================================
ID_PATTERN = re.compile(r"^:ID:\s*(.+)", re.MULTILINE)
TITLE_PATTERN = re.compile(r"^#\+title:\s*(.+)", re.IGNORECASE | re.MULTILINE)
# Org links:
# [[id:...]]
# [[file:...]]
# [[./...]]
LINK_PATTERN = re.compile(r"\[\[(.*?)\]\]")
# ================================
# PARSING FUNCTIONS
# ================================
def parse_org_file(file_path):
"""Extract ID, title, and links from an Org file."""
content = file_path.read_text(encoding="utf-8", errors="ignore")
id_match = ID_PATTERN.search(content)
title_match = TITLE_PATTERN.search(content)
org_id = id_match.group(1).strip() if id_match else None
title = title_match.group(1).strip() if title_match else "Untitled"
links = extract_links(content)
return org_id, title, links
def extract_links(content):
"""Extract all internal Org links."""
raw_links = LINK_PATTERN.findall(content)
links = []
for link in raw_links:
# Remove description part: [[link][desc]]
if "][" in link:
link = link.split("][")[0]
# ID links
if link.startswith("id:"):
links.append(link.replace("id:", "").strip())
# File links
elif link.startswith("file:"):
path = link.replace("file:", "").strip()
links.append(path)
# Relative links
elif link.endswith(".org"):
links.append(link.strip())
return links
# ================================
# MAIN GENERATOR
# ================================
def generate_search_index(org_dir, out_file):
print("Generating search-index.json with links...")
org_dir = Path(org_dir).expanduser()
out_file = Path(out_file).expanduser()
org_files = [
f for f in org_dir.rglob("*.org")
if not re.search(r"/\.#[^/]+\.org$", str(f))
]
# First pass: collect all files + IDs
file_map = {} # id -> url
path_map = {} # relative path -> url
temp_entries = []
for file_path in org_files:
org_id, title, links = parse_org_file(file_path)
relative = file_path.relative_to(org_dir).with_suffix('')
url = f"/{relative}.html"
if org_id:
file_map[org_id] = url
path_map[str(relative) + ".org"] = url
temp_entries.append({
"title": title,
"url": url,
"id": org_id,
"raw_links": links
})
# Second pass: resolve links → URLs
index = []
for entry in temp_entries:
resolved_links = []
for link in entry["raw_links"]:
# ID link
if link in file_map:
resolved_links.append(file_map[link])
# File link
elif link in path_map:
resolved_links.append(path_map[link])
index.append({
"title": entry["title"],
"url": entry["url"],
"id": entry["id"],
"links": list(set(resolved_links)) # remove duplicates
})
# De-duplicate by ID
seen_ids = set()
unique_index = []
for entry in index:
if entry["id"] and entry["id"] not in seen_ids:
unique_index.append(entry)
seen_ids.add(entry["id"])
out_file.parent.mkdir(parents=True, exist_ok=True)
with open(out_file, "w", encoding="utf-8") as f:
json.dump(unique_index, f, ensure_ascii=False, indent=2)
print(f"search-index.json generated ({len(unique_index)} entries).")
# ================================
# ALL FILES JSON (UNCHANGED)
# ================================
def generate_all_files_json(search_index_file, out_file):
search_index_file = Path(search_index_file).expanduser()
out_file = Path(out_file).expanduser()
with open(search_index_file, "r", encoding="utf-8") as f:
data = json.load(f)
print(f"Loaded {len(data)} entries from {search_index_file}")
data.sort(key=lambda x: x.get("title", "").lower())
grouped = {}
for entry in data:
title = entry.get("title", "")
if not title:
continue
letter = title[0].upper()
grouped.setdefault(letter, []).append({
"id": entry["id"],
"title": title,
"url": entry["url"]
})
output = {
"title": "All Files",
"description": "A complete alphabetical index of all published pages.",
"groups": grouped,
}
with open(out_file, "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print(f"all-files.json generated → {out_file}")
# ================================
# ENTRY POINT
# ================================
def main():
base_dir = Path.home() / "master-folder" / "org_files" / "org_roam"
search_index = base_dir / "output" / "search-index.json"
all_files_json = base_dir / "all-files.json"
generate_search_index(base_dir, search_index)
generate_all_files_json(search_index, all_files_json)
if __name__ == "__main__":
main()