From 92a528b03b9a0c77d84bd6811a4f7e10cf7c62e7 Mon Sep 17 00:00:00 2001 From: Mikhail Romanko Date: Thu, 17 Apr 2025 11:56:15 +0300 Subject: Initial commit --- .gitignore | 256 +++++++++++++++++++++++++ .idea/.gitignore | 3 + .idea/SiteGen.iml | 10 + .idea/inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + content/common/404.md | 9 + content/common/favicon.ico | Bin 0 -> 122702 bytes content/common/favicon.png | Bin 0 -> 26905 bytes content/common/images/01-oldsite.png | Bin 0 -> 133318 bytes content/common/images/hex.png | Bin 0 -> 26905 bytes content/common/index.md | 3 + content/common/robots.txt | 14 ++ content/en/about.md | 19 ++ content/en/blog.md | 4 + content/en/blog/2025/01-sitegen.md | 135 +++++++++++++ content/en/projects.md | 3 + content/ru/about.md | 19 ++ content/ru/blog.md | 4 + content/ru/blog/2025/01-sitegen.md | 134 +++++++++++++ content/ru/projects.md | 3 + main.py | 89 +++++++++ requirements.txt | 5 + template/en.html | 5 + template/none.html | 5 + template/ru.html | 5 + template/style.html | 16 ++ 28 files changed, 767 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/SiteGen.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 content/common/404.md create mode 100644 content/common/favicon.ico create mode 100644 content/common/favicon.png create mode 100644 content/common/images/01-oldsite.png create mode 100644 content/common/images/hex.png create mode 100644 content/common/index.md create mode 100644 content/common/robots.txt create mode 100644 content/en/about.md create mode 100644 content/en/blog.md create mode 100644 content/en/blog/2025/01-sitegen.md create mode 100644 content/en/projects.md create mode 100644 content/ru/about.md create mode 100644 content/ru/blog.md create mode 100644 content/ru/blog/2025/01-sitegen.md create mode 100644 content/ru/projects.md create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 template/en.html create mode 100644 template/none.html create mode 100644 template/ru.html create mode 100644 template/style.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfd9b9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,256 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Artifacts +*.tgz +public \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/SiteGen.iml b/.idea/SiteGen.iml new file mode 100644 index 0000000..f286330 --- /dev/null +++ b/.idea/SiteGen.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..06ec480 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d4faa8a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/content/common/404.md b/content/common/404.md new file mode 100644 index 0000000..05ed756 --- /dev/null +++ b/content/common/404.md @@ -0,0 +1,9 @@ +Sorry, there is no page you're looking for :( + +Check [main page](/en/about) + +--- + +Извините, здесь нет страницы, которую вы ищете :( + +Загляните на [главную страницу](/ru/about) diff --git a/content/common/favicon.ico b/content/common/favicon.ico new file mode 100644 index 0000000..b0bfab5 Binary files /dev/null and b/content/common/favicon.ico differ diff --git a/content/common/favicon.png b/content/common/favicon.png new file mode 100644 index 0000000..8813264 Binary files /dev/null and b/content/common/favicon.png differ diff --git a/content/common/images/01-oldsite.png b/content/common/images/01-oldsite.png new file mode 100644 index 0000000..959de40 Binary files /dev/null and b/content/common/images/01-oldsite.png differ diff --git a/content/common/images/hex.png b/content/common/images/hex.png new file mode 100644 index 0000000..8813264 Binary files /dev/null and b/content/common/images/hex.png differ diff --git a/content/common/index.md b/content/common/index.md new file mode 100644 index 0000000..41b56c5 --- /dev/null +++ b/content/common/index.md @@ -0,0 +1,3 @@ +[English](en/about) + +[Русский](ru/about) diff --git a/content/common/robots.txt b/content/common/robots.txt new file mode 100644 index 0000000..335b71b --- /dev/null +++ b/content/common/robots.txt @@ -0,0 +1,14 @@ +User-agent: * +Allow: / + +User-agent: GPTBot +Disallow: / + +User-agent: ChatGPT-User +Disallow: / + +User-agent: anthropic-ai +Disallow: / + +User-agent: Google-Extended +Disallow: / diff --git a/content/en/about.md b/content/en/about.md new file mode 100644 index 0000000..6291e85 --- /dev/null +++ b/content/en/about.md @@ -0,0 +1,19 @@ +# About + +Graduate of ITMO University +and Saint Petersburg State Polytechnic University. + +I like: +- programing in C and C++ +- studying various algorithms +- developing games (usually small prototypes) +- foxes + +## Contacts + +Social Media: +- X/Twitter (`@_blankhex_`) +- Reddit (`u/_blankhex_`) +- GitHub (`blankhex`) + +You can also contact me via email: [me@blankhex.com](mailto:me@blankhex.com) \ No newline at end of file diff --git a/content/en/blog.md b/content/en/blog.md new file mode 100644 index 0000000..b5363e1 --- /dev/null +++ b/content/en/blog.md @@ -0,0 +1,4 @@ +# Blog + +## 2025 +Apr 16 - [Static site generator in 90 lines of Python code](blog/2025/01-sitegen) diff --git a/content/en/blog/2025/01-sitegen.md b/content/en/blog/2025/01-sitegen.md new file mode 100644 index 0000000..99fb97f --- /dev/null +++ b/content/en/blog/2025/01-sitegen.md @@ -0,0 +1,135 @@ +# Static site generator in 90 lines of Python code + +Created: Apr 16, 2025 + +[На русском](/ru/blog/2025/01-sitegen) + +A long time ago, I made a small business card website. It consisted of three +simple HTML pages, one CSS file (which I generated from SCSS), several fonts +and images. That was more than enough to get a link to my website featured +in a resume or social media profile. + + +![Picture of the old website](/images/01-oldsite.png "Picture of the old website") + +I recently decided to continue working on my pet-project +and would like to publish all sorts of notes and articles on this topic on my +website. I didn't want to manually mess with HTML files, so I decided to look +for an alternative in the form of some kind of static website generator. +Ideally, I would like it to be: + +- Small and simple +- Able to work with Markdown +- Able syntax-highlight blocks of code + +Unfortunately, I couldn't find any suitable solutions for myself, so I decided +to build my own using Python, mistune +Markdown parser, Jinja2 +template engine, and Pygments. +The whole generation process boils down to the following: + +1. For every file in the input directory check whether it is Markdown + - If yes - convert it to HTML (with highlighting) and write to output directory + - If no - copy as is to output directory +2. Compress content of the output directory + +This generation process has a rather major drawback - due to the fact that +there is no post-processing of HTML, any links to other Markdown pages must +end with a `.html` extension[^1]. + +[^1]: This can be mitigated by special web-server configuration, that replaces + `.md` extension with `.html` or by omitting `.md` extension entirely and + using something like `try_files $uri $uri.html` + +Here is the code: + +```python +import re, jinja2, mistune, shutil, os, pathlib, tarfile +from pygments.lexers import get_lexer_by_name +from pygments.formatters import HtmlFormatter +from pygments import highlight + + +class PygmentsHTMLRenderer(mistune.HTMLRenderer): + def block_code(self, code: str, info = None): + if not info: + return '\n
%s
\n' % mistune.escape(code) + lexer = get_lexer_by_name(info, stripall=True) + formatter = HtmlFormatter(lineseparator='
') + return highlight(code, lexer, formatter) + + +def convert_markdown(page: str): + plugins = ['footnotes', 'table', 'strikethrough', 'url'] + renderer = PygmentsHTMLRenderer(escape=False) + return mistune.create_markdown(plugins=plugins, renderer=renderer)(page) + + +def extract_title(page: str): + matches = re.match('

(.*?)

', page) + if matches: + return matches.group(1) + return 'BlankHex' + + +def handle_file(path: str, input_dir: str, output_dir: str, template_name: str): + # Calculate input and output paths + relpath = os.path.relpath(path, input_dir) + input_path = path + output_path = os.path.join(output_dir, relpath) + if input_path.endswith('.md'): + output_path = output_path.replace('.md', '.html') + + # Don't convert if output path exists + if os.path.exists(output_path): + return + + # Run conversion + pathlib.Path(os.path.dirname(output_path)).mkdir(parents=True, exist_ok=True) + if input_path.endswith('.md'): + # Read Markdown document + with open(input_path, 'r') as handle: + markdown_page = handle.read() + + # Get Pygments styles for light and dark themes + light_style = HtmlFormatter(style='default').get_style_defs() + dark_style = HtmlFormatter(style='monokai').get_style_defs() + + # Convert Markdown document to HTML document + html_page = convert_markdown(markdown_page) + html_header = extract_title(html_page) + environment = jinja2.Environment(loader=jinja2.FileSystemLoader('template/')) + template = environment.get_template(template_name) + output_page = template.render(title=html_header, + body=html_page, + light_style=light_style, + dark_style=dark_style) + + # Write HTML document + with open(output_path, 'w') as handle: + handle.write(output_page) + else: + # Copy file as is + shutil.copy(path, output_path) + + +def convert_dir(input_dir: str, output_dir: str, template_name: str): + # Convert or copy every file from the input directory to the output directory + for subdir, dirs, files in os.walk(input_dir): + for file in files: + handle_file(os.path.join(subdir, file), input_dir, output_dir, template_name) + + +# Remove output from previous run +if os.path.isdir('public'): + shutil.rmtree('public') +if os.path.isfile('public.tgz'): + os.remove('public.tgz') + +# Run conversion +convert_dir('content', 'public', 'template.html') +with tarfile.open('public.tgz', 'w:gz') as tar: + for file in os.listdir('public'): + tar.add(os.path.join('public', file), file) +``` + diff --git a/content/en/projects.md b/content/en/projects.md new file mode 100644 index 0000000..3440a80 --- /dev/null +++ b/content/en/projects.md @@ -0,0 +1,3 @@ +# Projects + +As of now this page is empty :( \ No newline at end of file diff --git a/content/ru/about.md b/content/ru/about.md new file mode 100644 index 0000000..d7e0007 --- /dev/null +++ b/content/ru/about.md @@ -0,0 +1,19 @@ +# Обо мне + +Выпускник университетов ИТМОСПбПУ. + +Я люблю: +- программирование на C и C++ +- изучение различных алгоритмов +- разработку игр (обычно небольшие прототипы) +- лис + +## Контакты + +Я в соцсетях: +- X/Twitter (`@_blankhex_`) +- Reddit (`u/_blankhex_`) +- GitHub (`blankhex`) + +Также можно воспользоваться электронной почтой [me@blankhex.com](mailto:me@blankhex.com) diff --git a/content/ru/blog.md b/content/ru/blog.md new file mode 100644 index 0000000..b12aece --- /dev/null +++ b/content/ru/blog.md @@ -0,0 +1,4 @@ +# Блог + +## 2025 +16 апреля - [Генератор статических сайтов в 90 строк Python кода](blog/2025/01-sitegen) \ No newline at end of file diff --git a/content/ru/blog/2025/01-sitegen.md b/content/ru/blog/2025/01-sitegen.md new file mode 100644 index 0000000..76b7353 --- /dev/null +++ b/content/ru/blog/2025/01-sitegen.md @@ -0,0 +1,134 @@ +# Генератор статических сайтов в 90 строк Python кода + +Создано: 16 апреля 2025 + +[In English](/en/blog/2025/01-sitegen) + +Давным-давно я сделал небольшой сайт-визитку - он состоял из трех простеньких +HTML-страниц, одного CSS-файла (который я генерировал из SCSS), нескольких +шрифтов и картинок. Этого было более чем достаточно, чтобы ссылка на мой +сайт красовалась в каком-либо резюме или профиле соцсети. + +![Изображение старого сайта](/images/01-oldsite.png "Изображение старого сайта") + +Недавно я решил продолжить работу над своим pet проектом +и хотел бы публиковать на своем сайте всякие заметки и статьи на эту тему. +Мне не хотелось вручную возиться с HTML-файлам, поэтому я решил подыскать +альтернативу в виде какого-нибудь статического генератора сайтов. В идеале, я +хотел бы, чтобы он был: + +- Небольшим и достаточно простым +- Мог работать с Markdown +- Мог выполнять подсветку синтаксиса в блоках кода + +К сожалению, я не смог найти ни одно подходящее для себя решение, поэтому +я решил собрать свое на коленке используя Python, парсер Markdown'а mistune, +шаблонизатор Jinja2Pygments. Весь процесс +генерации сводиться к следующему: + +1. Для каждого файла во входном каталоге проверяем, является ли он Markdown + - Если да - преобразуем его в HTML (с подсветкой синтаксиса) и записываем + в выходной каталог + - Если нет - копируем файл как есть в выходной каталог +2. Сжимаем содержимое выходного каталога + +У данного процесса генерации есть достаточно крупный недостаток - из-за того, +что нет постобработки HTML, любые ссылки на другие страницы Markdown должны +заканчиваться расширением `.html`[^1]. + +[^1]: Эту проблему можно устранить с помощью специальной настройки веб-сервера, + которая заменяет в расширение `.md` на `.html` или путем автоматического + дописывания расширения `.html` (к примеру: `try_files $uri $uri.html`). + +Сам код генератора: + +```python +import re, jinja2, mistune, shutil, os, pathlib, tarfile +from pygments.lexers import get_lexer_by_name +from pygments.formatters import HtmlFormatter +from pygments import highlight + + +class PygmentsHTMLRenderer(mistune.HTMLRenderer): + def block_code(self, code: str, info = None): + if not info: + return '\n
%s
\n' % mistune.escape(code) + lexer = get_lexer_by_name(info, stripall=True) + formatter = HtmlFormatter(lineseparator='
') + return highlight(code, lexer, formatter) + + +def convert_markdown(page: str): + plugins = ['footnotes', 'table', 'strikethrough', 'url'] + renderer = PygmentsHTMLRenderer(escape=False) + return mistune.create_markdown(plugins=plugins, renderer=renderer)(page) + + +def extract_title(page: str): + matches = re.match('

(.*?)

', page) + if matches: + return matches.group(1) + return 'BlankHex' + + +def handle_file(path: str, input_dir: str, output_dir: str, template_name: str): + # Calculate input and output paths + relpath = os.path.relpath(path, input_dir) + input_path = path + output_path = os.path.join(output_dir, relpath) + if input_path.endswith('.md'): + output_path = output_path.replace('.md', '.html') + + # Don't convert if output path exists + if os.path.exists(output_path): + return + + # Run conversion + pathlib.Path(os.path.dirname(output_path)).mkdir(parents=True, exist_ok=True) + if input_path.endswith('.md'): + # Read Markdown document + with open(input_path, 'r') as handle: + markdown_page = handle.read() + + # Get Pygments styles for light and dark themes + light_style = HtmlFormatter(style='default').get_style_defs() + dark_style = HtmlFormatter(style='monokai').get_style_defs() + + # Convert Markdown document to HTML document + html_page = convert_markdown(markdown_page) + html_header = extract_title(html_page) + environment = jinja2.Environment(loader=jinja2.FileSystemLoader('template/')) + template = environment.get_template(template_name) + output_page = template.render(title=html_header, + body=html_page, + light_style=light_style, + dark_style=dark_style) + + # Write HTML document + with open(output_path, 'w') as handle: + handle.write(output_page) + else: + # Copy file as is + shutil.copy(path, output_path) + + +def convert_dir(input_dir: str, output_dir: str, template_name: str): + # Convert or copy every file from the input directory to the output directory + for subdir, dirs, files in os.walk(input_dir): + for file in files: + handle_file(os.path.join(subdir, file), input_dir, output_dir, template_name) + + +# Remove output from previous run +if os.path.isdir('public'): + shutil.rmtree('public') +if os.path.isfile('public.tgz'): + os.remove('public.tgz') + +# Run conversion +convert_dir('content', 'public', 'template.html') +with tarfile.open('public.tgz', 'w:gz') as tar: + for file in os.listdir('public'): + tar.add(os.path.join('public', file), file) +``` diff --git a/content/ru/projects.md b/content/ru/projects.md new file mode 100644 index 0000000..16a61b1 --- /dev/null +++ b/content/ru/projects.md @@ -0,0 +1,3 @@ +# Проекты + +Пока здесь ничего нет :( \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..e9fd447 --- /dev/null +++ b/main.py @@ -0,0 +1,89 @@ +import re, jinja2, mistune, shutil, os, pathlib, tarfile +from pygments.lexers import get_lexer_by_name +from pygments.formatters import HtmlFormatter +from pygments import highlight + + +class PygmentsHTMLRenderer(mistune.HTMLRenderer): + def block_code(self, code: str, info = None): + if not info: + return '\n
%s
\n' % mistune.escape(code) + lexer = get_lexer_by_name(info, stripall=True) + formatter = HtmlFormatter(lineseparator='
') + return highlight(code, lexer, formatter) + + +def convert_markdown(page: str): + plugins = ['footnotes', 'table', 'strikethrough', 'url'] + renderer = PygmentsHTMLRenderer(escape=False) + return mistune.create_markdown(plugins=plugins, renderer=renderer)(page) + + +def extract_title(page: str): + matches = re.match('

(.*?)

', page) + if matches: + return matches.group(1) + return 'BlankHex' + + +def handle_file(path: str, input_dir: str, output_dir: str, template_name: str): + # Calculate input and output paths + relpath = os.path.relpath(path, input_dir) + input_path = path + output_path = os.path.join(output_dir, relpath) + if input_path.endswith('.md'): + output_path = output_path.replace('.md', '.html') + + # Don't convert if output path exists + if os.path.exists(output_path): + return + + # Run conversion + pathlib.Path(os.path.dirname(output_path)).mkdir(parents=True, exist_ok=True) + if input_path.endswith('.md'): + # Read Markdown document + with open(input_path, 'r') as handle: + markdown_page = handle.read() + + # Get Pygments styles for light and dark themes + light_style = HtmlFormatter(style='default').get_style_defs() + dark_style = HtmlFormatter(style='monokai').get_style_defs() + + # Convert Markdown document to HTML document + html_page = convert_markdown(markdown_page) + html_header = extract_title(html_page) + environment = jinja2.Environment(loader=jinja2.FileSystemLoader('template/')) + template = environment.get_template(template_name) + output_page = template.render(title=html_header, + body=html_page, + light_style=light_style, + dark_style=dark_style) + + # Write HTML document + with open(output_path, 'w') as handle: + handle.write(output_page) + else: + # Copy file as is + shutil.copy(path, output_path) + + +def convert_dir(input_dir: str, output_dir: str, template_name: str): + # Convert or copy every file from the input directory to the output directory + for subdir, dirs, files in os.walk(input_dir): + for file in files: + handle_file(os.path.join(subdir, file), input_dir, output_dir, template_name) + + +# Remove output from previous run +if os.path.isdir('public'): + shutil.rmtree('public') +if os.path.isfile('public.tgz'): + os.remove('public.tgz') + +# Run conversion for english, russian and 'common' folder +convert_dir('content/en', 'public/en', 'en.html') +convert_dir('content/ru', 'public/ru', 'ru.html') +convert_dir('content/common', 'public', 'none.html') +with tarfile.open('public.tgz', 'w:gz') as tar: + for file in os.listdir('public'): + tar.add(os.path.join('public', file), file) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7694f3b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Jinja2==3.1.6 +MarkupSafe==3.0.2 +mistune==3.1.3 +Pygments==2.19.1 +typing_extensions==4.13.2 diff --git a/template/en.html b/template/en.html new file mode 100644 index 0000000..0ffc06e --- /dev/null +++ b/template/en.html @@ -0,0 +1,5 @@ + + +{% include 'style.html' %}{{ title }} +
{{ body }}

+ diff --git a/template/none.html b/template/none.html new file mode 100644 index 0000000..fdb646c --- /dev/null +++ b/template/none.html @@ -0,0 +1,5 @@ + + +{% include 'style.html' %} +
{{ body }}

+ diff --git a/template/ru.html b/template/ru.html new file mode 100644 index 0000000..cc64c9c --- /dev/null +++ b/template/ru.html @@ -0,0 +1,5 @@ + + +{% include 'style.html' %}{{ title }} +
{{ body }}

+ diff --git a/template/style.html b/template/style.html new file mode 100644 index 0000000..01d4a37 --- /dev/null +++ b/template/style.html @@ -0,0 +1,16 @@ + \ No newline at end of file -- cgit v1.2.3