🔌 Плагины
В этом разделе мы рассмотрим систему плагинов StaticFlow и как создавать свои плагины.
🤔 Что такое плагин?
Плагин в StaticFlow - это расширение, которое добавляет новую функциональность или изменяет существующую. Плагины могут:
- ✨ Добавлять новые форматы контента
- 🎨 Расширять функциональность шаблонов
- 🖥️ Добавлять новые команды CLI
- 🔗 Интегрировать внешние сервисы
- ⚡ Оптимизировать процесс сборки
- 🖼️ Обрабатывать медиафайлы
- 🌐 Поддерживать многоязычность
🏗️ Архитектура системы плагинов
Основные компоненты
Plugin (Базовый класс)
python
classPlugin(ABC):
def__init__(self):self.config:Dict[str,Any]={}self.enabled:bool=Trueself._hooks:Dict[HookType,List[str]]={}
PluginMetadata (Метаданные)
python
@dataclass
classPluginMetadata:name:strversion:strdescription:strauthor:strdependencies:List[str]=Nonerequires_config:bool=Falsepriority:int=100
HookType (Типы хуков)
python
classHookType(Enum):
PRE_BUILD=auto()# Перед сборкой сайтаPOST_BUILD=auto()# После сборки сайтаPRE_PAGE=auto()# Перед обработкой страницыPOST_PAGE=auto()# После обработки страницыPRE_TEMPLATE=auto()# Перед рендерингом шаблонаPOST_TEMPLATE=auto()# После рендеринга шаблонаPRE_ASSET=auto()# Перед обработкой ресурсаPOST_ASSET=auto()# После обработки ресурса
📦 Установка плагинов
Через pip
bash
pipinstallstaticflow-plugin-name
В конфигурации
toml
# config.toml
[PLUGINS]enabled=["syntax_highlight","math","media","cdn","seo"][PLUGIN_SYNTAX_HIGHLIGHT]style="monokai"linenums=falsecss_class="highlight"[PLUGIN_MEDIA]output_dir="media"sizes={"thumbnail"={width=200,height=200}}formats=["webp","original"]
🛠️ Создание плагина
1. Структура плагина
scdoc
my-plugin/
├── staticflow_my_plugin/│ ├── __init__.py│ ├── plugin.py│ └── utils.py├── tests/│ └── test_plugin.py├── setup.py├── README.md└── LICENSE
2. Основной код плагина
python
# staticflow_my_plugin/plugin.py
fromstaticflow.plugins.core.baseimportPlugin,PluginMetadatafromtypingimportDict,AnyclassMyPlugin(Plugin):@propertydefmetadata(self)->PluginMetadata:returnPluginMetadata(name="my-plugin",version="1.0.0",description="Описание моего плагина",author="Your Name",requires_config=False,priority=100)definitialize(self,config:Optional[Dict[str,Any]]=None)->None:"""Инициализация плагина."""super().initialize(config)# Ваша логика инициализацииdefon_pre_build(self,context:Dict[str,Any])->Dict[str,Any]:"""Хук перед сборкой сайта."""# Подготовка к сборкеreturncontextdefon_post_page(self,context:Dict[str,Any])->Dict[str,Any]:"""Хук после обработки страницы."""# Модификация контента страницыcontent=context.get('content','')# Ваша обработка контентаcontext['content']=contentreturncontextdefon_pre_asset(self,context:Dict[str,Any])->Dict[str,Any]:"""Хук перед обработкой ресурса."""# Обработка статических файловreturncontextdefcleanup(self)->None:"""Очистка ресурсов плагина."""# Очистка ресурсовpass
3. Регистрация плагина
python
# staticflow_my_plugin/__init__.py
from.pluginimportMyPlugindefregister(engine):plugin=MyPlugin()engine.add_plugin(plugin)returnplugin
4. Настройка setup.py
python
# setup.py
fromsetuptoolsimportsetup,find_packagessetup(name="staticflow-my-plugin",version="1.0.0",packages=find_packages(),install_requires=["staticflow>=1.0.0",],entry_points={"staticflow.plugins":["my-plugin = staticflow_my_plugin:register",],},author="Your Name",author_email="your.email@example.com",description="Описание моего плагина",long_description=open("README.md").read(),long_description_content_type="text/markdown",url="https://github.com/your-username/staticflow-my-plugin",classifiers=["Development Status :: 4 - Beta","Intended Audience :: Developers","License :: OSI Approved :: MIT License","Programming Language :: Python :: 3",],)
🎯 Типы плагинов
📝 Плагины контента
python
classContentPlugin(Plugin):
defon_post_page(self,context:Dict[str,Any])->Dict[str,Any]:"""Обработка контента страницы."""content=context.get('content','')# Модификация контентаcontext['content']=processed_contentreturncontext
🎨 Плагины шаблонов
python
classTemplatePlugin(Plugin):
defon_post_template(self,context:Dict[str,Any])->Dict[str,Any]:"""Обработка после рендеринга шаблона."""# Модификация контекста или шаблонаreturncontext
🖥️ Плагины CLI
python
classCLIPlugin(Plugin):
defregister_commands(self,cli):@cli.command()defmy_command():"""Описание команды"""# Код командыpass
⚡ Плагины сборки
python
classBuildPlugin(Plugin):
defon_pre_build(self,context:Dict[str,Any])->Dict[str,Any]:"""Подготовка к сборке."""# Подготовка ресурсовreturncontextdefon_post_build(self,context:Dict[str,Any])->Dict[str,Any]:"""Очистка после сборки."""# Очистка временных файловreturncontext
🔗 Хуки плагинов
🔄 Хуки жизненного цикла
on_pre_build
- перед сборкой сайтаon_post_build
- после сборки сайтаinitialize
- инициализация плагинаcleanup
- очистка ресурсов
📄 Хуки страниц
on_pre_page
- перед обработкой страницыon_post_page
- после обработки страницыprocess_content
- обработка контента
🎨 Хуки шаблонов
on_pre_template
- перед рендерингом шаблонаon_post_template
- после рендеринга шаблона
📁 Хуки ресурсов
on_pre_asset
- перед обработкой ресурсаon_post_asset
- после обработки ресурса
⚙️ Конфигурация плагина
Настройки по умолчанию
python
classMyPlugin(Plugin):
definitialize(self,config:Optional[Dict[str,Any]]=None)->None:default_config={'option1':'default1','option2':'default2',}# Объединение с пользовательской конфигурациейself.config={**default_config,**(configor{})}super().initialize()
Валидация настроек
python
defvalidate_config(self)->bool:
"""Проверка конфигурации плагина."""required=['option1','option2']foroptioninrequired:ifoptionnotinself.config:raiseValueError(f"Missing required setting: {option}")returnTrue
🧪 Тестирование плагинов
Модульные тесты
python
# tests/test_plugin.py
importpytestfromstaticflow_my_pluginimportMyPlugindeftest_plugin_initialization():plugin=MyPlugin()assertplugin.metadata.name=="my-plugin"assertplugin.metadata.version=="1.0.0"deftest_plugin_config():config={'option1':'value1'}plugin=MyPlugin()plugin.initialize(config)assertplugin.config['option1']=='value1'deftest_plugin_hooks():plugin=MyPlugin()context={'content':'test content'}# Тестирование хукаresult=plugin.on_post_page(context)assert'content'inresult
Интеграционные тесты
python
deftest_plugin_integration():
fromstaticflow.core.engineimportEnginefromstaticflow.core.configimportConfig# Создание тестового движкаconfig=Config("test_config.toml")engine=Engine(config)# Добавление плагинаplugin=MyPlugin()engine.add_plugin(plugin)# Тестирование функциональностиassertengine.get_plugin("my-plugin")==plugin
📦 Встроенные плагины
🔍 SEO Plugin
python
# Автоматическое добавление Open Graph тегов
# Twitter Card тегов# Schema.org разметки# Оптимизация заголовков и изображений
🗺️ Sitemap Plugin
python
# Генерация sitemap.xml
# Автоматическое обновление# Поддержка приоритетов
📡 RSS Plugin
python
# Генерация RSS лент
# Поддержка категорий# Автоматическое обновление
🖼️ Media Plugin
python
# Обработка изображений
# Генерация WebP# Создание srcset# Оптимизация видео
🌐 CDN Plugin
python
# Интеграция с CDN
# Автоматическая загрузка файлов# Очистка кэша
💎 Syntax Highlight Plugin
python
# Подсветка синтаксиса кода
# Поддержка множества языков# Кастомизируемые темы
➗ Math Plugin
python
# Рендеринг математических формул
# Поддержка LaTeX# Автоматическое рендеринг
📊 Mermaid Plugin
python
# Создание диаграмм
# Поддержка различных типов# Интерактивные диаграммы
🚀 Публикация плагина
Подготовка
- 📝 Создайте README.md
- 📄 Добавьте лицензию
- 📚 Напишите документацию
- 🧪 Создайте тесты
- 🔧 Настройте CI/CD
Публикация в PyPI
bash
# Сборка пакета
pythonsetup.pysdistbdist_wheel# Загрузка в PyPItwineuploaddist/*
Обновление
- 🔢 Увеличьте версию
- 📚 Обновите документацию
- 🔄 Добавьте миграции
- 🚀 Опубликуйте новую версию
💡 Лучшие практики
🛠️ Разработка
- 📏 Следуйте PEP 8
- 🧪 Пишите тесты
- 📝 Документируйте код
- 🏷️ Используйте типизацию
- 🔍 Обрабатывайте ошибки
⚡ Производительность
- 🚀 Оптимизируйте операции
- 💾 Используйте кэширование
- 📦 Минимизируйте зависимости
- 💻 Следите за памятью
- 🔄 Используйте асинхронность
🔒 Безопасность
- ✅ Валидируйте входные данные
- 🔐 Используйте безопасные настройки
- ⚠️ Обрабатывайте ошибки
- 🔄 Следите за обновлениями
- 🛡️ Проверяйте зависимости
🔗 Совместимость
- 🐍 Поддерживайте версии Python
- 🧪 Тестируйте на разных версиях
- 📋 Документируйте зависимости
- 👀 Следите за изменениями API
- 🔄 Обеспечивайте обратную совместимость
🎯 Примеры использования
Простой плагин для добавления мета-тегов
python
classMetaTagsPlugin(Plugin):
@propertydefmetadata(self)->PluginMetadata:returnPluginMetadata(name="meta-tags",version="1.0.0",description="Добавляет мета-теги к страницам",author="Your Name")defon_post_page(self,context:Dict[str,Any])->Dict[str,Any]:content=context.get('content','')# Добавляем мета-тегиmeta_tags=f""" <meta name="generator" content="StaticFlow"> <meta name="author" content="{context.get('author','Unknown')}"> <meta name="date" content="{context.get('date','')}"> """# Вставляем в headif'<head>'incontent:content=content.replace('<head>',f'<head>{meta_tags}')context['content']=contentreturncontext
Плагин для обработки изображений
python
classImageProcessorPlugin(Plugin):
defon_pre_asset(self,context:Dict[str,Any])->Dict[str,Any]:file_path=context.get('file_path')ifself._is_image(file_path):# Обработка изображенияprocessed_path=self._process_image(file_path)context['file_path']=processed_pathreturncontextdef_is_image(self,path:str)->bool:returnpath.lower().endswith(('.png','.jpg','.jpeg','.gif'))def_process_image(self,path:str)->str:# Логика обработки изображенияreturnpath