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 #from jinja_env import env, content_item_template, index_template from jinja2 import Environment, FileSystemLoader 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.")