Compare commits

..

No commits in common. "main" and "0.0.7" have entirely different histories.
main ... 0.0.7

4 changed files with 59 additions and 75 deletions

View File

@ -8,7 +8,7 @@ Example website: https://git.digitalstudium.com/digitalstudium/digitalstudium.co
## Installation (Linux): ## Installation (Linux):
``` ```
curl https://git.digitalstudium.com/attachments/ceaf659d-f161-4ca4-86f9-172993b35e7e -o franca && chmod +x franca && sudo mv franca /usr/local/bin curl https://git.digitalstudium.com/attachments/ea3e7e10-198b-4ae7-823d-2a6168c8d82c -o franca && chmod +x franca && sudo mv franca /usr/local/bin
``` ```
## Usage: ## Usage:
Development: Development:

2
common

@ -1 +1 @@
Subproject commit f551c17705ea4be7369423f5d829264911588523 Subproject commit faba107ed11c0ffb1965710e602cab9a1342baf0

121
franca.py Executable file → Normal file
View File

@ -1,17 +1,17 @@
#!/usr/bin/env python3
import csv import csv
import os import os
import shutil import shutil
from pathlib import Path from pathlib import Path
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler from watchdog.events import FileSystemEventHandler
from jinja2 import Environment, FileSystemLoader, select_autoescape from jinja2 import Environment, FileSystemLoader, select_autoescape
import yaml import yaml
import frontmatter import frontmatter
import markdown2 import markdown2
import fire import fire
import minify_html from PIL import Image
from common.functions import * from common.functions import *
@ -21,8 +21,7 @@ def load_translations(file_path, _dict):
reader = csv.DictReader(f, delimiter='|') # reader = csv.DictReader(f, delimiter='|') #
for row in reader: # проходим по строкам csv, каждая из которых является словарём for row in reader: # проходим по строкам csv, каждая из которых является словарём
_dict[row['id']] = row # добавляем ключ - значение ключа id, значение - словарь row _dict[row['id']] = row # добавляем ключ - значение ключа id, значение - словарь row
del row[ del row['id'] # удаляем ключ по названием "id" из словаря row, так как его значение уже является ключом в словаре _dict
'id'] # удаляем ключ по названием "id" из словаря row, так как его значение уже является ключом в словаре _dict
# Функции, доступные в теме # Функции, доступные в теме
@ -30,31 +29,22 @@ def translate(id, language):
global translations global translations
return translations[id][language] return translations[id][language]
translations = {} # здесь будут переводы от темы и от сайта translations = {} # здесь будут переводы от темы и от сайта
config = yaml.safe_load(read_file('config.yaml')) # Читаем конфиг сайта config = yaml.safe_load(read_file('config.yaml')) # Чиатем конфиг сайта
running = False # нужно для проверки running = False # нужно для проверки
# класс для watchdog.
# класс для watchdog # При обнаружении изменений в папках content и themes/{config['theme']}, перегенерировать папку public
# Во время разработки, при обнаружении изменений в папках content и themes/{config['theme']}, class Develop(FileSystemEventHandler):
# перегенерировать папку public def on_modified(self, event):
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}') print(f'event type: {event.event_type} path : {event.src_path}')
franca() crater()
def on_created(self, event):
def on_created(self, event):
print(f'event type: {event.event_type} path : {event.src_path}') print(f'event type: {event.event_type} path : {event.src_path}')
franca() crater()
def on_deleted(self, event):
def on_deleted(self, event):
print(f'event type: {event.event_type} path : {event.src_path}') print(f'event type: {event.event_type} path : {event.src_path}')
franca() crater()
# Функция для запуска разработки # Функция для запуска разработки
@ -63,11 +53,11 @@ def develop(prod):
if not prod and not running: if not prod and not running:
event_handler = Develop() event_handler = Develop()
observer = Observer() observer = Observer()
observer.schedule(event_handler, path='content', recursive=True) observer.schedule(event_handler, path='content', recursive=True)
observer.schedule(event_handler, path='assets', recursive=True) observer.schedule(event_handler, path='assets', recursive=True)
observer.schedule(event_handler, path='static', recursive=True) observer.schedule(event_handler, path='static', recursive=True)
observer.schedule(event_handler, path='config.yaml') observer.schedule(event_handler, path='config.yaml')
observer.schedule(event_handler, path=f"themes/{config['theme']}", recursive=True) observer.schedule(event_handler, path=f"themes/{config['theme']}", recursive=True)
observer.start() observer.start()
running = True running = True
try: try:
@ -75,10 +65,10 @@ def develop(prod):
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
# Функция для генерации сайта # Функция для генерации сайта
def franca(prod=False): def crater(prod=False):
if not prod: # if prod is False, then redefine config.base_url
if prod is False:
config['base_url'] = "http://127.0.0.1:8000" config['base_url'] = "http://127.0.0.1:8000"
# Load theme's jinja templates # Load theme's jinja templates
templates = Environment(loader=FileSystemLoader( templates = Environment(loader=FileSystemLoader(
@ -113,20 +103,20 @@ def franca(prod=False):
"content", "public").rstrip(".md") "content", "public").rstrip(".md")
os.makedirs(post_path, exist_ok=True) os.makedirs(post_path, exist_ok=True)
content = markdown2.markdown(post_data.content, extras=['fenced-code-blocks']) content = markdown2.markdown(post_data.content, extras=['fenced-code-blocks'])
description = content.partition('<!--more-->')[0] description = content.partition('<!--more-->')[0]
content = "{% import 'shortcodes.j2' as shortcodes %}" + content content = "{% import 'shortcodes.j2' as shortcodes %}" + content
url = post_path.replace(f"public/{language}", "") url = post_path.replace(f"public/{language}", "")
section = "/" if len(url.split('/')) == 2 else url.split('/')[1] 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] = { posts[language].setdefault(section, {})
posts[language][section][url] = {
'title': post_data['title'], 'title': post_data['title'],
'description': description, 'description': description,
'date': date, 'date': post_data['date'],
'content': templates.from_string(content).render() 'content': templates.from_string(content).render()
} }
@ -134,47 +124,47 @@ def franca(prod=False):
if image: if image:
os.makedirs(os.path.dirname(f'public{image}'), exist_ok=True) os.makedirs(os.path.dirname(f'public{image}'), exist_ok=True)
filename = image.split('/')[-1].split('.')[0] filename = image.split('/')[-1].split('.')[0]
extension = image.split('/')[-1].split('.')[1]
image = Image.open(f'assets{image}')
create_thumbnail(f'assets{image}', f'public/images/{filename}_600.jpg', 600) image.thumbnail((600, 600))
posts[language][date][section][url]['image'] = f'/images/{filename}_600.jpg' image.save(f'public/images/{filename}_600.{extension}', optimize=True)
posts[language][section][url]['image'] = f'/images/{filename}_600.{extension}'
create_thumbnail(f'assets{image}', f'public/images/{filename}_400.jpg', 400) image.thumbnail((400, 400))
posts[language][date][section][url]['thumbnail'] = f'/images/{filename}_400.jpg' image.save(f'public/images/{filename}_400.{extension}', optimize=True)
posts[language][section][url]['thumbnail'] = f'/images/{filename}_400.{extension}'
posts[language] = dict(sorted(posts[language].items(), reverse=True))
for date, sections in posts[language].items(): for section, urls in posts[language].items():
for section, urls in sections.items(): if section != "/":
if section != "/": html = base.render(config=config, section=section,
html = base.render(config=config, section=section, language=language, posts=posts)
language=language, posts=posts)
write_file(f"public/{language}/{section}/index.html", minify_html.minify(html, minify_js=True)) write_file(f"public/{language}/{section}/index.html", html)
for url, post in urls.items(): for url, post in urls.items():
html = base.render(config=config, post=post, html = base.render(config=config, post=post,
language=language, url=url, posts=posts) language=language, url=url, posts=posts)
write_file(f"public/{language}{url}/index.html", minify_html.minify(html, minify_js=True)) write_file(f"public/{language}{url}/index.html", html)
html = base.render(config=config, posts=posts, html = base.render(config=config, posts=posts,
language=language, home=True) language=language, home=True)
write_file(f"public/{language}/index.html", minify_html.minify(html, minify_js=True)) write_file(f"public/{language}/index.html", html)
# copy images/css/js from theme # 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/images", 'public/images', dirs_exist_ok=True)
css = read_file(f"themes/{config['theme']}/static/css/style.css") copy_file(f"themes/{config['theme']}/static/css/style.css", 'public/css/')
minify_css('public/css/style.css', css)
if 'css_includes' in config: if 'css_includes' in config:
for include in config['css_includes']: for include in config['css_includes']:
css = read_file(f"themes/{config['theme']}/static/css/{include}") copy_file(f"themes/{config['theme']}/static/css/{include}", 'public/css/')
minify_css(f'public/css/{include}', css)
if 'js_includes' in config: if 'js_includes' in config:
for include in config['js_includes']: for include in config['js_includes']:
copy_file(f"themes/{config['theme']}/static/js/{include}", 'public/js/') copy_file(f"themes/{config['theme']}/static/js/{include}", 'public/js/')
# copy css/images from site static folder # copy css/images from site static folder
if 'custom_css' in config: if 'custom_css' in config:
shutil.copytree('static/css', 'public/css', dirs_exist_ok=True) shutil.copytree('static/css', 'public/css', dirs_exist_ok=True)
@ -183,20 +173,15 @@ def franca(prod=False):
# Write main index.html # Write main index.html
html = index.render(config=config) html = index.render(config=config)
write_file('public/index.html', minify_html.minify(html, minify_js=True)) write_file('public/index.html', html)
# Write robots.txt # Write robots.txt
robots_content = "User-agent: *" robots_content = "User-agent: *\nDisallow: /"
if not config.get('search_engines', None) == "allow": if config.get('search_engines', None) == "allow":
robots_content += "\nDisallow: /" robots_content = robots_content.rstrip("/")
write_file('public/robots.txt', robots_content) 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) develop(prod)
if __name__ == '__main__': if __name__ == '__main__':
fire.Fire(franca) fire.Fire(crater)

View File

@ -3,7 +3,6 @@ fire==0.5.0
Jinja2==3.1.2 Jinja2==3.1.2
markdown2==2.4.9 markdown2==2.4.9
MarkupSafe==2.1.3 MarkupSafe==2.1.3
minify_html==0.15.0
Pillow==10.0.0 Pillow==10.0.0
Pygments==2.15.1 Pygments==2.15.1
pyinstaller==5.13.0 pyinstaller==5.13.0