From 1854366a9dbbe260b6f256e1f56aed0d00d2edf9 Mon Sep 17 00:00:00 2001 From: SG Date: Sat, 7 Jun 2025 13:17:51 +0300 Subject: [PATCH] some reworking --- argparser.py | 11 ++++ classes.py | 58 +++++++++++++++++++++ config.py | 13 ++++- functions.py | 42 +++++++++++++++ hydrogen.py | 130 +++++++++++++++++++++-------------------------- logger.py | 2 +- requirements.txt | 1 + 7 files changed, 183 insertions(+), 74 deletions(-) create mode 100644 argparser.py create mode 100644 classes.py create mode 100644 functions.py diff --git a/argparser.py b/argparser.py new file mode 100644 index 0000000..2d9dd4f --- /dev/null +++ b/argparser.py @@ -0,0 +1,11 @@ +import argparse +from config import Config + +argparser = argparse.ArgumentParser( + prog = Config.APP_NAME, + description = Config.APP_DESCRIPTION, + epilog = f"See {Config.APP_SRC_URL} for more info.") + +argparser.add_argument('--init', action='store_true', help = 'Initialize new site') +argparser.add_argument('--content', '--edit', type = str, help = 'Create new content') +argparser.add_argument('--build', action='store_true', help = 'Build the site') \ No newline at end of file diff --git a/classes.py b/classes.py new file mode 100644 index 0000000..f5f7f11 --- /dev/null +++ b/classes.py @@ -0,0 +1,58 @@ +from datetime import datetime +import shutil +import markdown +import frontmatter +from jinja2 import Environment, FileSystemLoader +from pathlib import Path +from logger import logger +from config import Config + +# Prepare template rendering engine +env = Environment(loader=FileSystemLoader(Config.TEMPLATE_DIR)) +env.globals['header_image'] = Config.HEADER_IMAGE +index_template = env.get_template("index.html") +content_item_template = env.get_template("content_item.html") + +class DefaultFrontmatterHeader: + def __init__(self, title): + self.title = f"title: '{title}'\n" + self.date = f"date: '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}'\n" + self.omit_second_title = f"omit_second_title: '{str(False)}'\n" + self.description = "description: \n" + self.author = "author: \n" + +class ContentItemPrototype: + def render_content(self): + logger.debug(f"Rendering {self.source_filename} to {Config.OUTPUT_DIR}/{self.target_filename}") + if self.image_src_file.exists(): + shutil.copyfile(self.image_src_file, Path(Config.OUTPUT_DIR) / self.image) + if self.custom_css_src_file.exists(): + shutil.copyfile(self.custom_css_src_file,Path(Config.OUTPUT_DIR) / self.custom_css) + if self.custom_js_src_file.exists(): + shutil.copyfile(self.custom_js_src_file, Path(Config.OUTPUT_DIR) / self.custom_js) + with self.content_output_path.open("w", encoding="utf-8") as f: + f.write(content_item_template.render(content_item = self, page_title = self.title)) + + def __init__(self, md_file): + logger.debug(f"Parsing {md_file}") + self.source_filename = md_file + self.slug = md_file.stem + self.target_filename = self.slug + ".html" + self.data = frontmatter.load(md_file) + self.html = markdown.markdown(self.data.content) + self.preview = self.html[:300] + "... read more" + self.title = self.data.get("title", self.slug) + self.omit_second_title = self.data.get("omit_second_title", False) + self.date = self.data.get("data", "2000-01-01T00:00:00+03:00") + self.content_output_path = Path(Config.OUTPUT_DIR) / f"{self.slug}.html" + self.image_src_file = Path(f"{Config.CONTENT_DIR}/{md_file.stem}.jpg") + self.image = f"images/{md_file.stem}.jpg" if self.image_src_file.exists() else None + self.custom_css_src_file = Path(f"{Config.CONTENT_DIR}/{md_file.stem}.css") + self.custom_css = f"static/css/{md_file.stem}.css" if self.custom_css_src_file.exists() else None + logger.debug(f"Custom CSS: {self.slug}: {self.custom_css}") + self.custom_js_src_file = Path(f"{Config.CONTENT_DIR}/{md_file.stem}.js") + self.custom_js = f"static/js/{md_file.stem}.js" if self.custom_js_src_file.exists() else None + logger.debug(f"Custom JS: {self.slug}: {self.custom_js}") + # Special handling for the 'about' item + if self.slug == 'about': + self.image = 'static/about.jpg' \ No newline at end of file diff --git a/config.py b/config.py index 5b7ace6..888b62e 100644 --- a/config.py +++ b/config.py @@ -1,7 +1,16 @@ import os # Main config section class Config: + MAIN_PAGE_TITLE = "hydrogen site" + APP_NAME = "hydrogen" - MAIN_PAGE_TITLE = "Selected poems" - OUTPUT_DIR = os.environ.get('OUTPUT_DIR') or 'public' + APP_DESCRIPTION = "Simplistic static site generator" + APP_SRC_URL = f"https://git.exocortex.ru/Exocortex/{APP_NAME}" + OUTPUT_DIR = 'public' + OUTPUT_IMG_DIR = f"{OUTPUT_DIR}/images" + OUTPUT_CSS_DIR = f"{OUTPUT_DIR}/css" + OUTPUT_JS_DIR = f"{OUTPUT_DIR}/js" + TEMPLATE_DIR = 'templates' + CONTENT_DIR = 'content' + STATIC_DIR = 'static' HEADER_IMAGE = 'static/header.jpg' \ No newline at end of file diff --git a/functions.py b/functions.py new file mode 100644 index 0000000..f8e1d43 --- /dev/null +++ b/functions.py @@ -0,0 +1,42 @@ +import os, subprocess +import shutil +from pathlib import Path +from classes import DefaultFrontmatterHeader +from config import Config +from logger import logger + +def create_content(filename): + filename += '.md' + filename = Path(f"{Config.CONTENT_DIR}/{filename}") + if not filename.exists(): + fh = DefaultFrontmatterHeader(filename.stem) + logger.debug(f"Creating new content {filename}") + with filename.open("w", encoding="utf-8") as f: + f.writelines(['---\n', fh.title, fh.date, fh.description, fh.author, fh.omit_second_title, '---\n', '\n', '\n']) + +def edit_content(filename): + logger.debug(f"Editing content {filename}") + editor = os.environ.get('EDITOR', 'vim') + subprocess.call([editor, filename]) + +def init_site(): + logger.debug(f"Initializing new site") + + # Recreate output directory if necessary + output_dir = Path(Config.OUTPUT_DIR) + if output_dir.exists(): + shutil.rmtree(output_dir) + output_dir.mkdir() + + # Create output directory subtree + for dir in [ Path(Config.CONTENT_DIR), Path(Config.STATIC_DIR), Path(Config.TEMPLATE_DIR), + Path(Config.OUTPUT_IMG_DIR), Path(Config.OUTPUT_CSS_DIR), Path(Config.OUTPUT_JS_DIR)]: + dir.mkdir(exist_ok=True) + + # Create "About.md" content item + create_content("about") + +def build_site(): + logger.debug(f"Starting build") + + \ No newline at end of file diff --git a/hydrogen.py b/hydrogen.py index 9dd9040..76019a4 100644 --- a/hydrogen.py +++ b/hydrogen.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 from pathlib import Path -import frontmatter -import markdown -from jinja2 import Environment, FileSystemLoader + import shutil from config import Config from logger import logger +from argparser import argparser +from classes import * +from functions import * content_dir = Path('content') template_dir = Path('templates') @@ -17,82 +18,69 @@ assets_js_dir = Path(output_dir / 'static/js') static_dir = Path('static') header_image = Config.HEADER_IMAGE or '/static/header.jpg' -class ContentItemPrototype: - def render_content(self): - if self.image_src_file.exists(): - shutil.copyfile(self.image_src_file, output_dir / self.image) - if self.custom_css_src_file.exists(): - shutil.copyfile(self.custom_css_src_file,output_dir / self.custom_css) - if self.custom_js_src_file.exists(): - shutil.copyfile(self.custom_js_src_file, output_dir / self.custom_js) - with self.content_output_path.open("w", encoding="utf-8") as f: - f.write(content_item_template.render(content_item = self, page_title = self.title)) - def __init__(self, md_file): - self.slug = md_file.stem - self.data = frontmatter.load(md_file) - self.html = markdown.markdown(self.data.content) - self.preview = self.html[:300] + "... read more" - self.title = self.data.get("title", self.slug) - self.omit_second_title = self.data.get("omit_second_title", False) - self.date = self.data.get("data", "2000-01-01T00:00:00+03:00") - self.content_output_path = output_dir / f"{self.slug}.html" - self.image_src_file = Path(f"{content_dir}/{md_file.stem}.jpg") - self.image = f"images/{md_file.stem}.jpg" if self.image_src_file.exists() else None - self.custom_css_src_file = Path(f"{content_dir}/{md_file.stem}.css") - self.custom_css = f"static/css/{md_file.stem}.css" if self.custom_css_src_file.exists() else None - logger.debug(f"Custom CSS: {self.slug}: {self.custom_css}") - self.custom_js_src_file = Path(f"{content_dir}/{md_file.stem}.js") - self.custom_js = f"static/js/{md_file.stem}.js" if self.custom_js_src_file.exists() else None - logger.debug(f"Custom JS: {self.slug}: {self.custom_js}") - # Special handling for the 'about' item - if self.slug == 'about': - self.image = 'static/about.jpg' -logger.info(f"Building the '{Config.MAIN_PAGE_TITLE}' site.") -# Recreate the output dir if needed -if output_dir.exists(): - shutil.rmtree(output_dir) -output_dir.mkdir() -# Create public subdirs -subdirs = [img_dir, assets_css_dir, assets_js_dir] -for subdir in subdirs: - subdir.mkdir(parents=True, exist_ok=True) -# Copy static files if exist -if static_dir.exists(): - shutil.copytree(static_dir, output_dir / "static", dirs_exist_ok=True) -# Prepare template rendering engine -env = Environment(loader=FileSystemLoader(str(template_dir))) -env.globals['header_image'] = header_image -index_template = env.get_template("index.html") -content_item_template = env.get_template("content_item.html") -# Parse the content files -content_items = [] -for md_file in content_dir.glob("*.md"): - current_content_item = ContentItemPrototype(md_file) - current_content_item.render_content() - content_items.append({ - "slug": current_content_item.slug, - "title": current_content_item.title, - "date": current_content_item.date, - "preview": markdown.markdown(current_content_item.preview), - "image": current_content_item.image, - }) +def build_site(): + logger.info(f"Building the '{Config.MAIN_PAGE_TITLE}' site.") -# Render the index file -with (output_dir / "index.html").open("w", encoding="utf-8") as f: - f.write(index_template.render(page_title = Config.MAIN_PAGE_TITLE, content_items=content_items)) + # Recreate the output dir if needed + if output_dir.exists(): + shutil.rmtree(output_dir) + output_dir.mkdir() -# Render the about file -about_content = ContentItemPrototype(Path('static/about.md')) -about_content.render_content() + # Create public subdirs + subdirs = [img_dir, assets_css_dir, assets_js_dir] + for subdir in subdirs: + subdir.mkdir(parents=True, exist_ok=True) -# Move 'robots.txt' into output_dir -shutil.copyfile(static_dir / 'robots.txt', output_dir / 'robots.txt') + # Copy static files if exist + if static_dir.exists(): + shutil.copytree(static_dir, output_dir / "static", dirs_exist_ok=True) -logger.info(f"Created {len(content_items)} content items.") \ No newline at end of file + # Parse the content files + content_items = [] + for md_file in content_dir.glob("*.md"): + current_content_item = ContentItemPrototype(md_file) + current_content_item.render_content() + content_items.append({ + "slug": current_content_item.slug, + "title": current_content_item.title, + "date": current_content_item.date, + # "preview": markdown.markdown(current_content_item.preview), + "image": current_content_item.image, + }) + + # Render the index file + with (output_dir / "index.html").open("w", encoding="utf-8") as f: + f.write(index_template.render(page_title = Config.MAIN_PAGE_TITLE, content_items=content_items)) + + # Render the about file + about_content = ContentItemPrototype(Path('static/about.md')) + about_content.render_content() + + # Move 'robots.txt' into output_dir + shutil.copyfile(static_dir / 'robots.txt', output_dir / 'robots.txt') + + logger.info(f"Created {len(content_items)} content items.") + + + + +def main(): + args = argparser.parse_args() + if args.content: + content = args.content or args.edit + create_content(content) + edit_content(content) + if args.build: + build_site() + if args.init: + init_site() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/logger.py b/logger.py index a3a59b7..c413738 100644 --- a/logger.py +++ b/logger.py @@ -4,7 +4,7 @@ from config import Config # Logging config section LOG_TO = sys.stdout -LOG_LEVEL = logging.INFO +LOG_LEVEL = logging.DEBUG logger = logging.getLogger(Config.APP_NAME) logger.setLevel(LOG_LEVEL) diff --git a/requirements.txt b/requirements.txt index d822853..d0d8a2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +argparse python-frontmatter jinja2 markdown