franca/franca.py

188 lines
7.4 KiB
Python
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.

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 PIL import Image
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):
# if prod is False, then redefine config.base_url
if prod is False:
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]
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:
os.makedirs(os.path.dirname(f'public{image}'), exist_ok=True)
filename = image.split('/')[-1].split('.')[0]
extension = image.split('/')[-1].split('.')[1]
image = Image.open(f'assets{image}')
image.thumbnail((600, 600))
image.save(f'public/images/{filename}_600.{extension}', optimize=True)
posts[language][section][url]['image'] = f'/images/{filename}_600.{extension}'
image.thumbnail((400, 400))
image.save(f'public/images/{filename}_400.{extension}', optimize=True)
posts[language][section][url]['thumbnail'] = f'/images/{filename}_400.{extension}'
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)
copy_file(f"themes/{config['theme']}/static/css/style.css", 'public/css/')
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)