Lets test

This commit is contained in:
2024-12-11 23:56:15 +01:00
parent 379f6a4273
commit 82228fdb27
27 changed files with 545 additions and 151 deletions

0
app/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,56 @@
import os
import json
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
class CategoryController:
def __init__(self):
"""Initialize the controller."""
self.router = APIRouter()
self.templates = Jinja2Templates(directory="templates")
self.data = self._load_mock_data()
self._add_routes()
def _load_mock_data(self):
"""Load mock data from a JSON file."""
with open("mock_data.json") as file:
return json.load(file)
def _add_routes(self):
"""Add routes to the router."""
self.router.add_api_route("/", self.get_index, methods=["GET"], response_class=HTMLResponse)
self.router.add_api_route(
"/category/{category_name}",
self.get_category,
methods=["GET"],
response_class=HTMLResponse,
)
async def get_index(self, request: Request):
"""Index route."""
return self.templates.TemplateResponse(
"index.html",
{"request": request, "data": self.data, "page_title": "Forside", "author": "Henrik"},
)
async def get_category(self, request: Request, category_name: str):
"""Category route."""
category = next((cat for cat in self.data["categories"] if cat["path"] == category_name), None)
if category:
category_file = f"data/{category_name}/index.html"
if os.path.exists(category_file):
with open(category_file) as file:
category_content = file.read()
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"data": self.data,
"page_title": category["name"],
"author": category["author"],
"content": category_content,
},
)
return HTMLResponse("Kategori ikke fundet", status_code=404)

View File

@@ -0,0 +1,65 @@
import os
import json
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, Response
from fastapi.templating import Jinja2Templates
class DynamicController:
def __init__(self, data_dir: str):
"""Initialize the dynamic controller."""
self.router = APIRouter()
self.templates = Jinja2Templates(directory="templates")
self.data_dir = data_dir
self.data = self._load_mock_data()
self._add_dynamic_routes()
def _load_mock_data(self):
"""Load mock data from a JSON file."""
with open("mock_data.json") as file:
return json.load(file)
def _add_dynamic_routes(self):
"""Scan data directory and create dynamic routes."""
for root, dirs, files in os.walk(self.data_dir):
for directory in dirs:
route_path = f"/{directory}" # Create route based on directory name
directory_path = os.path.join(root, directory)
# Register route dynamically
self.router.add_api_route(
route_path,
self._serve_dynamic_template(directory, directory_path),
methods=["GET"],
response_class=HTMLResponse,
)
def _serve_dynamic_template(self, route_name: str, directory_path: str):
"""Closure to serve templates for each route."""
async def route_handler(request: Request):
# Look for index.html or render fallback content
index_html = os.path.join(directory_path, "index.html")
if os.path.exists(index_html):
with open(index_html, "r", encoding="utf-8") as file:
content = file.read()
# Find the author for this route from preloaded data
for category in self.data.get("categories", []):
if category["path"] == route_name:
author_name = category["author"]
break
# Pass required data to the template
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"page_title": route_name.capitalize(),
"content": content,
"author": author_name,
"data": self.data, # Pass additional data if needed
},
)
# Fallback: Return a 404 if no content is found
return Response(f"No content found for {route_name}", status_code=404)
return route_handler

53
app/main.py Normal file
View File

@@ -0,0 +1,53 @@
from fastapi import FastAPI
from contextlib import asynccontextmanager
from fastapi.staticfiles import StaticFiles
from app.services.markdown_processor import MarkdownProcessor
from app.services.metadata_processor import MetadataProcessor
from app.controllers.dynamic_controller import DynamicController
from app.controllers.category_controller import CategoryController
class Application:
def __init__(self):
"""Initialize the FastAPI app and configure it."""
self.app = FastAPI(lifespan=self._lifespan_event)
self._setup_static_files()
self._include_routers()
@asynccontextmanager
async def _lifespan_event(self, app: FastAPI):
"""Lifespan event for startup and shutdown logic."""
print("App startup: Processing Markdown files...")
# Process Markdown files into HTML
processor = MarkdownProcessor(input_dir="./data", templates_dir="./templates")
processor.run()
# Generate dynamic JSON data
metadata_processor = MetadataProcessor(input_dir="./data", output_file="generated_data.json")
metadata_processor.generate_json()
print("Generated dynamic data file.")
print("Markdown processing complete!")
yield
print("App shutdown: Cleanup complete.")
def _setup_static_files(self):
"""Mount static file directories."""
self.app.mount("/data", StaticFiles(directory="data"), name="data")
self.app.mount("/static", StaticFiles(directory="static"), name="static")
def _include_routers(self):
"""Include all route controllers."""
category_controller = CategoryController()
dynamic_controller = DynamicController("./data")
self.app.include_router(category_controller.router)
self.app.include_router(dynamic_controller.router)
def get_app(self):
"""Return the FastAPI app instance."""
return self.app
application = Application()
app = application.get_app()

0
app/services/__init__.py Normal file
View File

View File

@@ -0,0 +1,87 @@
import os
from app.services.markdown_render import render_markdown_with_jinja # Your custom renderer
from jinja2 import Environment, FileSystemLoader
class MarkdownProcessor:
"""
A class to process Markdown files, extract metadata, and generate a single
'index.html' per category directory using a custom rendering engine.
"""
def __init__(self, input_dir: str, templates_dir: str):
"""
Initialize the MarkdownProcessor.
Args:
input_dir (str): Root directory containing category subdirectories.
templates_dir (str): Directory containing Jinja2 templates.
"""
self.input_dir = input_dir
self.env = Environment(loader=FileSystemLoader(templates_dir))
def _process_markdown_files_in_directory(self, directory_path: str) -> list:
"""
Process all Markdown files in a directory using Markdown and Jinja2 custom tags.
Args:
directory_path (str): Path to the category directory.
Returns:
list: A list of processed sections containing metadata and rendered content.
"""
sections = []
for file in sorted(os.listdir(directory_path)):
if file.endswith(".md"):
file_path = os.path.join(directory_path, file)
with open(file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read()
# Process Markdown and Jinja2
rendered_content, metadata = render_markdown_with_jinja(markdown_content)
# Append the section to the list
sections.append({
"name": metadata.get("title", "Untitled"),
"content": rendered_content,
"summary": metadata.get("summary", ""),
"author": metadata.get("author", "Unknown"),
})
return sections
def _generate_index_html(self, directory_path: str, sections: list, output_file: str):
"""
Generate the index.html file for a category using the combined sections.
Args:
directory_path (str): Path to the category directory.
sections (list): List of processed Markdown content and metadata.
output_file (str): Path to save the generated index.html.
"""
# Render the template with the combined sections
template = self.env.get_template("combined_template.html")
rendered_html = template.render(
title=os.path.basename(directory_path).capitalize(),
sections=sections
)
# Write the rendered HTML to index.html
os.makedirs(directory_path, exist_ok=True)
with open(output_file, "w", encoding="utf-8") as output:
output.write(rendered_html)
print(f"Generated: {output_file}")
def run(self):
"""
Run the Markdown processing workflow: one 'index.html' per category.
"""
for root, dirs, _ in os.walk(self.input_dir):
for directory in dirs:
category_path = os.path.join(root, directory)
output_file = os.path.join(category_path, "index.html")
# Process all Markdown files in the current category directory
sections = self._process_markdown_files_in_directory(category_path)
if sections:
self._generate_index_html(category_path, sections, output_file)

View File

@@ -0,0 +1,70 @@
import markdown
from jinja2 import Environment, DictLoader
# Define Jinja2 custom functions
def img_left_overlay(src):
"""Render an image with overlay."""
return f'''
<div class="img-left-overlay">
<img src="{src}" alt="Overlay Image">
<div class="overlay-text">Overlay Text</div>
</div>
'''
def box(title, content):
"""Render a box component."""
return f'''
<div class="box">
<strong>{title}</strong>
<p>{content}</p>
</div>
'''
def note(content):
"""Render a note component."""
return f'''
<div class="note">
<p>{content}</p>
</div>
'''
def warning(content):
"""Render a warning component."""
return f'''
<div class="warning">
⚠️ <p>{content}</p>
</div>
'''
def create_jinja_environment():
"""Set up Jinja2 environment and register custom components."""
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
env.globals.update({
"img_left_overlay": img_left_overlay,
"box": box,
"note": note,
"warning": warning,
})
return env
def render_markdown_with_jinja(markdown_content: str):
"""
Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
Args:
markdown_content (str): Raw Markdown content.
Returns:
tuple: Rendered HTML content and metadata as a dictionary.
"""
# Step 1: Convert Markdown to HTML and extract metadata
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
html_content = md.convert(markdown_content)
metadata = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
# Step 2: Render the HTML with Jinja2 to apply custom tags
env = create_jinja_environment()
template = env.get_template("base_template")
final_html = template.render(content=html_content)
return final_html, metadata

View File

@@ -0,0 +1,83 @@
import os
import markdown
import json
from typing import List, Dict
class MetadataProcessor:
"""
A class to scan Markdown files, extract front matter metadata,
and generate a structured JSON file.
"""
def __init__(self, input_dir: str, output_file: str):
"""
Initialize the MetadataProcessor.
Args:
input_dir (str): Directory containing Markdown files.
output_file (str): Path to save the generated JSON file.
"""
self.input_dir = input_dir
self.output_file = output_file
self.data = {"categories": [], "favorites": []}
def _extract_metadata(self, file_path: str) -> Dict:
"""
Extract front matter metadata using the 'markdown' package.
Args:
file_path (str): Path to the Markdown file.
Returns:
dict: A dictionary containing the extracted metadata.
"""
with open(file_path, "r", encoding="utf-8") as file:
markdown_content = file.read()
# Initialize Markdown with meta extension
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
md.convert(markdown_content)
# Metadata is stored in md.Meta as a dictionary of lists
meta = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
return meta
def _process_directory(self):
"""
Recursively scan the input directory for Markdown files
and extract metadata to build the JSON structure.
"""
for root, _, files in os.walk(self.input_dir):
for file in files:
if file.endswith(".md"):
file_path = os.path.join(root, file)
metadata = self._extract_metadata(file_path)
if metadata:
# Add to 'categories'
self.data["categories"].append({
"name": metadata.get("name", "Unknown"),
"path": os.path.relpath(root, self.input_dir).replace(os.sep, "/"),
"author": metadata.get("author", "Unknown")
})
# Add to 'favorites' if 'favorite' is true
if metadata.get("favorite") and metadata["favorite"].lower() == "true":
self.data["favorites"].append({
"name": metadata.get("name", "Unknown"),
"image": metadata.get("image", "images/default.jpg"),
"description": metadata.get("summary", "No description provided")
})
def generate_json(self):
"""
Generate the JSON structure and save it to the output file.
"""
self._process_directory()
# Save JSON to file
with open(self.output_file, "w", encoding="utf-8") as json_file:
json.dump(self.data, json_file, indent=4, ensure_ascii=False)
print(f"Generated JSON saved to {self.output_file}")