from datetime import datetime import os import sys import shutil from collections import defaultdict import markdown import frontmatter from bs4 import BeautifulSoup 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 # get the scrip directory if getattr(sys, 'frozen', False): # Running fron Pyinstaller-packed binary base_dir = Path(sys._MEIPASS) else: # Running as a plain Python app base_dir = Path(__file__).resolve().parent # Prepare template rendering engine jinja_env_dir = f"{base_dir}/themes/{Config.THEME}/templates" env = Environment(loader=FileSystemLoader(f"{jinja_env_dir}")) env.globals['header_image'] = f"{base_dir}/themes/{Config.THEME}/static/images/header.jpg" index_template = env.get_template("index.html") content_item_template = env.get_template("content_item.html") 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}") 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(content_item_template.render(content_item = self, page_title = self.title, parent_path = '../', categories = categories)) 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.subdir = self.source_file.parent 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') 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 "" 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): logger.info("Initializing new site") # Create directories for subdir in [self.content_dir]: os.makedirs(subdir, exist_ok=True) # copy default theme shutil.copytree(f"{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 needed if self.output_dir.exists(): shutil.rmtree(self.output_dir) self.output_dir.mkdir() # Create public subdirs #subdirs = [self.images_dir, self.css_dir, self.js_dir, self.content_dir, "categories"] 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"{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}/static/about.md')) about_content.parse_content() about_content.render_content(categories = self.categories, target_file='public/static/about.html') # Render the index file 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(index_template.render(page_title = Config.MAIN_PAGE_TITLE, content_items=visible_content_items, categories = self.categories)) # Render the categories indices 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(index_template.render(page_title=category, content_items=category_items, categories = self.categories, parent_path = '../')) logger.info(f"Created {len(self.content_items)} content items.")