import os import shutil import datetime import re import argparse import html import sys SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) DOMAIN_URL = "https://patate.dev" PATHS = { "images_src": SCRIPT_DIR, "images_dest": os.path.abspath(os.path.join(SCRIPT_DIR, "../images")), "pages": os.path.abspath(os.path.join(SCRIPT_DIR, "../pages")), "rss": os.path.abspath(os.path.join(SCRIPT_DIR, "../rss")), "sitemap": os.path.abspath(os.path.join(SCRIPT_DIR, "../sitemap.xml")), "blog_index": os.path.abspath(os.path.join(SCRIPT_DIR, "../pages/blog.html")), "header": os.path.abspath(os.path.join(SCRIPT_DIR, "../pages/header.html")), "footer": os.path.abspath(os.path.join(SCRIPT_DIR, "../pages/footer.html")) } class BlogGenerator: def __init__(self, filepath): self.filepath = filepath self.filename = os.path.basename(filepath) self.content_lines = [] self.metadata = { "foldername": "", "title": "", "description": "", "date_str": "", "date_obj": None, "filename_html": "" } def run(self): print(f"Processing {self.filename}...") if not os.path.exists(PATHS["header"]): print(f"CRITICAL ERROR: header.html not found at {PATHS['header']}") return self.parse_file() missing_fields = [] if not self.metadata["foldername"]: missing_fields.append("[foldername]") if not self.metadata["title"]: missing_fields.append("[title]") if not self.metadata["description"]: missing_fields.append("[description]") if not self.metadata["date_str"]: missing_fields.append("[date]") if missing_fields: print("Error: The following mandatory fields are missing from your text file:") for field in missing_fields: print(f" - {field}") print("Aborting generation.") return self.write_html_output() self.handle_images() self.update_blog_index() self.update_rss() self.update_sitemap() print("Done!") def parse_date(self, date_str): clean_date = date_str.strip() try: return datetime.datetime.strptime(clean_date, "%b %d, %Y") except ValueError: print(f"Warning: Could not parse date '{clean_date}'. Defaulting to NOW.") return datetime.datetime.now() def highlight_code(self, code): """Simple regex-based syntax highlighter for C/C++/Python/Rust.""" code = html.escape(code) code = re.sub(r'(".*?")', r'\1', code) code = re.sub(r"('.*?')", r'\1', code) code = re.sub(r'(//.*)', r'\1', code) code = re.sub(r'(#.*)', r'\1', code) keywords = [ "int", "void", "char", "float", "double", "struct", "class", "if", "else", "while", "for", "return", "switch", "case", "break", "def", "import", "from", "fn", "let", "mut", "pub", "impl", "use", "const", "static", "unsigned", "long", "true", "false", "NULL", "nullptr" ] for kw in keywords: pattern = r'\b(' + kw + r')\b(?![^<]*>)' code = re.sub(pattern, r'\1', code) code = re.sub(r'\b(0x[0-9a-fA-F]+|\d+)\b(?![^<]*>)', r'\1', code) return code def process_inline_tags(self, text): text = re.sub(r'\[data\](.*?)\[data\]', r'\1', text) def replace_link(match): url = match.group(1) label = match.group(2) return f'{label}' text = re.sub(r'\[link\]\[(.*?)\](.*?)\[link\]', replace_link, text) return text def get_template_content(self, path): if os.path.exists(path): with open(path, "r", encoding="utf-8") as f: return f.read() else: print(f"Warning: Template file not found at {path}") return f"" def parse_file(self): with open(self.filepath, "r", encoding="utf-8") as f: lines = f.readlines() is_list = False in_code_block = False code_buffer = [] html_buffer = [] header_content = self.get_template_content(PATHS["header"]) html_buffer.append(header_content) html_buffer.append('
') for line in lines: if line.strip().startswith("[code]"): if in_code_block: raw_code = "".join(code_buffer).strip() highlighted_code = self.highlight_code(raw_code) html_buffer.append(f'
{highlighted_code}
') code_buffer = [] in_code_block = False else: in_code_block = True continue if in_code_block: code_buffer.append(line) continue line = line.strip() if not line: continue if line.startswith("[foldername]"): val = line.replace("[foldername]", "").strip() self.metadata["foldername"] = val self.metadata["filename_html"] = val + ".html" elif line.startswith("[title]"): val = line.replace("[title]", "").strip() self.metadata["title"] = val html_buffer.append(f'

{val}

') elif line.startswith("[description]"): val = line.replace("[description]", "").strip() self.metadata["description"] = val elif line.startswith("[date]"): val = line.replace("[date]", "").strip() self.metadata["date_str"] = val self.metadata["date_obj"] = self.parse_date(val) html_buffer.append(f'') html_buffer.append('
') elif line.startswith("[section]"): val = line.replace("[section]", "").strip() anchor = val.replace(" ", "-") html_buffer.append(f'

{val}

') elif line.startswith("[image]"): val = line.replace("[image]", "").strip() img_path = f'../images/{self.metadata["foldername"]}/{val}' html_buffer.append(f'

') elif line.startswith("[list]"): parts = line.split("[list]") items = [p for p in parts if p.strip()] if not is_list: html_buffer.append("") is_list = False else: if is_list and not line.startswith("[list]"): html_buffer.append("") is_list = False processed_line = self.process_inline_tags(line) html_buffer.append(f"

{processed_line}

") html_buffer.append('
') footer_content = self.get_template_content(PATHS["footer"]) html_buffer.append(footer_content) if "" not in footer_content: html_buffer.append('') if "" not in footer_content: html_buffer.append('') self.content_lines = html_buffer def write_html_output(self): output_path = os.path.join(PATHS["pages"], self.metadata["filename_html"]) with open(output_path, "w", encoding="utf-8") as f: f.write("\n".join(self.content_lines)) print(f"Generated page: {output_path}") def handle_images(self): target_dir = os.path.join(PATHS["images_dest"], self.metadata["foldername"]) if not os.path.exists(target_dir): os.makedirs(target_dir) source_dir = os.path.dirname(os.path.abspath(self.filepath)) for f in os.listdir(source_dir): if f.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.ico')): shutil.copy(os.path.join(source_dir, f), os.path.join(target_dir, f)) def update_blog_index(self): marker = "" entry = f'\t\t
  • {self.metadata["title"]}
  • ' print(f"Updating {PATHS['blog_index']}...") try: with open(PATHS["blog_index"], "r", encoding="utf-8") as f: content = f.read() if self.metadata["filename_html"] in content and self.metadata["title"] in content: print("Link already exists in blog.html. Skipping index update.") return marker_index = content.find(marker) if marker_index == -1: print(f"Warning: '{marker}' marker not found in blog.html") return ul_start_index = content.find("