"""
HTML Generation Library
Atomic functions for loading themes and rendering HTML from templates.
"""
import json
from pathlib import Path
from typing import Dict, List, Optional
from datetime import datetime
import jinja2
class html_generation_lib:
"""Atomic HTML generation functions"""
@staticmethod
def load_theme(theme_name: str, themes_dir: str = './themes') -> Dict:
"""
Load theme configuration and templates.
Returns:
Dict with theme config, template paths, and metadata
"""
theme_dir = Path(themes_dir) / theme_name
theme_config_path = theme_dir / 'theme.json'
if not theme_config_path.exists():
raise FileNotFoundError(f"Theme config not found: {theme_config_path}")
with open(theme_config_path, 'r') as f:
config = json.load(f)
# Load template files
templates = {}
if 'templates' in config:
for template_name, template_path in config['templates'].items():
full_path = Path(template_path)
if full_path.exists():
with open(full_path, 'r') as f:
templates[template_name] = f.read()
config['loaded_templates'] = templates
config['theme_dir'] = str(theme_dir)
return config
@staticmethod
def render_template(template_string: str, data: Dict) -> str:
"""
Render template string with data using Jinja2 templating.
Handles nested expressions and complex logic better.
Args:
template_string: Template with {{variable}} placeholders
data: Dict of data to inject
Returns:
Rendered HTML string
"""
# Add helper functions to data context
context = {
**data,
'formatTime': html_generation_lib.format_time,
'formatTimeAgo': html_generation_lib.format_time_ago,
'formatDateTime': html_generation_lib.format_datetime,
'truncate': html_generation_lib.truncate,
'renderMarkdown': html_generation_lib.render_markdown,
'escapeHtml': html_generation_lib.escape_html
}
# Extract template content from tag if present
if ']*>(.*?)', template_string, re.DOTALL)
if match:
template_string = match.group(1)
# Use Jinja2 for template rendering
try:
template = jinja2.Template(template_string)
return template.render(**context)
except Exception as e:
print(f"Template rendering error: {e}")
return f""
@staticmethod
def render_post(post: Dict, theme: Dict, comments: Optional[List[Dict]] = None) -> str:
"""
Render single post to HTML using theme's post/card/detail template.
Args:
post: Post data dict
theme: Theme config with loaded templates
comments: Optional list of comments to render with post
Returns:
Rendered HTML string
"""
# Choose template (prefer 'detail' if comments, else 'card')
template_name = 'detail' if comments else 'card'
if template_name not in theme.get('loaded_templates', {}):
template_name = 'card' # Fallback
template = theme['loaded_templates'].get(template_name)
if not template:
return f""
# Render comments if provided
comments_section = ''
if comments:
comments_section = html_generation_lib.render_comment_tree(comments, theme)
# Create post data with comments_section
post_data = dict(post)
post_data['comments_section'] = comments_section
# Render post
return html_generation_lib.render_template(template, post_data)
@staticmethod
def render_post_page(post: Dict, theme: Dict, comments: Optional[List[Dict]] = None) -> str:
"""
Render single post as a complete HTML page with navigation.
Args:
post: Post data dict
theme: Theme config with loaded templates
comments: Optional list of comments to render with post
Returns:
Complete HTML page string
"""
# Render the post content
post_content = html_generation_lib.render_post(post, theme, comments)
# Build CSS links
css_links = ''
if theme.get('css_dependencies'):
for css_path in theme['css_dependencies']:
adjusted_path = css_path.replace('./themes/', '../../themes/')
css_links += f' \n'
# Build JS scripts
js_scripts = ''
if theme.get('js_dependencies'):
for js_path in theme['js_dependencies']:
adjusted_path = js_path.replace('./themes/', '../../themes/')
js_scripts += f' \n'
# Create full page
page_html = f'''
{len(posts)} posts{filterset_name.replace('_', ' ').title() if filterset_name else 'All Posts'}
')
html = html.replace('\n', '
')
# Bold and italic
import re
html = re.sub(r'\*\*(.*?)\*\*', r'\1', html)
html = re.sub(r'\*(.*?)\*', r'\1', html)
# Images (must be processed before links since they use similar syntax)
html = re.sub(r'!\[(.*?)\]\((.*?)\)', r'', html)
# Links
html = re.sub(r'\[(.*?)\]\((.*?)\)', r'\1', html)
return f'
{html}
' @staticmethod def escape_html(text: str) -> str: """Escape HTML entities""" return (text .replace('&', '&') .replace('<', '<') .replace('>', '>') .replace('"', '"') .replace("'", '''))