pmi-site/build.py

199 lines
9.3 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 os
import markdown
import shutil
from pathlib import Path
TEMPLATE = """<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title} | ФМИ</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<link rel="stylesheet" href="/static/css/custom.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; color: #333;
}}
header {{ border-bottom: 2px solid #003366; padding-bottom: 10px; margin-bottom: 30px; }}
h1 {{ color: #003366; }}
h2 {{ color: #004080; border-bottom: 1px solid #eee; padding-bottom: 5px; margin-top: 30px; }}
h3 {{ color: #0055a4; margin-top: 25px; }}
nav {{ margin-bottom: 30px; }}
nav a {{ margin-right: 20px; text-decoration: none; color: #003366; font-weight: bold; font-size: 1.1em; }}
nav a:hover {{ text-decoration: underline; }}
.person {{ margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px dashed #ddd; }}
.person-name {{ font-weight: bold; font-size: 1.1em; color: #222; }}
.person-name a {{ color: #003366; text-decoration: none; }}
.person-name a:hover {{ text-decoration: underline; }}
.person-details {{ color: #555; font-size: 0.95em; }}
.back-link {{ display: inline-block; margin-bottom: 20px; color: #003366; text-decoration: none; }}
.back-link:hover {{ text-decoration: underline; }}
.pdf-link {{ display: inline-flex; align-items: center; padding: 8px 16px; background: #003366; color: white; text-decoration: none; border-radius: 4px; margin: 10px 0; }}
.pdf-link:hover {{ background: #004080; }}
.pdf-link::before {{ content: ""; margin-right: 8px; }}
.pdf-list {{ list-style: none; padding: 0; }}
.pdf-list li {{ margin: 10px 0; padding: 10px; background: #f9f9f9; border-left: 4px solid #003366; }}
img {{ max-width: 100%; height: auto; display: block; margin: 20px auto; }}
</style>
</head>
<body>
<header>
<h1>Физико-математический институт</h1>
<nav>
<a href="/">Главная</a>
<a href="/news.html">Новости</a>
<a href="/staff.html">Сотрудники</a>
<a href="/publications.html">Публикации</a>
</nav>
</header>
<main>
{content}
</main>
<footer style="margin-top: 50px; border-top: 1px solid #ccc; padding-top: 20px; font-size: 0.8em; color: #666;">
© 2026 Физико-математический институт Коми НЦ УрО РАН
</footer>
</body>
</html>"""
def parse_front_matter(text):
if text.strip().startswith('---'):
parts = text.split('---', 2)
if len(parts) >= 3:
meta = {}
for line in parts[1].strip().split('\n'):
if ':' in line:
key, val = line.split(':', 1)
meta[key.strip()] = val.strip().strip('"')
return meta, parts[2].strip()
return {}, text.strip()
def build_page(md_path, html_path, title):
with open(md_path, 'r', encoding='utf-8') as f:
meta, md_text = parse_front_matter(f.read())
html_content = markdown.markdown(md_text, extensions=['fenced_code', 'tables'])
with open(html_path, 'w', encoding='utf-8') as f:
f.write(TEMPLATE.format(title=title, content=html_content))
def main():
Path('public').mkdir(exist_ok=True)
Path('public/news').mkdir(exist_ok=True)
Path('public/staff').mkdir(exist_ok=True)
# 1. Главная страница
if Path('content/index.md').exists():
build_page(Path('content/index.md'), 'public/index.html', 'Главная')
# 2. Страница новостей
news_files = sorted(Path('content/news').glob('*.md'), reverse=True)
news_links = "".join([f'<li><a href="news/{f.stem}.html">{f.stem.replace("-", " ").title()}</a></li>' for f in news_files])
with open('public/news.html', 'w', encoding='utf-8') as f:
f.write(TEMPLATE.format(title="Новости", content=f"<h2>Последние новости</h2><ul>{news_links}</ul>"))
for md_file in news_files:
build_page(md_file, Path(f'public/news/{md_file.stem}.html'), md_file.stem.replace('-', ' ').title())
# 3. Страница сотрудников
staff_by_lab = {}
unassigned = []
for md_file in sorted(Path('content/staff').glob('*.md')):
with open(md_file, 'r', encoding='utf-8') as f:
meta, _ = parse_front_matter(f.read())
name = meta.get('name', md_file.stem.replace('-', ' ').title())
degree = meta.get('degree', '')
position = meta.get('position', '')
lab = meta.get('lab', 'Без подразделения')
details = []
if degree: details.append(degree)
if position: details.append(position)
details_str = ", ".join(details)
person_html = f'''<div class="person">
<div class="person-name"><a href="staff/{md_file.stem}.html">{name}</a></div>
<div class="person-details">{details_str}</div>
</div>'''
if lab == 'Без подразделения' or not lab:
unassigned.append(person_html)
else:
if lab not in staff_by_lab:
staff_by_lab[lab] = []
staff_by_lab[lab].append(person_html)
staff_html = "<h2>Наши сотрудники</h2>"
lab_order = [
"Лаборатория математики и телекоммуникаций",
"Лаборатория теоретической и вычислительной физики",
"Лаборатория экспериментальной физики"
]
for lab in lab_order:
if lab in staff_by_lab:
staff_html += f"<h3>{lab}</h3>"
for p in staff_by_lab[lab]:
staff_html += p
for lab, people in staff_by_lab.items():
if lab not in lab_order:
staff_html += f"<h3>{lab}</h3>"
for p in people:
staff_html += p
if unassigned:
staff_html += "<h3>Сотрудники (без привязки к лаборатории)</h3>"
for p in unassigned:
staff_html += p
with open('public/staff.html', 'w', encoding='utf-8') as f:
f.write(TEMPLATE.format(title="Сотрудники", content=staff_html))
# 4. Персональные страницы сотрудников
for md_file in Path('content/staff').glob('*.md'):
with open(md_file, 'r', encoding='utf-8') as f:
meta, body = parse_front_matter(f.read())
name = meta.get('name', md_file.stem.replace('-', ' ').title())
full_content = f'<a href="/staff.html" class="back-link">← Назад к списку сотрудников</a>\n{body}'
html_content = markdown.markdown(full_content, extensions=['fenced_code', 'tables'])
with open(Path(f'public/staff/{md_file.stem}.html'), 'w', encoding='utf-8') as f:
f.write(TEMPLATE.format(title=name, content=html_content))
# 5. Страница с публикациями (PDF-файлы)
pdf_files = sorted(Path('static/pdf').glob('*.pdf'))
if pdf_files:
pdf_list_html = "<h2>Научные публикации и документы</h2><ul class='pdf-list'>"
for pdf in pdf_files:
# Получаем название из имени файла или из метаданных (если есть)
title = pdf.stem.replace('-', ' ').replace('_', ' ').title()
size_mb = pdf.stat().st_size / (1024 * 1024)
pdf_list_html += f"""
<li>
<a href="/static/pdf/{pdf.name}" class="pdf-link" target="_blank">{title}</a>
<span style="color: #666; font-size: 0.9em;">({size_mb:.1f} MB)</span>
</li>"""
pdf_list_html += "</ul>"
with open('public/publications.html', 'w', encoding='utf-8') as f:
f.write(TEMPLATE.format(title="Публикации", content=pdf_list_html))
else:
# Если PDF пока нет, создаем заглушку
with open('public/publications.html', 'w', encoding='utf-8') as f:
f.write(TEMPLATE.format(title="Публикации", content="<h2>Публикации</h2><p>В этом разделе скоро появятся научные публикации и документы.</p>"))
# 6. Копирование статических файлов (картинки, PDF, CSS)
if Path('static').exists():
shutil.rmtree('public/static', ignore_errors=True)
shutil.copytree('static', 'public/static')
print("✅ Статические файлы скопированы (включая PDF)")
print("✅ Сайт успешно собран!")
if __name__ == '__main__':
main()