import csv import os import shutil from functools import partial from http.server import HTTPServer, SimpleHTTPRequestHandler from pathlib import Path from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from jinja2 import Environment, FileSystemLoader, select_autoescape import yaml import frontmatter import markdown import fire from common.functions import * def load_translations(file_path, _dict): with open(file_path) as f: reader = csv.DictReader(f, delimiter='|') # for row in reader: # проходим по строкам csv, каждая из которых является словарём _dict[row['id']] = row # добавляем ключ - значение ключа id, значение - словарь row del row['id'] # удаляем ключ по названием "id" из словаря row, так как его значение уже является ключом в словаре _dict # Функции, доступные в теме def translate(id, language): global translations return translations[id][language] translations = {} # здесь будут переводы от темы и от сайта config = yaml.safe_load(read_file('config.yaml')) # Чиатем конфиг сайта running = False # нужно для проверки # класс для watchdog. # При обнаружении изменений в папках content и themes/{config['theme']}, перегенерировать папку public class Develop(FileSystemEventHandler): def on_modified(self, event): print(f'event type: {event.event_type} path : {event.src_path}') crater() def on_created(self, event): print(f'event type: {event.event_type} path : {event.src_path}') crater() def on_deleted(self, event): print(f'event type: {event.event_type} path : {event.src_path}') crater() # Функция для запуска веб-сервера def start_httpd(directory: Path, port: int = 8000): print(f"Listen on port {port}, serving from {directory}...") handler = partial(SimpleHTTPRequestHandler, directory=directory) httpd = HTTPServer(('localhost', port), handler) httpd.serve_forever() # Функция для запуска разработки def develop(prod): global running if not prod and not running: event_handler = Develop() observer = Observer() observer.schedule(event_handler, path='content', recursive=True) observer.schedule(event_handler, path=f"themes/{config['theme']}", recursive=True) observer.start() running = True try: start_httpd(directory='public') except KeyboardInterrupt: pass # Функция для генерации сайта def crater(prod=False): # Load theme's jinja templates templates = Environment(loader=FileSystemLoader( f"themes/{config['theme']}/templates/"), autoescape=select_autoescape()) # Load base jinja template base = templates.get_template("base.j2") # Load translations global translations load_translations(f"themes/{config['theme']}/i18n.csv", translations) load_translations("i18n.csv", translations) # Add functions to base template base.globals.update({"translate": translate}) # Remove public folder if exists before generating new content shutil.rmtree('public', ignore_errors=True) posts = {} # Create new public folder for language in config['languages']: content_dir = Path(f"content/{language}") posts[language] = {} # Create posts dict for item in list(content_dir.rglob("*.md")): post_data = frontmatter.loads(read_file(item)) post_path = str(item).replace( "content", "public").rstrip(".md") os.makedirs(post_path, exist_ok=True) description = post_data.content.partition('')[0] content = "{% import 'shortcodes.j2' as shortcodes %}" + \ markdown.markdown(post_data.content) content = templates.from_string(content).render() url = post_path.replace(f"public/{language}", "") section = "/" if len(url.split('/')) == 2 else url.split('/')[1] posts[language].setdefault(section, {}) posts[language][section][url] = { 'title': post_data['title'], 'description': description, 'date': post_data['date'], 'content': content } image = post_data.get('image', None) if image: posts[language][section][url]['image'] = image copy_file(f'assets{image}', f'public{image}') for section, urls in posts[language].items(): if section != "/": html = base.render(config=config, section=section, language=language, posts=posts) write_file(f"public/{language}/{section}/index.html", html) for url, post in urls.items(): html = base.render(config=config, post=post, language=language, url=url, posts=posts) write_file(f"public/{language}{url}/index.html", html) html = base.render(config=config, posts=posts, language=language, home=True) write_file(f"public/{language}/index.html", html) for source_folder in ('static', f"themes/{config['theme']}/static"): shutil.copytree(source_folder, 'public', dirs_exist_ok=True) develop(prod) if __name__ == '__main__': fire.Fire(crater)