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.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', '
')[: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 "" 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.")