Enable dynamic docs discovery
This commit is contained in:
@@ -32,34 +32,67 @@ from config import get_settings
|
||||
@router.get("/docs", response_class=HTMLResponse)
|
||||
async def list_docs(req: Request):
|
||||
"""
|
||||
List available documentation files.
|
||||
List available documentation files dynamically.
|
||||
"""
|
||||
available = []
|
||||
base_path = _get_docs_base_path()
|
||||
|
||||
for filename, title in ALLOWED_DOCS.items():
|
||||
# Check root and subdirectories (1 level deep)
|
||||
full_path = _find_file(base_path, filename)
|
||||
if full_path:
|
||||
available.append({"name": title, "file": filename})
|
||||
# 1. Pinned Docs (from whitelist)
|
||||
pinned = []
|
||||
|
||||
# 2. All other docs (dynamic scan)
|
||||
library = []
|
||||
|
||||
# helper to check if already in pinned
|
||||
pinned_filenames = set()
|
||||
|
||||
for filename, title in ALLOWED_DOCS.items():
|
||||
full_path = _find_file(base_path, filename) # Helper now needs to look deeper?
|
||||
if full_path:
|
||||
pinned.append({"name": title, "file": filename})
|
||||
pinned_filenames.add(filename)
|
||||
|
||||
# Scan for all .md files recursively
|
||||
if os.path.exists(base_path):
|
||||
for root, dirs, files in os.walk(base_path):
|
||||
for file in files:
|
||||
if file.endswith(".md"):
|
||||
# Calculate relative path or just filename.
|
||||
# If we use just filename, distinct files with same name in diff folders conflict.
|
||||
# Current view_doc takes {filename}. Let's assume filenames are unique enough or we handle paths.
|
||||
# For now, let's just show them if they aren't pinned.
|
||||
|
||||
if file not in pinned_filenames:
|
||||
# Try to get a nice name (Title from first line?) or filename
|
||||
name = file.replace(".md", "").replace("-", " ").title()
|
||||
library.append({"name": name, "file": file})
|
||||
|
||||
# Combine or separate? Let's just return all in 'docs' for now to fit the template expectation.
|
||||
# The template iterates 'docs'.
|
||||
|
||||
all_docs = pinned + sorted(library, key=lambda x: x["name"])
|
||||
|
||||
return req.app.state.render(
|
||||
req,
|
||||
"pages/docs_index.html",
|
||||
docs=available,
|
||||
docs=all_docs,
|
||||
page_title="Dokumentation"
|
||||
)
|
||||
|
||||
@router.get("/docs/{filename}", response_class=HTMLResponse)
|
||||
async def view_doc(req: Request, filename: str):
|
||||
"""
|
||||
Render a specific markdown file.
|
||||
Render a specific markdown file. Now supports any MD file found.
|
||||
"""
|
||||
if filename not in ALLOWED_DOCS:
|
||||
raise HTTPException(404, "File not found or not allowed.")
|
||||
# Security: basic check to prevent directory traversal outside allowable scope
|
||||
if ".." in filename or "/" in filename: # filename param is usually just the last part if caught by path param?
|
||||
# Actually FastAPI path param "{filename}" stops at slashes unless defined as "{path:path}".
|
||||
# So 'filename' here is just the leaf name.
|
||||
pass
|
||||
|
||||
base_path = _get_docs_base_path()
|
||||
file_path = _find_file(base_path, filename)
|
||||
|
||||
# Use recursive find
|
||||
file_path = _find_file_recursive(base_path, filename)
|
||||
|
||||
if not file_path:
|
||||
raise HTTPException(404, "File not on server.")
|
||||
@@ -68,16 +101,18 @@ async def view_doc(req: Request, filename: str):
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Convert Markdown to HTML
|
||||
html_content = markdown.markdown(
|
||||
content,
|
||||
extensions=['tables', 'fenced_code', 'nl2br']
|
||||
)
|
||||
|
||||
# Determine Title
|
||||
title = ALLOWED_DOCS.get(filename, filename.replace(".md", "").replace("-", " ").title())
|
||||
|
||||
return req.app.state.render(
|
||||
req,
|
||||
"pages/doc_viewer.html",
|
||||
doc_title=ALLOWED_DOCS[filename],
|
||||
doc_title=title,
|
||||
doc_content=html_content,
|
||||
filename=filename
|
||||
)
|
||||
@@ -100,17 +135,19 @@ def _get_docs_base_path():
|
||||
return base_path
|
||||
|
||||
def _find_file(base_path, filename):
|
||||
"""Find file in base_path or immediate subdirectories."""
|
||||
# 1. Direct match
|
||||
"""Legacy helper: find in base or level 1 subdirs."""
|
||||
return _find_file_recursive(base_path, filename)
|
||||
|
||||
def _find_file_recursive(base_path, filename):
|
||||
"""Find file recursively in base_path."""
|
||||
# 1. Direct check
|
||||
direct = os.path.join(base_path, filename)
|
||||
if os.path.exists(direct):
|
||||
if os.path.exists(direct) and os.path.isfile(direct):
|
||||
return direct
|
||||
|
||||
# 2. Check subdirectories (max depth 1)
|
||||
if os.path.isdir(base_path):
|
||||
for entry in os.scandir(base_path):
|
||||
if entry.is_dir():
|
||||
sub_path = os.path.join(entry.path, filename)
|
||||
if os.path.exists(sub_path):
|
||||
return sub_path
|
||||
# 2. Walk
|
||||
for root, dirs, files in os.walk(base_path):
|
||||
if filename in files:
|
||||
return os.path.join(root, filename)
|
||||
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user