173 lines
6.6 KiB
Python
173 lines
6.6 KiB
Python
import csv
|
||
import os
|
||
import shutil
|
||
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 markdown2
|
||
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 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='assets', recursive=True)
|
||
observer.schedule(event_handler, path='static', recursive=True)
|
||
observer.schedule(event_handler, path='config.yaml')
|
||
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")
|
||
index = templates.get_template("index.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)
|
||
|
||
|
||
content = markdown2.markdown(post_data.content, extras=['fenced-code-blocks'])
|
||
description = content.partition('<!--more-->')[0]
|
||
content = "{% import 'shortcodes.j2' as shortcodes %}" + content
|
||
|
||
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': templates.from_string(content).render()
|
||
}
|
||
|
||
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)
|
||
|
||
# copy images/css/js from theme
|
||
shutil.copytree(f"themes/{config['theme']}/static/images", 'public/images', dirs_exist_ok=True)
|
||
shutil.copytree(f"themes/{config['theme']}/static/css", 'public/css', dirs_exist_ok=True)
|
||
if 'css_includes' in config:
|
||
for include in config['css_includes']:
|
||
copy_file(f"themes/{config['theme']}/static/css/{include}", 'public/css/')
|
||
if 'js_includes' in config:
|
||
for include in config['js_includes']:
|
||
copy_file(f"themes/{config['theme']}/static/js/{include}", 'public/js/')
|
||
|
||
# copy css/images from site static folder
|
||
if 'custom_css' in config:
|
||
shutil.copytree('static/css', 'public/css', dirs_exist_ok=True)
|
||
|
||
copy_file('static/logo.svg', 'public/')
|
||
copy_file('static/favicon.ico', 'public/')
|
||
|
||
# Write main index.html
|
||
html = index.render(config=config)
|
||
write_file('public/index.html', html)
|
||
# Write robots.txt
|
||
robots_content = "User-agent: *\nDisallow: /"
|
||
if config.get('search_engines', None) == "allow":
|
||
robots_content = robots_content.rstrip("/")
|
||
write_file('public/robots.txt', robots_content)
|
||
|
||
develop(prod)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
fire.Fire(crater)
|