feat: added a twist
This commit is contained in:
112
backend/pages.py
112
backend/pages.py
@@ -1,9 +1,12 @@
|
||||
import os
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
import uframe
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
PAGES_DIR = Path(os.environ.get("PAGES_DIR", "/data/pages"))
|
||||
@@ -21,20 +24,42 @@ class PageMeta(BaseModel):
|
||||
|
||||
class PageDetail(BaseModel):
|
||||
name: str
|
||||
markdown: str | None = None
|
||||
micron: str | None = None
|
||||
source: str | None = None
|
||||
|
||||
|
||||
class SaveRequest(BaseModel):
|
||||
markdown: str
|
||||
source: str
|
||||
publish: bool = False
|
||||
|
||||
|
||||
def _extract_title(markdown: str) -> str | None:
|
||||
for line in markdown.splitlines():
|
||||
def _extract_title(source: str) -> str | None:
|
||||
"""Extract title from µFrame source or legacy Micron."""
|
||||
for line in source.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("# "):
|
||||
return stripped[2:].strip()
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
# µFrame: page "Title" [width]
|
||||
if stripped.lower().startswith("page "):
|
||||
try:
|
||||
parts = shlex.split(stripped)
|
||||
if len(parts) >= 2:
|
||||
return parts[1]
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
# µFrame: heading 1 "Title"
|
||||
if stripped.lower().startswith("heading "):
|
||||
try:
|
||||
parts = shlex.split(stripped)
|
||||
if len(parts) >= 3:
|
||||
return parts[2]
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
# Legacy Micron: >Title
|
||||
if stripped.startswith(">") and not stripped.startswith(">>"):
|
||||
return stripped[1:].strip()
|
||||
break
|
||||
return None
|
||||
|
||||
|
||||
@@ -46,18 +71,29 @@ def _list_all_page_names() -> set[str]:
|
||||
names.add(f.stem)
|
||||
if SOURCES_DIR.is_dir():
|
||||
for f in SOURCES_DIR.iterdir():
|
||||
if f.suffix == ".md" and f.is_file():
|
||||
if f.suffix in (".uf", ".mu") and f.is_file():
|
||||
names.add(f.stem)
|
||||
return names
|
||||
|
||||
|
||||
def _source_path(name: str) -> Path:
|
||||
"""Get source file path, preferring .uf over legacy .mu."""
|
||||
uf = SOURCES_DIR / f"{name}.uf"
|
||||
if uf.is_file():
|
||||
return uf
|
||||
mu = SOURCES_DIR / f"{name}.mu"
|
||||
return mu if mu.is_file() else uf # default to .uf for new files
|
||||
|
||||
|
||||
def _page_meta(name: str) -> PageMeta:
|
||||
src_path = _source_path(name)
|
||||
mu_path = PAGES_DIR / f"{name}.mu"
|
||||
md_path = SOURCES_DIR / f"{name}.md"
|
||||
|
||||
title = None
|
||||
if md_path.is_file():
|
||||
title = _extract_title(md_path.read_text(encoding="utf-8"))
|
||||
if src_path.is_file():
|
||||
title = _extract_title(src_path.read_text(encoding="utf-8"))
|
||||
elif mu_path.is_file():
|
||||
title = _extract_title(mu_path.read_text(encoding="utf-8"))
|
||||
|
||||
published = mu_path.is_file()
|
||||
last_modified = mu_path.stat().st_mtime if published else None
|
||||
@@ -67,7 +103,7 @@ def _page_meta(name: str) -> PageMeta:
|
||||
name=name,
|
||||
title=title,
|
||||
published=published,
|
||||
has_source=md_path.is_file(),
|
||||
has_source=src_path.is_file(),
|
||||
last_modified=last_modified,
|
||||
size=size,
|
||||
)
|
||||
@@ -80,16 +116,19 @@ async def list_pages():
|
||||
|
||||
@router.get("/pages/{name}", response_model=PageDetail)
|
||||
async def get_page(name: str):
|
||||
md_path = SOURCES_DIR / f"{name}.md"
|
||||
src_path = _source_path(name)
|
||||
mu_path = PAGES_DIR / f"{name}.mu"
|
||||
|
||||
if not md_path.is_file() and not mu_path.is_file():
|
||||
if not src_path.is_file() and not mu_path.is_file():
|
||||
raise HTTPException(status_code=404, detail="Page not found")
|
||||
|
||||
markdown = md_path.read_text(encoding="utf-8") if md_path.is_file() else None
|
||||
micron = mu_path.read_text(encoding="utf-8") if mu_path.is_file() else None
|
||||
source = (
|
||||
src_path.read_text(encoding="utf-8")
|
||||
if src_path.is_file()
|
||||
else mu_path.read_text(encoding="utf-8")
|
||||
)
|
||||
|
||||
return PageDetail(name=name, markdown=markdown, micron=micron)
|
||||
return PageDetail(name=name, source=source)
|
||||
|
||||
|
||||
@router.post("/pages/{name}", response_model=PageMeta)
|
||||
@@ -97,39 +136,40 @@ async def save_page(name: str, req: SaveRequest):
|
||||
SOURCES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
PAGES_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Always save markdown source
|
||||
md_path = SOURCES_DIR / f"{name}.md"
|
||||
md_path.write_text(req.markdown, encoding="utf-8")
|
||||
# Save source as .uf
|
||||
src_path = SOURCES_DIR / f"{name}.uf"
|
||||
src_path.write_text(req.source, encoding="utf-8")
|
||||
|
||||
# Optionally publish
|
||||
# Remove legacy .mu source if it exists
|
||||
legacy_mu = SOURCES_DIR / f"{name}.mu"
|
||||
if legacy_mu.is_file():
|
||||
legacy_mu.unlink()
|
||||
|
||||
# Publish: compile .uf → .mu and write to pages dir
|
||||
if req.publish:
|
||||
try:
|
||||
from md2txt import convert_markdown
|
||||
|
||||
micron = convert_markdown(
|
||||
req.markdown,
|
||||
width=80,
|
||||
renderer_name="micron",
|
||||
)
|
||||
result = uframe.compile(req.source)
|
||||
mu_path = PAGES_DIR / f"{name}.mu"
|
||||
mu_path.write_text(result.micron, encoding="utf-8")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Conversion failed: {e}")
|
||||
|
||||
mu_path = PAGES_DIR / f"{name}.mu"
|
||||
mu_path.write_text(micron, encoding="utf-8")
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=f"Compile failed during publish: {e}",
|
||||
)
|
||||
|
||||
return _page_meta(name)
|
||||
|
||||
|
||||
@router.delete("/pages/{name}")
|
||||
async def delete_page(name: str):
|
||||
md_path = SOURCES_DIR / f"{name}.md"
|
||||
src_path = _source_path(name)
|
||||
mu_path = PAGES_DIR / f"{name}.mu"
|
||||
|
||||
if not md_path.is_file() and not mu_path.is_file():
|
||||
if not src_path.is_file() and not mu_path.is_file():
|
||||
raise HTTPException(status_code=404, detail="Page not found")
|
||||
|
||||
if md_path.is_file():
|
||||
md_path.unlink()
|
||||
if src_path.is_file():
|
||||
src_path.unlink()
|
||||
if mu_path.is_file():
|
||||
mu_path.unlink()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user