franca/franca.py

203 lines
7.9 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import csv
import os
import shutil
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from jinja2 import Environment, FileSystemLoader, select_autoescape
import yaml
import frontmatter
import markdown2
import fire
import minify_html
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(PatternMatchingEventHandler):
def __init__(self):
PatternMatchingEventHandler.__init__(self, patterns=['*.md', '*.css'],
ignore_directories=True, case_sensitive=False)
def on_modified(self, event):
print(f'event type: {event.event_type} path : {event.src_path}')
franca()
def on_created(self, event):
print(f'event type: {event.event_type} path : {event.src_path}')
franca()
def on_deleted(self, event):
print(f'event type: {event.event_type} path : {event.src_path}')
franca()
# Функция для запуска разработки
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 franca(prod=False):
if not prod:
config['base_url'] = "http://127.0.0.1:8000"
# 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]
date = post_data['date']
posts[language].setdefault(date, {})
posts[language][date].setdefault(section, {})
posts[language][date][section][url] = {
'title': post_data['title'],
'description': description,
'date': date,
'content': templates.from_string(content).render()
}
image = post_data.get('image', None)
if image:
os.makedirs(os.path.dirname(f'public{image}'), exist_ok=True)
filename = image.split('/')[-1].split('.')[0]
create_thumbnail(f'assets{image}', f'public/images/{filename}_600.jpg', 600)
posts[language][date][section][url]['image'] = f'/images/{filename}_600.jpg'
create_thumbnail(f'assets{image}', f'public/images/{filename}_400.jpg', 400)
posts[language][date][section][url]['thumbnail'] = f'/images/{filename}_400.jpg'
posts[language] = dict(sorted(posts[language].items(), reverse=True))
for date, sections in posts[language].items():
for section, urls in sections.items():
if section != "/":
html = base.render(config=config, section=section,
language=language, posts=posts)
write_file(f"public/{language}/{section}/index.html", minify_html.minify(html, minify_js=True))
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", minify_html.minify(html, minify_js=True))
html = base.render(config=config, posts=posts,
language=language, home=True)
write_file(f"public/{language}/index.html", minify_html.minify(html, minify_js=True))
# copy images/css/js from theme
shutil.copytree(f"themes/{config['theme']}/static/images", 'public/images', dirs_exist_ok=True)
css = read_file(f"themes/{config['theme']}/static/css/style.css")
minify_css('public/css/style.css', css)
if 'css_includes' in config:
for include in config['css_includes']:
css = read_file(f"themes/{config['theme']}/static/css/{include}")
minify_css(f'public/css/{include}', 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', minify_html.minify(html, minify_js=True))
# Write robots.txt
robots_content = "User-agent: *"
if not config.get('search_engines', None) == "allow":
robots_content += "\nDisallow: /"
write_file('public/robots.txt', robots_content)
global running
if not running:
if 'pagefind' in config:
os.system("npx pagefind --source public") # build search index
develop(prod)
if __name__ == '__main__':
fire.Fire(franca)