Files
hydrogen/classes.py
2025-06-22 12:12:36 +03:00

186 lines
8.9 KiB
Python

from datetime import datetime
import os
import sys
import shutil
from collections import defaultdict
import markdown
import frontmatter
from pathlib import Path
from logger import logger
from config import config
class ContentItem:
def render_content(self, categories, target_file = False):
if target_file:
self.target_file = Path(target_file)
logger.debug(f"Rendering {self.source_file} to {self.target_file}")
if hasattr(config, "footer"):
footer_data = config.footer
else:
footer_data = ''
try:
if self.image_file and self.image_file.exists():
image_targetfile = Path(f"{config.output_dir}/{config.static_dir}/images/{self.image_file.name}")
logger.debug(f"Copying {self.image_file} to {image_targetfile}")
shutil.copyfile(self.image_file, image_targetfile)
self.image_file = f"{self.image_file.stem}.jpg"
if self.audio_file and self.audio_file.exists():
audio_targetfile = Path(f"{config.output_dir}/{config.static_dir}/audio/{self.audio_file.name}")
logger.debug(f"Copying {self.audio_file} to {audio_targetfile}")
shutil.copyfile(self.audio_file, audio_targetfile)
self.audio_file = f"{self.audio_file.stem}.mp3"
if self.css_file and self.css_file.exists():
css_targetfile = Path(f"{config.output_dir}/{config.static_dir}/css/{self.css_file.name}")
logger.debug(f"Copying {self.css_file} to {css_targetfile}")
shutil.copyfile(self.css_file, css_targetfile)
if self.js_file and self.js_file.exists():
js_targetfile = Path(f"{config.output_dir}/{config.static_dir}/js/{self.js_file.name}")
logger.debug(f"Copying {self.js_file} to {js_targetfile}")
shutil.copyfile(self.js_file, js_targetfile)
with self.target_file.open("w", encoding="utf-8") as f:
f.write(config.content_item_template.render(content_item = self, page_title = f"{config.site_name}: {self.title}", parent_path = '../', categories = categories, footer_data = footer_data))
except Exception as e:
logger.error(f"Renderer: {e}")
def parse_content(self):
logger.debug(f"Parsing file {self.source_file}")
try:
self.source_file = Path(self.source_file)
self.parent_dir = self.source_file.parent # Most likely './content'
self.slug = self.source_file.stem
self.target_file = Path(f"{config.output_dir}/{self.source_file.parent}/{self.source_file.stem}.html")
self.data = frontmatter.load(self.source_file)
self.preview = self.data.content.replace('\n', '<br>')[:300]
self.title = self.data.get("title", self.slug)
self.omit_second_title = self.data.get("omit_second_title", False)
self.date = self.data.get("date", "2000-01-01T00:00:00+03:00")
self.categories = [c for c in self.data.get("categories", []) if c != 'default']
self.hidden = self.data.get("hidden", str(False))
self.data.content = self.data.content.replace('\n', ' \n') # For markdown newline rendering
self.html = markdown.markdown(self.data.content)
cover_image_path = Path(f"{self.source_file.parent}/{self.source_file.stem}.jpg")
self.image_file = cover_image_path if cover_image_path.exists() else ""
audio_filepath = Path(f"{self.source_file.parent}/{self.source_file.stem}.mp3")
self.audio_file = audio_filepath if audio_filepath.exists() else ""
css_filepath = Path(f"{self.source_file.parent}/{self.source_file.stem}.css")
self.css_file = css_filepath if css_filepath.exists() else ""
js_filepath = Path(f"{self.source_file.parent}/{self.source_file.stem}.js")
self.js_file = js_filepath if js_filepath.exists() else ""
logger.debug(f"CCC {self.source_file.parts} : {self.source_file.parent} / {self.source_file.stem}.jpg")
except Exception as e:
logger.error(f"Parser error, {e}")
def create_content(self):
with open(self.source_file, mode="w", encoding="utf-8") as f:
f.writelines([
"---\n",
f"title: {self.source_file.stem}\n",
f"date: '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}'\n",
"description: ''\n",
"author: ''\n",
"categories: ['default']\n",
"hidden: False\n",
"omit_second_title: False\n",
"---\n",
"\n\n\n"
])
def __init__(self, file):
self.source_file = file
class Site:
def __init__(self):
self.output_dir = Path(config.output_dir)
self.content_dir = Path(config.content_dir)
self.static_dir = Path(config.static_dir)
self.templates_dir = Path(config.templates_dir)
self.images_dir = Path(f"{config.static_dir}/images")
self.css_dir = Path(f"{config.static_dir}/css")
self.js_dir = Path(f"{config.static_dir}/js")
self.output_dir = Path(config.output_dir)
self.content_items = []
self.categories = defaultdict(list)
def init_site(self):
# Exit if current directory not empty
if os.path.isdir('.') and os.listdir('.'):
logger.error("Current directory is not empty.")
sys.exit(1)
logger.info("Initializing new site")
config.create_default_config()
# Create directories
for subdir in [self.content_dir]:
os.makedirs(subdir, exist_ok=True)
# Copy default theme
shutil.copytree(f"{config.base_dir}/themes/default", "themes/default", dirs_exist_ok=True)
def get_content_items(self):
logger.debug("Getting content items")
self.get_content_items = []
logger.debug(f"Scanning {Path(config.content_dir)}")
for md_file in Path(config.content_dir).glob("*.md"):
content_item = ContentItem(md_file)
content_item.parse_content()
self.content_items.append(content_item)
self.content_items.sort(key=lambda i: (-datetime.fromisoformat(i.date).timestamp(), i.title))
def map_categories(self):
for content_item in self.content_items:
for category in content_item.categories:
if not category == "default":
self.categories[category].append(content_item.slug)
def build(self):
# Recreate the output dir if exists
if self.output_dir.exists():
shutil.rmtree(self.output_dir)
self.output_dir.mkdir()
# Create public subdirs
subdirs = ["categories", "content", "static"]
for subdir in subdirs:
subdir = self.output_dir / subdir
subdir.mkdir(parents=True, exist_ok=True)
# Copy theme's static dir to 'public/static
shutil.copytree(f"{config.base_dir}/themes/{config.theme}/static", f"{self.output_dir}/static", dirs_exist_ok=True)
# Get content items
self.get_content_items()
# Build categories map
self.map_categories()
# Render the content files
for content_item in self.content_items:
content_item.render_content(categories = self.categories)
# Render the about file
about_content = ContentItem(Path(f'themes/{config.theme}/content/about.md'))
about_content.parse_content()
about_content.render_content(categories = self.categories, target_file='public/static/about.html')
# Render the index file
if hasattr(config, "footer"):
footer_data = config.footer
else:
footer_data = ''
visible_content_items = [c for c in self.content_items if c.data.get("hidden") != True]
with (self.output_dir / "index.html").open("w", encoding="utf-8") as f:
f.write(config.index_template.render(page_title = config.site_name, content_items=visible_content_items, categories = self.categories, footer_data = footer_data))
# Render the categories indices
if hasattr(config, "footer"):
footer_data = config.footer
else:
footer_data = ''
visible_content_items = [c for c in self.content_items if c.data.get("hidden") != True]
for category in self.categories:
category_index = Path(f"{self.output_dir}/categories/{category}.html")
category_items = [i for i in visible_content_items if category in i.data.get("categories")]
with (category_index).open(mode="w", encoding="utf-8") as f:
f.write(config.index_template.render(page_title=f"{config.site_name}: [ {category} ]", content_items=category_items, categories = self.categories, parent_path = '../', footer_data = footer_data))
logger.info(f"Created {len(self.content_items)} content items.")