diff options
| author | 2025-10-25 00:30:48 +0800 | |
|---|---|---|
| committer | 2025-10-25 00:30:48 +0800 | |
| commit | cbc653ffd0ea9abf4360623dc7a7651e1a49cc61 (patch) | |
| tree | ea3c396148158077bae3e77eaa9341f8c1990636 | |
| parent | 08299b37dfda86e56e4f2b442f68ccd2da7a82e3 (diff) | |
| download | conventional_role_play-cbc653ffd0ea9abf4360623dc7a7651e1a49cc61.tar.gz conventional_role_play-cbc653ffd0ea9abf4360623dc7a7651e1a49cc61.zip | |
feat: Implement plugin system with combat tracker and dice analyzer
- Added `plugin_system_demo.py` to demonstrate basic plugin usage, processing, and analysis.
- Created `CombatTrackerPlugin` for tracking combat statistics including damage and healing.
- Developed `DiceAnalyzerPlugin` for analyzing dice rolls and calculating success rates.
- Introduced `renderer_demo.py` for rendering output in HTML, Markdown, and JSON formats.
- Implemented `rule_system_demo.py` to showcase rule engine capabilities with various examples.
- Established core rule engine functionality in `rules.py` with support for conditions and actions.
- Enhanced base plugin structure in `base.py` to support different plugin types (Processor, Renderer, Analyzer).
- Added custom exception handling in `exceptions.py` for better error management.
- Configured logging setup in `logging_config.py` for improved logging capabilities.
- Created unit tests in `test_rust_core.py` to validate core functionalities and performance.
46 files changed, 3885 insertions, 1668 deletions
@@ -1,6 +1,6 @@ [package] name = "conventionalrp" -version = "0.2.3" +version = "1.1.3" edition = "2021" description = "HydroRoll Conventional Role Play SDK" license = "AGPLv3.0" @@ -9,7 +9,6 @@ homepage = "https://crp.hydroroll.team/" repository = "https://github.com/HydroRoll-Team/conventional_role_play" authors = ["HsiangNianian"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "_core" crate-type = ["cdylib"] @@ -17,3 +16,6 @@ crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.27.0", features = ["abi3-py39"] } time = { version = "0.3.39", features = ["formatting", "macros", "local-offset"] } +regex = "1.10" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/docs/source/advanced_usage.md b/docs/source/advanced_usage.md new file mode 100644 index 0000000..7c0ca14 --- /dev/null +++ b/docs/source/advanced_usage.md @@ -0,0 +1,457 @@ +# 高级使用指南 + +## 目录 + +1. [插件系统](#插件系统) +2. [规则引擎](#规则引擎) +3. [自定义渲染器](#自定义渲染器) +4. [性能优化](#性能优化) +5. [错误处理](#错误处理) + +--- + +## 插件系统 + +ConventionalRP 提供了强大的插件系统,允许您扩展和定制框架的功能。 + +### 插件类型 + +#### 1. 解析器插件 (ParserPlugin) + +用于扩展文本解析功能: + +```python +from conventionalrp.plugins import ParserPlugin + +class CustomParserPlugin(ParserPlugin): + def __init__(self): + super().__init__("CustomParser", "1.0.0") + self.priority = 60 # 优先级 + + def initialize(self, config=None): + self.config = config or {} + + def can_parse(self, text: str) -> bool: + # 判断是否可以解析 + return text.startswith("custom:") + + def parse(self, text: str) -> List[Dict[str, Any]]: + # 解析文本 + return [{"type": "custom", "content": text}] +``` + +#### 2. 处理器插件 (ProcessorPlugin) + +用于数据处理和转换: + +```python +from conventionalrp.plugins import ProcessorPlugin + +class CombatTrackerPlugin(ProcessorPlugin): + def __init__(self): + super().__init__("CombatTracker", "1.0.0") + self.total_damage = 0 + + def initialize(self, config=None): + self.config = config or {} + + def process_token(self, token: Dict[str, Any]) -> Dict[str, Any]: + # 处理单个 token + content = token.get("content", "") + + # 检测伤害 + import re + damage_match = re.search(r'(\d+)\s*点?伤害', content) + if damage_match: + damage = int(damage_match.group(1)) + self.total_damage += damage + token["combat_data"] = { + "type": "damage", + "amount": damage, + "total": self.total_damage + } + + return token +``` + +#### 3. 渲染器插件 (RendererPlugin) + +用于自定义输出格式: + +```python +from conventionalrp.plugins import RendererPlugin + +class PlainTextRenderer(RendererPlugin): + def __init__(self): + super().__init__("PlainText", "1.0.0") + + def initialize(self, config=None): + self.config = config or {} + + def render(self, data: Any) -> str: + # 渲染为纯文本 + lines = [] + for item in data: + speaker = item.get("speaker", "") + content = item.get("content", "") + if speaker: + lines.append(f"{speaker}: {content}") + else: + lines.append(content) + return "\n".join(lines) +``` + +#### 4. 分析器插件 (AnalyzerPlugin) + +用于数据分析和统计: + +```python +from conventionalrp.plugins import AnalyzerPlugin + +class SessionAnalyzerPlugin(AnalyzerPlugin): + def __init__(self): + super().__init__("SessionAnalyzer", "1.0.0") + + def initialize(self, config=None): + self.config = config or {} + + def analyze(self, data: Any) -> Dict[str, Any]: + # 分析游戏数据 + stats = { + "total_dialogues": 0, + "total_dice_rolls": 0, + "active_characters": set(), + } + + for token in data: + token_type = token.get("type", "") + if token_type == "dialogue": + stats["total_dialogues"] += 1 + elif token_type == "dice": + stats["total_dice_rolls"] += 1 + + speaker = token.get("speaker", "") + if speaker: + stats["active_characters"].add(speaker) + + stats["active_characters"] = list(stats["active_characters"]) + return stats +``` + +### 使用插件管理器 + +```python +from conventionalrp.plugins import PluginManager + +# 创建管理器 +manager = PluginManager(plugin_dirs=["./my_plugins"]) + +# 发现插件 +discovered = manager.discover_plugins() +print(f"发现 {len(discovered)} 个插件") + +# 注册插件 +my_plugin = MyCustomPlugin() +my_plugin.initialize() +manager.register_plugin(my_plugin) + +# 执行插件 +result = manager.execute_plugins(data, plugin_type=ProcessorPlugin) + +# 启用/禁用插件 +manager.disable_plugin("MyPlugin") +manager.enable_plugin("MyPlugin") + +# 获取统计信息 +stats = manager.get_statistics() +print(stats) +``` + +--- + +## 规则引擎 + +规则引擎允许您定义灵活的数据处理规则。 + +### 规则定义 + +```python +from conventionalrp.core.rules import Rule, RuleEngine + +# 创建规则 +rule = Rule( + name="detect_critical_hit", + conditions={ + "type": {"equals": "dice"}, + "content": {"matches": r"d20=20"} + }, + actions=[ + {"action": "add_tag", "tag": "critical_hit"}, + {"action": "set_field", "field": "is_critical", "value": True} + ], + priority=100 +) +``` + +### 条件运算符 + +- `equals`: 精确匹配 +- `not_equals`: 不等于 +- `contains`: 包含子字符串 +- `not_contains`: 不包含 +- `matches`: 正则表达式匹配 +- `in`: 值在列表中 +- `not_in`: 值不在列表中 +- `greater_than`: 大于 +- `less_than`: 小于 + +### 动作类型 + +- `set_field`: 设置字段值 +- `add_tag`: 添加标签 +- `remove_field`: 删除字段 +- `transform`: 自定义转换函数 + +### 使用规则引擎 + +```python +# 创建引擎 +engine = RuleEngine() + +# 添加规则 +engine.add_rule(rule) + +# 应用规则 +tokens = [ + {"type": "dice", "content": "d20=20"}, + {"type": "dialogue", "content": "我投了大成功!"} +] + +processed_tokens = engine.apply_rules(tokens) + +# 移除规则 +engine.remove_rule("detect_critical_hit") +``` + +### 高级示例:动态规则 + +```python +def create_damage_rule(min_damage: int): + """创建伤害检测规则""" + return Rule( + name=f"detect_damage_{min_damage}", + conditions={ + "content": {"matches": rf"(\d+)点伤害"}, + # 可以添加自定义验证 + }, + actions=[ + {"action": "set_field", "field": "damage_detected", "value": True}, + {"action": "transform", "function": lambda token: { + **token, + "damage_level": "high" if int(re.search(r'(\d+)', token["content"]).group(1)) > min_damage else "normal" + }} + ] + ) + +# 使用 +engine.add_rule(create_damage_rule(20)) +``` + +--- + +## 自定义渲染器 + +### 继承 BaseRenderer + +```python +from conventionalrp.renderers.base import BaseRenderer + +class LaTeXRenderer(BaseRenderer): + def __init__(self): + super().__init__() + + def render(self, data: List[Dict[str, Any]]) -> str: + latex = r"\documentclass{article}" + "\n" + latex += r"\begin{document}" + "\n" + + for item in data: + token_type = item.get("type", "") + content = item.get("content", "") + + if token_type == "dialogue": + speaker = item.get("speaker", "") + latex += rf"\textbf{{{speaker}:}} {content}\\" + "\n" + else: + latex += f"{content}\\\\\n" + + latex += r"\end{document}" + "\n" + return latex +``` + +### 使用主题系统 + +```python +from conventionalrp.renderers.html_renderer import HTMLRenderer + +# 使用内置主题 +renderer = HTMLRenderer(theme="dark") + +# 使用自定义 CSS +custom_css = """ +body { + background: linear-gradient(to bottom, #2c3e50, #3498db); +} +.token { + box-shadow: 0 4px 6px rgba(0,0,0,0.3); +} +""" + +renderer = HTMLRenderer(theme="light", custom_css=custom_css) +output = renderer.render(data) +``` + +--- + +## 性能优化 + +### 1. 使用 Rust 核心 + +编译 Rust 模块获得更好的性能: + +```bash +maturin develop --uv --release +``` + +### 2. 批量处理 + +```python +# 不推荐:逐个处理 +for token in tokens: + processor.process(token) + +# 推荐:批量处理 +processed_tokens = processor.process_batch(tokens) +``` + +### 3. 规则优化 + +```python +# 设置合适的优先级 +Rule(name="fast_rule", priority=100, ...) # 先执行 +Rule(name="slow_rule", priority=10, ...) # 后执行 + +# 预编译正则表达式 +import re +pattern = re.compile(r'd\d+') # 编译一次 +rule = Rule( + conditions={"content": {"matches": pattern}}, + ... +) +``` + +### 4. 缓存结果 + +```python +from functools import lru_cache + +@lru_cache(maxsize=128) +def expensive_operation(data: str) -> str: + # 昂贵的计算 + return result +``` + +### 性能基准测试 + +```bash +# 运行基准测试 +python benchmarks/benchmark_parser.py +python benchmarks/benchmark_rules.py +``` + +--- + +## 错误处理 + +### 自定义异常 + +```python +from conventionalrp.utils.exceptions import ( + ParsingError, + RuleError, + PluginError +) + +try: + parser.parse_log("invalid.txt") +except ParsingError as e: + print(f"解析错误: {e}") +except FileNotFoundError as e: + print(f"文件未找到: {e}") +``` + +### 日志配置 + +```python +from conventionalrp.utils.logging_config import setup_logging +import logging + +# 设置日志级别 +setup_logging(level=logging.DEBUG) + +# 在您的代码中使用 +logger = logging.getLogger(__name__) +logger.debug("调试信息") +logger.info("普通信息") +logger.warning("警告") +logger.error("错误") +``` + +### 最佳实践 + +1. **始终验证输入** + ```python + def process_token(token: Dict[str, Any]) -> Dict[str, Any]: + if not isinstance(token, dict): + raise TypeError("Token must be a dictionary") + + if "type" not in token: + raise ValueError("Token must have a 'type' field") + + return processed_token + ``` + +2. **使用类型提示** + ```python + from typing import List, Dict, Any, Optional + + def parse_data(text: str) -> List[Dict[str, Any]]: + ... + ``` + +3. **提供清晰的错误消息** + ```python + if not file_path.exists(): + raise FileNotFoundError( + f"Log file not found: {file_path}\n" + f"Please check the file path and try again." + ) + ``` + +--- + +## 完整示例 + +查看 `examples/` 目录获取更多示例: + +- `basic_usage.py` - 基本使用 +- `rule_system_demo.py` - 规则系统演示 +- `plugin_system_demo.py` - 插件系统演示 +- `renderer_demo.py` - 渲染器演示 + +--- + +## 下一步 + +- 阅读 [API 文档](api.md) +- 查看 [插件开发指南](plugin_guide.md) +- 参与 [GitHub 讨论](https://github.com/HydroRoll-Team/conventional_role_play/discussions) diff --git a/docs/source/index.rst b/docs/source/index.rst index e69de29..e3b529f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -0,0 +1,8 @@ +.. toctree:: + :maxdepth: 2 + :caption: 内容目录 + + usage <usage.md> + advanced_usage <advanced_usage.md> + plugin_guide <plugin_guide.md> + api <api.md>
\ No newline at end of file diff --git a/docs/source/plugin_guide.md b/docs/source/plugin_guide.md new file mode 100644 index 0000000..7c146fe --- /dev/null +++ b/docs/source/plugin_guide.md @@ -0,0 +1,564 @@ +# 插件开发指南 + +本指南将帮助您创建自己的 ConventionalRP 插件。 + +## 目录 + +1. [插件基础](#插件基础) +2. [插件类型](#插件类型) +3. [开发流程](#开发流程) +4. [最佳实践](#最佳实践) +5. [发布插件](#发布插件) + +--- + +## 插件基础 + +### 什么是插件? + +插件是扩展 ConventionalRP 功能的独立模块。每个插件继承自基础 `Plugin` 类,并实现特定的接口。 + +### 插件生命周期 + +1. **发现** - PluginManager 扫描插件目录 +2. **加载** - 导入插件模块 +3. **初始化** - 调用 `initialize()` 方法 +4. **启用** - 调用 `on_enable()` 钩子 +5. **执行** - 调用 `process()` 方法处理数据 +6. **禁用** - 调用 `on_disable()` 钩子 + +### 基本结构 + +```python +from conventionalrp.plugins import Plugin +from typing import Any, Dict, Optional + +class MyPlugin(Plugin): + def __init__(self): + super().__init__(name="MyPlugin", version="1.0.0") + # 初始化插件状态 + self.data = {} + + def initialize(self, config: Optional[Dict[str, Any]] = None): + """初始化插件,加载配置""" + self.config = config or {} + self.logger.info(f"{self.name} initialized with config: {self.config}") + + def process(self, data: Any) -> Any: + """处理数据""" + self.logger.debug(f"Processing data: {data}") + # 实现您的处理逻辑 + return data + + def on_enable(self): + """插件启用时调用""" + super().on_enable() + # 执行启用逻辑(如打开文件、建立连接等) + + def on_disable(self): + """插件禁用时调用""" + # 执行清理逻辑(如关闭文件、断开连接等) + super().on_disable() +``` + +--- + +## 插件类型 + +### 1. 解析器插件 (ParserPlugin) + +解析器插件用于识别和解析特定格式的文本。 + +#### 接口 + +```python +from conventionalrp.plugins import ParserPlugin + +class MyParserPlugin(ParserPlugin): + def can_parse(self, text: str) -> bool: + """判断是否可以解析给定文本""" + raise NotImplementedError + + def parse(self, text: str) -> List[Dict[str, Any]]: + """解析文本并返回结果""" + raise NotImplementedError +``` + +#### 示例:CoC 规则解析器 + +```python +import re +from conventionalrp.plugins import ParserPlugin + +class CoCParserPlugin(ParserPlugin): + """Call of Cthulhu 规则解析器""" + + def __init__(self): + super().__init__("CoCParser", "1.0.0") + self.priority = 60 + + # CoC 特有的模式 + self.skill_check_pattern = re.compile( + r'\.(?:rc|san|sc)\s+(.+)', + re.IGNORECASE + ) + self.sanity_pattern = re.compile( + r'(\d+)/(\d+d\d+)', + re.IGNORECASE + ) + + def initialize(self, config=None): + self.config = config or {} + + def can_parse(self, text: str) -> bool: + """检查是否是 CoC 指令""" + return bool(self.skill_check_pattern.match(text)) + + def parse(self, text: str) -> List[Dict[str, Any]]: + """解析 CoC 指令""" + results = [] + + match = self.skill_check_pattern.match(text) + if match: + command_text = match.group(1) + + # 解析 SAN 检定 + san_match = self.sanity_pattern.search(command_text) + if san_match: + results.append({ + "type": "sanity_check", + "success_loss": san_match.group(1), + "failure_loss": san_match.group(2), + "content": text, + }) + else: + # 普通技能检定 + results.append({ + "type": "skill_check", + "skill": command_text.strip(), + "content": text, + }) + + return results +``` + +### 2. 处理器插件 (ProcessorPlugin) + +处理器插件用于转换和增强数据。 + +#### 示例:经验值追踪器 + +```python +from conventionalrp.plugins import ProcessorPlugin +import re + +class ExperienceTrackerPlugin(ProcessorPlugin): + """经验值追踪插件""" + + def __init__(self): + super().__init__("ExperienceTracker", "1.0.0") + self.character_exp = {} + self.exp_pattern = re.compile(r'获得\s*(\d+)\s*点?经验', re.IGNORECASE) + + def initialize(self, config=None): + self.config = config or {} + # 加载初始经验值 + if "initial_exp" in self.config: + self.character_exp = self.config["initial_exp"].copy() + + def process_token(self, token: Dict[str, Any]) -> Dict[str, Any]: + """处理 token 并追踪经验值""" + content = token.get("content", "") + speaker = token.get("speaker", "") + + # 检测经验值获取 + match = self.exp_pattern.search(content) + if match and speaker: + exp_gained = int(match.group(1)) + + if speaker not in self.character_exp: + self.character_exp[speaker] = 0 + + self.character_exp[speaker] += exp_gained + + # 添加经验值信息到 token + token["exp_data"] = { + "gained": exp_gained, + "total": self.character_exp[speaker], + "character": speaker, + } + + self.logger.info(f"{speaker} 获得 {exp_gained} 经验,总计 {self.character_exp[speaker]}") + + return token + + def get_leaderboard(self) -> List[Dict[str, Any]]: + """获取经验值排行榜""" + sorted_chars = sorted( + self.character_exp.items(), + key=lambda x: x[1], + reverse=True + ) + return [ + {"character": char, "exp": exp} + for char, exp in sorted_chars + ] +``` + +### 3. 渲染器插件 (RendererPlugin) + +渲染器插件用于生成特定格式的输出。 + +#### 示例:Discord Markdown 渲染器 + +```python +from conventionalrp.plugins import RendererPlugin + +class DiscordRenderer(RendererPlugin): + """Discord Markdown 渲染器""" + + def __init__(self): + super().__init__("DiscordRenderer", "1.0.0") + + def initialize(self, config=None): + self.config = config or {} + self.use_embeds = self.config.get("use_embeds", False) + + def render(self, data: Any) -> str: + """渲染为 Discord 格式""" + output = [] + + for token in data: + token_type = token.get("type", "unknown") + speaker = token.get("speaker", "") + content = token.get("content", "") + + if token_type == "dialogue": + # 使用 Discord 的粗体和引用 + output.append(f"**{speaker}:** {content}") + + elif token_type == "dice": + # 使用代码块高亮骰子结果 + result = token.get("result", "?") + output.append(f"`{content}` → **{result}**") + + elif token_type == "narration": + # 使用斜体表示旁白 + output.append(f"*{content}*") + + elif token_type == "system": + # 使用代码块表示系统消息 + output.append(f"```\n{content}\n```") + + else: + output.append(content) + + return "\n\n".join(output) +``` + +### 4. 分析器插件 (AnalyzerPlugin) + +分析器插件用于统计和分析数据。 + +#### 示例:对话分析器 + +```python +from conventionalrp.plugins import AnalyzerPlugin +from collections import Counter + +class DialogueAnalyzerPlugin(AnalyzerPlugin): + """对话分析插件""" + + def __init__(self): + super().__init__("DialogueAnalyzer", "1.0.0") + + def initialize(self, config=None): + self.config = config or {} + + def analyze(self, data: Any) -> Dict[str, Any]: + """分析对话数据""" + dialogue_count = Counter() + word_count = Counter() + total_words = 0 + + for token in data: + if token.get("type") == "dialogue": + speaker = token.get("speaker", "Unknown") + content = token.get("content", "") + + # 统计对话次数 + dialogue_count[speaker] += 1 + + # 统计词数 + words = len(content.split()) + word_count[speaker] += words + total_words += words + + # 计算参与度 + most_active = dialogue_count.most_common(1) + most_talkative = word_count.most_common(1) + + return { + "total_dialogues": sum(dialogue_count.values()), + "total_words": total_words, + "dialogue_count": dict(dialogue_count), + "word_count": dict(word_count), + "most_active_character": most_active[0][0] if most_active else None, + "most_talkative_character": most_talkative[0][0] if most_talkative else None, + "average_words_per_dialogue": total_words / sum(dialogue_count.values()) if dialogue_count else 0, + } +``` + +--- + +## 开发流程 + +### 1. 创建插件项目 + +```bash +mkdir my_plugin +cd my_plugin +touch my_plugin.py +touch config.json +touch README.md +``` + +### 2. 实现插件 + +```python +# my_plugin.py +from conventionalrp.plugins import ProcessorPlugin + +class MyPlugin(ProcessorPlugin): + # ... 实现代码 + pass +``` + +### 3. 添加配置 + +```json +{ + "name": "MyPlugin", + "version": "1.0.0", + "description": "My custom plugin", + "author": "Your Name", + "settings": { + "enable_feature_x": true, + "threshold": 10 + } +} +``` + +### 4. 测试插件 + +```python +# test_my_plugin.py +import unittest +from my_plugin import MyPlugin + +class TestMyPlugin(unittest.TestCase): + def setUp(self): + self.plugin = MyPlugin() + self.plugin.initialize() + + def test_process(self): + test_data = [{"type": "test", "content": "test"}] + result = self.plugin.process(test_data) + self.assertIsNotNone(result) + + def test_enable_disable(self): + self.assertTrue(self.plugin.enabled) + self.plugin.on_disable() + self.assertFalse(self.plugin.enabled) + +if __name__ == "__main__": + unittest.main() +``` + +### 5. 集成测试 + +```python +# integration_test.py +from conventionalrp.plugins import PluginManager +from my_plugin import MyPlugin + +manager = PluginManager() +plugin = MyPlugin() +plugin.initialize({"test": True}) +manager.register_plugin(plugin) + +# 测试数据 +test_data = [...] + +# 执行插件 +result = manager.execute_plugins(test_data) +print(result) +``` + +--- + +## 最佳实践 + +### 1. 命名约定 + +- 插件类名使用 PascalCase,如 `MyCustomPlugin` +- 文件名使用 snake_case,如 `my_custom_plugin.py` +- 配置键使用 snake_case + +### 2. 日志记录 + +```python +def process_token(self, token): + self.logger.debug(f"Processing token: {token['type']}") + + try: + result = self._do_processing(token) + self.logger.info("Processing successful") + return result + except Exception as e: + self.logger.error(f"Processing failed: {e}") + raise +``` + +### 3. 错误处理 + +```python +from conventionalrp.utils.exceptions import PluginError + +def initialize(self, config=None): + try: + self.config = config or {} + # 验证必需的配置 + if "required_key" not in self.config: + raise PluginError( + f"{self.name}: Missing required configuration 'required_key'" + ) + except Exception as e: + self.logger.error(f"Initialization failed: {e}") + raise +``` + +### 4. 性能优化 + +```python +# 使用缓存 +from functools import lru_cache + +@lru_cache(maxsize=128) +def expensive_operation(self, key: str) -> Any: + # 昂贵的计算 + return result + +# 批量处理 +def process(self, data: List[Dict]) -> List[Dict]: + # 一次性处理所有数据,而不是逐个处理 + return [self.process_token(token) for token in data] +``` + +### 5. 文档注释 + +```python +class MyPlugin(Plugin): + """ + 我的自定义插件 + + 这个插件用于... + + Attributes: + setting_x: 设置 X 的说明 + setting_y: 设置 Y 的说明 + + Example: + >>> plugin = MyPlugin() + >>> plugin.initialize({"setting_x": True}) + >>> result = plugin.process(data) + """ + + def process_token(self, token: Dict[str, Any]) -> Dict[str, Any]: + """ + 处理单个 token + + Args: + token: 输入 token + + Returns: + 处理后的 token + + Raises: + PluginError: 如果处理失败 + """ + pass +``` + +--- + +## 发布插件 + +### 1. 准备文件 + +创建以下文件: +- `README.md` - 插件说明 +- `LICENSE` - 开源许可证 +- `requirements.txt` - 依赖列表 +- `setup.py` 或 `pyproject.toml` - 打包配置 + +### 2. 打包 + +```bash +# 使用 setuptools +python setup.py sdist bdist_wheel + +# 或使用 poetry +poetry build +``` + +### 3. 发布 + +```bash +# 发布到 PyPI +twine upload dist/* + +# 或发布到 GitHub +git tag v1.0.0 +git push origin v1.0.0 +``` + +### 4. 文档 + +在 README.md 中包含: +- 插件描述 +- 安装方法 +- 使用示例 +- 配置选项 +- API 文档 +- 许可证信息 + +--- + +## 示例项目结构 + +``` +my_plugin/ +├── README.md +├── LICENSE +├── requirements.txt +├── setup.py +├── my_plugin/ +│ ├── __init__.py +│ ├── plugin.py +│ └── utils.py +├── tests/ +│ ├── test_plugin.py +│ └── test_utils.py +└── examples/ + └── example_usage.py +``` + +--- + +## 相关资源 + +- [插件系统演示](../../examples/plugin_system_demo.py) +- [示例插件](../../examples/plugins/) +- [API 文档](api.md) +- [高级使用指南](advanced_usage.md) diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index d823f5b..0000000 --- a/examples/README.md +++ /dev/null @@ -1,190 +0,0 @@ -# ConventionalRP 示例 - -本目录包含 ConventionalRP SDK 的使用示例。 - -## 目录结构 - -``` -examples/ -├── basic_usage.py # 基础使用示例 -├── custom_plugin.py # 自定义插件示例 -├── rules/ # 规则文件 -│ └── dnd5e_rules.json5 # D&D 5E 解析规则 -├── logs/ # 示例日志文件 -│ ├── sample_session.txt # 完整会话日志 -│ └── combat_log.txt # 战斗日志 -└── output/ # 输出文件目录(自动生成) -``` - -## 快速开始 - -### 1. 基础使用示例 - -演示如何解析 TRPG 日志并以多种格式输出: - -```bash -cd examples -python basic_usage.py -``` - -**输出:** -- `output/session_output.json` - JSON 格式 -- `output/session_output.html` - HTML 格式 -- `output/session_output.md` - Markdown 格式 - -### 2. 自定义插件示例 - -演示如何创建自定义插件进行数据分析: - -```bash -python custom_plugin.py -``` - -**功能:** -- 骰子统计分析 -- 对话提取 -- 角色行为分析 - -## 规则文件格式 - -规则文件使用 JSON5 格式(支持注释和尾随逗号): - -```json5 -{ - metadata: [{ - type: "metadata", - patterns: ["正则表达式"], - groups: ["字段名"], - priority: 100 - }], - - content: [{ - type: "内容类型", - match_type: "匹配模式", // enclosed, prefix, suffix - patterns: ["正则表达式"], - groups: ["提取字段"], - priority: 90 - }] -} -``` - -### 匹配模式说明 - -- **enclosed**: 封闭匹配(如 `**动作**`、`「对话」`) -- **prefix**: 前缀匹配(如 `[系统]消息`) -- **suffix**: 后缀匹配(文本结尾) - -### 优先级 - -- 数字越大优先级越高 -- 建议范围:1-100 -- 元数据通常设置为最高优先级(100) - -## 日志文件格式 - -标准 TRPG 日志格式: - -``` -[时间戳] <角色名> 内容 -``` - -**示例:** - -``` -[2025-10-24 14:30:01] <艾莉娅> 「我要检查这扇门」 -[2025-10-24 14:30:05] <DiceBot> 检定结果: [d20 = 18] -[2025-10-24 14:30:10] <DM> 你发现了陷阱 -``` - -## 自定义规则 - -你可以为不同的游戏系统创建自定义规则: - -### D&D 5E - -已提供 `rules/dnd5e_rules.json5` - -### 其他系统 - -创建新的规则文件,参考 D&D 5E 规则的结构: - -```bash -cp rules/dnd5e_rules.json5 rules/my_system_rules.json5 -# 然后编辑 my_system_rules.json5 -``` - -## 创建自定义插件 - -插件是用于扩展功能的 Python 类: - -```python -class MyPlugin: - def __init__(self): - self.name = "My Plugin" - - def process(self, parsed_data): - # 你的处理逻辑 - return result -``` - -查看 `custom_plugin.py` 了解完整示例。 - -## 常见模式 - -### 1. 骰子投掷 - -- `[d20 = 18]` - 简单投掷结果 -- `.r1d20+5` - 投掷命令 -- `(1d20+5 = 18)` - 完整投掷信息 - -### 2. 角色动作 - -- `*动作描述*` - 单星号 -- `**重要动作**` - 双星号 - -### 3. 对话 - -- `「对话内容」` - 中文引号 -- `"对话内容"` - 英文引号 -- `"对话内容"` - 弯引号 - -### 4. OOC(脱戏) - -- `((OOC内容))` - 双括号 -- `//OOC注释` - 双斜杠 - -### 5. 系统消息 - -- `[系统]消息内容` -- `[System]Message` - -## 疑难解答 - -### 问题:规则文件加载失败 - -**解决方案:** -1. 确保文件是有效的 JSON5 格式 -2. 检查正则表达式是否转义正确(使用 `\\` 而不是 `\`) -3. 验证文件编码为 UTF-8 - -### 问题:解析结果不正确 - -**解决方案:** -1. 调整规则的优先级 -2. 测试正则表达式(使用 https://regex101.com/) -3. 检查 match_type 是否正确 - -### 问题:中文字符显示异常 - -**解决方案:** -- 确保所有文件使用 UTF-8 编码 -- 在打开文件时指定 `encoding='utf-8'` - -## 更多示例 - -访问项目文档查看更多示例: -https://crp.hydroroll.team/ - -## 贡献 - -欢迎提交新的示例和规则文件!请参考 CONTRIBUTING.md diff --git a/examples/basic_usage.py b/examples/basic_usage.py index c327cb0..d1ab724 100644 --- a/examples/basic_usage.py +++ b/examples/basic_usage.py @@ -1,13 +1,6 @@ -#!/usr/bin/env python3 -""" -基础使用示例 -演示如何使用 ConventionalRP 解析和处理 TRPG 日志 -""" - import sys from pathlib import Path -# 添加 src 目录到 Python 路径 project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root / "src")) @@ -18,37 +11,22 @@ from conventionalrp.renderers.html_renderer import HTMLRenderer from conventionalrp.renderers.json_renderer import JSONRenderer from conventionalrp.renderers.markdown_renderer import MarkdownRenderer - -def main(): - # 获取示例文件路径 +def main(): example_dir = Path(__file__).parent rules_file = example_dir / "rules" / "dnd5e_rules.json5" log_file = example_dir / "logs" / "sample_session.txt" - - print("=" * 60) - print("ConventionalRP 基础使用示例") - print("=" * 60) - - # 步骤 1: 加载规则 - print("\n[步骤 1] 加载解析规则...") + parser = Parser() parser.load_rules(str(rules_file)) - print(f"✓ 规则加载成功: {rules_file.name}") + print(f"Rule loaded: {rules_file.name}") - # 步骤 2: 解析日志 - print("\n[步骤 2] 解析 TRPG 日志...") parsed_data = parser.parse_log(str(log_file)) - print(f"✓ 日志解析完成,共 {len(parsed_data)} 条记录") + print(f"Log parsed successfully, {len(parsed_data)} entries found.") - # 步骤 3: 处理解析结果 - print("\n[步骤 3] 处理解析后的数据...") processor = Processor() processed_data = processor.process_tokens(parsed_data) - print(f"✓ 数据处理完成") - - # 步骤 4: 渲染输出 - print("\n[步骤 4] 渲染输出...") - + print(f"Done processing data") + # JSON 格式 json_renderer = JSONRenderer() json_output = json_renderer.render(processed_data) @@ -56,7 +34,7 @@ def main(): json_file.parent.mkdir(exist_ok=True) with open(json_file, "w", encoding="utf-8") as f: f.write(json_output) - print(f"✓ JSON 输出已保存: {json_file}") + print(f"Json: {json_file}") # HTML 格式 html_renderer = HTMLRenderer() @@ -64,32 +42,14 @@ def main(): html_file = example_dir / "output" / "session_output.html" with open(html_file, "w", encoding="utf-8") as f: f.write(html_output) - print(f"✓ HTML 输出已保存: {html_file}") + print(f"HTML: {html_file}") - # Markdown 格式 md_renderer = MarkdownRenderer() md_output = md_renderer.render(processed_data) md_file = example_dir / "output" / "session_output.md" with open(md_file, "w", encoding="utf-8") as f: f.write(md_output) - print(f"✓ Markdown 输出已保存: {md_file}") - - # 预览前几条记录 - print("\n" + "=" * 60) - print("解析结果预览(前3条):") - print("=" * 60) - for i, entry in enumerate(parsed_data[:3], 1): - print(f"\n[记录 {i}]") - print(f" 时间: {entry.get('timestamp', 'N/A')}") - print(f" 发言者: {entry.get('speaker', 'N/A')}") - print(f" 内容类型数: {len(entry.get('content', []))}") - for content in entry.get('content', [])[:2]: # 只显示前2个内容 - print(f" - {content.get('type', 'unknown')}: {content.get('content', '')[:50]}...") - - print("\n" + "=" * 60) - print("✓ 所有步骤完成!") - print("=" * 60) - + print(f"Markdown: {md_file}") if __name__ == "__main__": main() diff --git a/examples/custom_plugin.py b/examples/custom_plugin.py index dd96311..5f77c41 100644 --- a/examples/custom_plugin.py +++ b/examples/custom_plugin.py @@ -1,14 +1,7 @@ -#!/usr/bin/env python3 -""" -自定义插件示例 -演示如何创建和使用自定义插件来扩展 ConventionalRP 的功能 -""" - import sys from typing import List, Dict, Any from pathlib import Path -# 添加 src 目录到 Python 路径 project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root / "src")) @@ -17,21 +10,12 @@ from conventionalrp.core.processor import Processor class DiceRollAnalyzer: - """骰子统计分析插件""" + """骰点统计分析插件""" def __init__(self): self.name = "Dice Roll Analyzer" def analyze(self, parsed_data: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - 分析日志中的所有骰子投掷 - - Args: - parsed_data: 解析后的日志数据 - - Returns: - 统计结果 - """ stats = { "total_rolls": 0, "by_character": {}, @@ -65,15 +49,6 @@ class DialogueExtractor: self.name = "Dialogue Extractor" def extract(self, parsed_data: List[Dict[str, Any]]) -> List[Dict[str, str]]: - """ - 提取所有角色对话 - - Args: - parsed_data: 解析后的日志数据 - - Returns: - 对话列表 - """ dialogues = [] for entry in parsed_data: @@ -92,59 +67,38 @@ class DialogueExtractor: def main(): - print("=" * 60) - print("ConventionalRP 自定义插件示例") - print("=" * 60) - - # 准备数据 example_dir = Path(__file__).parent rules_file = example_dir / "rules" / "dnd5e_rules.json5" log_file = example_dir / "logs" / "combat_log.txt" - - print("\n[1] 解析日志...") + + print("\nLoading log...") parser = Parser() parser.load_rules(str(rules_file)) parsed_data = parser.parse_log(str(log_file)) - print(f"✓ 解析完成,共 {len(parsed_data)} 条记录") + print(f"Done, {len(parsed_data)} in total") - # 使用骰子分析插件 - print("\n[2] 运行骰子统计分析插件...") dice_analyzer = DiceRollAnalyzer() dice_stats = dice_analyzer.analyze(parsed_data) - - print(f"\n骰子统计结果:") + + print(f"\nStatistics:") print(f" 总投掷次数: {dice_stats['total_rolls']}") print(f"\n 按角色统计:") for character, count in dice_stats['by_character'].items(): print(f" {character}: {count} 次") - print(f"\n 按骰子类型统计:") + print(f"\n Statistics in Dice Types:") for dice_type, count in dice_stats['dice_types'].items(): - print(f" d{dice_type}: {count} 次") + print(f" d{dice_type}: {count} times") - # 使用对话提取插件 - print("\n[3] 运行对话提取插件...") dialogue_extractor = DialogueExtractor() dialogues = dialogue_extractor.extract(parsed_data) - - print(f"\n提取到 {len(dialogues)} 条对话:") - for i, dialogue in enumerate(dialogues[:5], 1): # 只显示前5条 + + print(f"\nExtracted {len(dialogues)} dialogues:") + for i, dialogue in enumerate(dialogues[:5], 1): # Only show the first 5 print(f"\n [{i}] {dialogue['speaker']} ({dialogue['timestamp']})") print(f" {dialogue['dialogue']}") if len(dialogues) > 5: - print(f"\n ... 还有 {len(dialogues) - 5} 条对话") - - print("\n" + "=" * 60) - print("✓ 插件演示完成!") - print("=" * 60) - print("\n提示: 你可以创建自己的插件来实现:") - print(" - 战斗统计分析") - print(" - 角色行为分析") - print(" - 关键词提取") - print(" - 情感分析") - print(" - 自动摘要生成") - print(" - ... 以及更多!") - + print(f"\n ... and {len(dialogues) - 5} more dialogues") if __name__ == "__main__": main() diff --git a/examples/plugin_system_demo.py b/examples/plugin_system_demo.py new file mode 100644 index 0000000..1899b34 --- /dev/null +++ b/examples/plugin_system_demo.py @@ -0,0 +1,173 @@ +import sys +import os +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +from conventionalrp.plugins import PluginManager +from conventionalrp.core.parser import Parser + +sys.path.insert(0, str(Path(__file__).parent / "plugins")) +from dice_analyzer_plugin import DiceAnalyzerPlugin +from combat_tracker_plugin import CombatTrackerPlugin + + +def demo_basic_plugin_usage(): + manager = PluginManager() + + dice_analyzer = DiceAnalyzerPlugin() + dice_analyzer.initialize() + manager.register_plugin(dice_analyzer) + + combat_tracker = CombatTrackerPlugin() + combat_tracker.initialize() + manager.register_plugin(combat_tracker) + + print("\nRegistered Plugins:") + for plugin_info in manager.list_plugins(): + print(f" - {plugin_info['name']} v{plugin_info['version']} (type: {plugin_info['type']})") + + stats = manager.get_statistics() + print(f"\nPlugin Statistics: {stats}") + + +def demo_processor_plugin(): + manager = PluginManager() + + combat_tracker = CombatTrackerPlugin() + combat_tracker.initialize() + manager.register_plugin(combat_tracker) + + combat_log = [ + {"type": "dialogue", "speaker": "战士", "content": "我攻击兽人,造成12点伤害!"}, + {"type": "dialogue", "speaker": "法师", "content": "火球术!造成28点火焰伤害"}, + {"type": "dialogue", "speaker": "牧师", "content": "治疗之光,恢复15点生命值"}, + {"type": "dialogue", "speaker": "战士", "content": "重击!造成18点伤害"}, + {"type": "dialogue", "speaker": "牧师", "content": "群体治疗,恢复10点生命值"}, + ] + + print("\nProcessing Combat Log:") + from conventionalrp.plugins.base import ProcessorPlugin + processed_log = manager.execute_plugins(combat_log, plugin_type=ProcessorPlugin) + + for token in processed_log: + if "combat_data" in token: + print(f" {token['speaker']}: {token['content']}") + print(f" -> {token['combat_data']}") + + print("\nCombat Summary:") + summary = combat_tracker.get_combat_summary() + print(f" 总伤害: {summary['total_damage']}") + print(f" 总治疗: {summary['total_healing']}") + print(f" 净伤害: {summary['net_damage']}") + print("\n 角色统计:") + for character, stats in summary['character_stats'].items(): + print(f" {character}: 造成伤害={stats['damage_dealt']}, 治疗={stats['healing_done']}") + + +def demo_analyzer_plugin(): + manager = PluginManager() + + dice_analyzer = DiceAnalyzerPlugin() + dice_analyzer.initialize() + manager.register_plugin(dice_analyzer) + + dice_rolls = [ + {"type": "dice", "content": "d20=15", "result": 15}, + {"type": "success", "content": "检定成功"}, + {"type": "dice", "content": "d6=4", "result": 4}, + {"type": "dice", "content": "d20=20", "result": 20}, + {"type": "success", "content": "大成功!Critical hit!"}, + {"type": "dice", "content": "d20=1", "result": 1}, + {"type": "failure", "content": "大失败..."}, + {"type": "dice", "content": "d20=12", "result": 12}, + {"type": "dice", "content": "d6=3", "result": 3}, + {"type": "success", "content": "检定成功"}, + ] + + print("\nDice Roll Data:") + for roll in dice_rolls: + if roll["type"] == "dice": + print(f" {roll['content']}") + + from conventionalrp.plugins.base import AnalyzerPlugin + analysis = manager.execute_plugins(dice_rolls, plugin_type=AnalyzerPlugin) + + print("\nAnalyze result:") + print(f" 总投掷次数: {analysis['total_rolls']}") + print(f" 骰子类型分布: {analysis['dice_types']}") + print(f" 成功次数: {analysis['success_count']}") + print(f" 失败次数: {analysis['failure_count']}") + print(f" 大成功次数: {analysis['critical_hits']}") + print(f" 大失败次数: {analysis['critical_fails']}") + print(f" 成功率: {analysis['success_rate']:.1%}") + print(f" 出现极值比率: {analysis['critical_rate']:.1%}") + + +def demo_plugin_enable_disable(): + manager = PluginManager() + dice_analyzer = DiceAnalyzerPlugin() + dice_analyzer.initialize() + manager.register_plugin(dice_analyzer) + + combat_tracker = CombatTrackerPlugin() + combat_tracker.initialize() + manager.register_plugin(combat_tracker) + + print("\nInitial State:") + for plugin_info in manager.list_plugins(): + print(f" {plugin_info['name']}: {'Enabled' if plugin_info['enabled'] else 'Disabled'}") + + # Disable DiceAnalyzer + print("\nDisabling DiceAnalyzer...") + manager.disable_plugin("DiceAnalyzer") + + print("\nCurrent State:") + for plugin_info in manager.list_plugins(): + print(f" {plugin_info['name']}: {'Enabled' if plugin_info['enabled'] else 'Disabled'}") + + print("\nRe-enabling DiceAnalyzer...") + manager.enable_plugin("DiceAnalyzer") + + print("\nFinal State:") + for plugin_info in manager.list_plugins(): + print(f" {plugin_info['name']}: {'Enabled' if plugin_info['enabled'] else 'Disabled'}") + + +def demo_plugin_discovery(): + plugin_dir = Path(__file__).parent / "plugins" + manager = PluginManager(plugin_dirs=[str(plugin_dir)]) + + print(f"\nSearching for plugins in directory: {plugin_dir}") + discovered = manager.discover_plugins() + + print(f"\nFound {len(discovered)} plugin modules:") + for module in discovered: + print(f" - {module}") + + print("\nLoading plugins...") + for py_file in plugin_dir.glob("*.py"): + if py_file.name.startswith("_"): + continue + + plugin_class = manager.load_plugin_from_file(str(py_file)) + if plugin_class: + print(f" ✓ Successfully loaded: {py_file.name}") + + +def main(): + try: + demo_basic_plugin_usage() + demo_processor_plugin() + demo_analyzer_plugin() + demo_plugin_enable_disable() + demo_plugin_discovery() + + except Exception as e: + print(f"\n{e!r}: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/examples/plugins/combat_tracker_plugin.py b/examples/plugins/combat_tracker_plugin.py new file mode 100644 index 0000000..a12bc9e --- /dev/null +++ b/examples/plugins/combat_tracker_plugin.py @@ -0,0 +1,96 @@ +import sys +import os +from pathlib import Path +import re +from typing import Any, Dict, List + +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) + +from conventionalrp.plugins.base import ProcessorPlugin + + +class CombatTrackerPlugin(ProcessorPlugin): + """战斗数据追踪插件""" + + def __init__(self): + super().__init__("CombatTracker", "1.0.0") + self.damage_pattern = re.compile(r'(\d+)\s*点?(伤害|damage|dmg)', re.IGNORECASE) + self.heal_pattern = re.compile(r'(\d+)\s*点?(治疗|healing|heal)', re.IGNORECASE) + + def initialize(self, config: Dict[str, Any] | None = None): + self.config = config or {} + self.total_damage = 0 + self.total_healing = 0 + self.character_stats = {} + self.logger.info("CombatTrackerPlugin initialized") + + def process_token(self, token: Dict[str, Any]) -> Dict[str, Any]: + content = token.get("content", "") + speaker = token.get("speaker", "Unknown") + + damage_match = self.damage_pattern.search(content) + if damage_match: + damage = int(damage_match.group(1)) + self.total_damage += damage + + if speaker not in self.character_stats: + self.character_stats[speaker] = {"damage_dealt": 0, "healing_done": 0} + self.character_stats[speaker]["damage_dealt"] += damage + + token["combat_data"] = { + "type": "damage", + "amount": damage, + "total_damage": self.total_damage, + } + + heal_match = self.heal_pattern.search(content) + if heal_match: + healing = int(heal_match.group(1)) + self.total_healing += healing + + if speaker not in self.character_stats: + self.character_stats[speaker] = {"damage_dealt": 0, "healing_done": 0} + self.character_stats[speaker]["healing_done"] += healing + + token["combat_data"] = { + "type": "healing", + "amount": healing, + "total_healing": self.total_healing, + } + + return token + + def get_combat_summary(self) -> Dict[str, Any]: + return { + "total_damage": self.total_damage, + "total_healing": self.total_healing, + "net_damage": self.total_damage - self.total_healing, + "character_stats": self.character_stats, + } + + def reset_stats(self): + self.total_damage = 0 + self.total_healing = 0 + self.character_stats.clear() + self.logger.info("Combat stats reset") + + +if __name__ == "__main__": + plugin = CombatTrackerPlugin() + plugin.initialize() + + test_tokens = [ + {"type": "dialogue", "speaker": "战士", "content": "我攻击兽人,造成12点伤害"}, + {"type": "dialogue", "speaker": "法师", "content": "火球术命中,造成28点伤害"}, + {"type": "dialogue", "speaker": "牧师", "content": "治疗术,恢复15点生命值"}, + {"type": "dialogue", "speaker": "战士", "content": "再次攻击,造成8点伤害"}, + ] + + for token in test_tokens: + processed = plugin.process_token(token) + if "combat_data" in processed: + print(f" {processed['speaker']}: {processed['combat_data']}") + + summary = plugin.get_combat_summary() + for key, value in summary.items(): + print(f" {key}: {value}") diff --git a/examples/plugins/dice_analyzer_plugin.py b/examples/plugins/dice_analyzer_plugin.py new file mode 100644 index 0000000..3ab2fb9 --- /dev/null +++ b/examples/plugins/dice_analyzer_plugin.py @@ -0,0 +1,90 @@ +import sys +import os +from pathlib import Path +import re +from typing import Any, Dict, List + +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) + +from conventionalrp.plugins.base import AnalyzerPlugin + + +class DiceAnalyzerPlugin(AnalyzerPlugin): + """骰子投掷数据分析插件""" + def __init__(self): + super().__init__("DiceAnalyzer", "1.0.0") + self.dice_pattern = re.compile(r'd(\d+)') + + def initialize(self, config: Dict[str, Any] | None = None): + self.config = config or {} + self.logger.info("DiceAnalyzerPlugin initialized") + + def analyze(self, data: Any) -> Dict[str, Any]: + if not isinstance(data, list): + return {"error": "Input must be a list of tokens"} + + total_rolls = 0 + dice_types = {} + success_count = 0 + failure_count = 0 + critical_hits = 0 + critical_fails = 0 + + for token in data: + if not isinstance(token, dict): + continue + + token_type = token.get("type", "") + content = token.get("content", "") + + if token_type == "dice": + total_rolls += 1 + + match = self.dice_pattern.search(content) + if match: + dice_type = f"d{match.group(1)}" + dice_types[dice_type] = dice_types.get(dice_type, 0) + 1 + + if token_type == "success": + success_count += 1 + elif token_type == "failure": + failure_count += 1 + + if "critical" in content.lower(): + if "success" in token_type or "成功" in content: + critical_hits += 1 + elif "failure" in token_type or "失败" in content: + critical_fails += 1 + + result = { + "total_rolls": total_rolls, + "dice_types": dice_types, + "success_count": success_count, + "failure_count": failure_count, + "critical_hits": critical_hits, + "critical_fails": critical_fails, + "success_rate": success_count / total_rolls if total_rolls > 0 else 0, + "critical_rate": (critical_hits + critical_fails) / total_rolls if total_rolls > 0 else 0, + } + + self.logger.info(f"Analyzed {total_rolls} dice rolls") + return result + + +if __name__ == "__main__": + plugin = DiceAnalyzerPlugin() + plugin.initialize() + + test_data = [ + {"type": "dice", "content": "d20=15"}, + {"type": "success", "content": "检定成功"}, + {"type": "dice", "content": "d6=4"}, + {"type": "dice", "content": "d20=20"}, + {"type": "success", "content": "大成功!Critical hit!"}, + {"type": "dice", "content": "d20=1"}, + {"type": "failure", "content": "大失败..."}, + ] + + result = plugin.analyze(test_data) + for key, value in result.items(): + print(f" {key}: {value}") diff --git a/examples/renderer_demo.py b/examples/renderer_demo.py new file mode 100644 index 0000000..fa89b42 --- /dev/null +++ b/examples/renderer_demo.py @@ -0,0 +1,147 @@ +import sys +import os +from pathlib import Path + +# 添加父目录到 Python 路径 +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +from conventionalrp.renderers.html_renderer import HTMLRenderer +from conventionalrp.renderers.markdown_renderer import MarkdownRenderer +from conventionalrp.renderers.json_renderer import JSONRenderer + + +TEST_DATA = [ + { + "type": "dialogue", + "speaker": "战士", + "content": "我们需要更小心地前进,前面可能有陷阱。", + "timestamp": "2024-01-15 20:30:15", + "tags": ["对话", "警告"], + }, + { + "type": "dice", + "speaker": "战士", + "content": "d20+5", + "result": 18, + "timestamp": "2024-01-15 20:30:30", + }, + { + "type": "success", + "content": "战士成功发现了隐藏的陷阱!", + "timestamp": "2024-01-15 20:30:35", + }, + { + "type": "narration", + "content": "昏暗的走廊中,石板地面上隐约可见一些不寻常的纹路。", + "timestamp": "2024-01-15 20:31:00", + "tags": ["环境描述"], + }, + { + "type": "dialogue", + "speaker": "法师", + "content": "让我施放侦测魔法,看看这里是否有魔法陷阱。", + "timestamp": "2024-01-15 20:31:15", + }, + { + "type": "dice", + "speaker": "法师", + "content": "d20+8", + "result": 23, + "timestamp": "2024-01-15 20:31:20", + "combat_data": {"type": "damage", "amount": 12, "total_damage": 12}, + }, + { + "type": "system", + "content": "法师侦测到前方10英尺处有一个魔法陷阱(火球术触发)。", + "timestamp": "2024-01-15 20:31:25", + }, +] + + +def demo_html_themes(): + themes = ["light", "dark", "fantasy"] + + for theme in themes: + print(f"\nGenerate {theme} theme...") + renderer = HTMLRenderer(theme=theme) + html_output = renderer.render(TEST_DATA) + + output_file = f"output_html_{theme}.html" + with open(output_file, "w", encoding="utf-8") as f: + f.write(html_output) + +def demo_html_custom_css(): + custom_css = """ + body { + background-image: linear-gradient(120deg, #fdfbfb 0%, #ebedee 100%); + } + + .token { + animation: fadeIn 0.5s; + } + + @keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } + } + """ + + renderer = HTMLRenderer(theme="light", custom_css=custom_css) + html_output = renderer.render(TEST_DATA) + + output_file = "output_html_custom.html" + with open(output_file, "w", encoding="utf-8") as f: + f.write(html_output) + + +def demo_markdown_styles(): + renderer_full = MarkdownRenderer(enable_syntax_hints=True, enable_emoji=True) + md_output_full = renderer_full.render(TEST_DATA) + + with open("output_markdown_full.md", "w", encoding="utf-8") as f: + f.write(md_output_full) + + renderer_simple = MarkdownRenderer(enable_syntax_hints=False, enable_emoji=False) + md_output_simple = renderer_simple.render(TEST_DATA) + + with open("output_markdown_simple.md", "w", encoding="utf-8") as f: + f.write(md_output_simple) + + print(md_output_full[:100] + "...") + + +def demo_json_formats(): + renderer_pretty = JSONRenderer(pretty=True, indent=2) + json_output_pretty = renderer_pretty.render(TEST_DATA) + + with open("output_json_pretty.json", "w", encoding="utf-8") as f: + f.write(json_output_pretty) + + renderer_compact = JSONRenderer(pretty=False) + json_output_compact = renderer_compact.render(TEST_DATA) + + with open("output_json_compact.json", "w", encoding="utf-8") as f: + f.write(json_output_compact) + + renderer_sorted = JSONRenderer(pretty=True, indent=4, sort_keys=True) + json_output_sorted = renderer_sorted.render(TEST_DATA) + + with open("output_json_sorted.json", "w", encoding="utf-8") as f: + f.write(json_output_sorted) + +def main(): + + try: + demo_html_themes() + demo_html_custom_css() + demo_markdown_styles() + demo_json_formats() + + except Exception as e: + print(f"\n{e!r}: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/examples/rule_system_demo.py b/examples/rule_system_demo.py new file mode 100644 index 0000000..d38056b --- /dev/null +++ b/examples/rule_system_demo.py @@ -0,0 +1,210 @@ +import sys +from pathlib import Path + +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root / "src")) + +from conventionalrp.core.rules import Rule, RuleEngine +from conventionalrp.core.processor import Processor + + +def example_1_simple_rules(): + rule = Rule( + name="tag_dice_rolls", + condition={"type": "dice_roll"}, + action={"type": "add_tag", "tag": "game_mechanics"}, + priority=100 + ) + + engine = RuleEngine() + engine.add_rule(rule) + + data = {"type": "dice_roll", "content": "[d20 = 18]"} + + print(f"Original data: {data}") + result = engine.process(data) + print(f"Processed result: {result}") + print() + + +def example_2_conditional_rules(): + engine = RuleEngine() + + # Rule1: High rolls (>=15) get "success" tag + engine.add_rule_dict( + name="high_roll", + condition={ + "type": "dice_roll", + "result": {"type": "greater_than", "value": 15} + }, + action={"type": "add_tag", "tag": "success"}, + priority=100 + ) + + # Rule2: Low rolls (<10) get "failure" tag + engine.add_rule_dict( + name="low_roll", + condition={ + "type": "dice_roll", + "result": {"type": "less_than", "value": 10} + }, + action={"type": "add_tag", "tag": "failure"}, + priority=100 + ) + + test_cases = [ + {"type": "dice_roll", "result": 18, "content": "[d20 = 18]"}, + {"type": "dice_roll", "result": 5, "content": "[d20 = 5]"}, + {"type": "dice_roll", "result": 12, "content": "[d20 = 12]"}, + ] + + for data in test_cases: + result = engine.process(data) + print(f"结果: {data['result']} -> 标签: {result.get('tags', [])}") + print() + + +def example_3_field_transformation(): + engine = RuleEngine() + + # Rule: Normalize speaker names + engine.add_rule_dict( + name="normalize_speaker", + condition={"type": "metadata"}, + action={ + "type": "transform", + "field": "speaker", + "function": "upper" + }, + priority=90 + ) + + data = { + "type": "metadata", + "speaker": "艾莉娅", + "timestamp": "2025-10-24 14:30:01" + } + + print(f"Original data: {data}") + result = engine.process(data) + print(f"Processed result: {result}") + print() + + +def example_4_processor_with_rules(): + processor = Processor() + + # Add rule: Highlight important dialogues + processor.add_rule(Rule( + name="highlight_important_dialogue", + condition={ + "type": "dialogue", + "content": {"type": "contains", "value": "重要"} + }, + action={"type": "add_tag", "tag": "important"}, + priority=100 + )) + + # Add rule: Mark all metadata as processed + processor.add_rule(Rule( + name="mark_metadata", + condition={"type": "metadata"}, + action={"type": "set_field", "field": "processed_by", "value": "rule_engine"}, + priority=90 + )) + + tokens = [ + {"type": "metadata", "speaker": "DM", "timestamp": "2025-10-24"}, + {"type": "dialogue", "content": "这是重要的线索"}, + {"type": "dialogue", "content": "普通对话"}, + {"type": "dice_roll", "result": 20}, + ] + + print(f"Processing {len(tokens)} tokens...") + results = processor.process_tokens(tokens) + + for i, result in enumerate(results): + print(f" [{i+1}] {result.get('type')}: " + f"Tags={result.get('tags', [])} " + f"Processed by={result.get('processed_by', 'N/A')}") + print() + + +def example_5_custom_processor(): + processor = Processor() + + # Custom processing function: Count characters + def add_char_count(data): + if "content" in data: + data["char_count"] = len(data["content"]) + return data + + # Custom processing function: Ensure timestamp exists + def ensure_timestamp(data): + if "timestamp" not in data: + from datetime import datetime + data["timestamp"] = datetime.now().isoformat() + return data + + processor.add_processor(add_char_count) + processor.add_processor(ensure_timestamp) + + test_data = [ + {"type": "dialogue", "content": "你好世界"}, + {"type": "text", "content": "这是一段很长的文本内容"}, + ] + + results = processor.process_tokens(test_data) + + for result in results: + print(f" {result.get('type')}: " + f"Character count={result.get('char_count')} " + f"Timestamp={result.get('timestamp', 'N/A')[:19]}") + print() + + +def example_6_priority_and_order(): + engine = RuleEngine() + + engine.add_rule_dict( + name="low_priority", + condition={"type": "test"}, + action={"type": "set_field", "field": "processed_by", "value": "low"}, + priority=10 + ) + + engine.add_rule_dict( + name="high_priority", + condition={"type": "test"}, + action={"type": "set_field", "field": "processed_by", "value": "high"}, + priority=100 + ) + + engine.add_rule_dict( + name="medium_priority", + condition={"type": "test"}, + action={"type": "set_field", "field": "processed_by", "value": "medium"}, + priority=50 + ) + + data = {"type": "test"} + + result1 = engine.process(data, apply_all=False) + print(f"Only apply the highest priority matching rule: {result1}") + + result2 = engine.process(data, apply_all=True) + print(f"Apply all matching rules: {result2}") + print() + + +def main(): + example_1_simple_rules() + example_2_conditional_rules() + example_3_field_transformation() + example_4_processor_with_rules() + example_5_custom_processor() + example_6_priority_and_order() + + +if __name__ == "__main__": + main() diff --git a/src/conventionalrp/__init__.py b/src/conventionalrp/__init__.py index 06dbd63..ab4b17d 100644 --- a/src/conventionalrp/__init__.py +++ b/src/conventionalrp/__init__.py @@ -1,15 +1,45 @@ +""" +Conventional Role Play SDK (ConventionalRP) +""" + import sys from importlib.metadata import version from . import _core +from .core import Parser, Processor, Rule, RuleEngine +from .utils import ( + setup_logging, + get_logger, + ConventionalRPError, + ParserError, + RuleError, + ProcessorError, + ValidationError, + ConfigurationError, +) -__all__ = ["_core", "__version__"] +__all__ = [ + "Parser", + "Processor", + "Rule", + "RuleEngine", + "setup_logging", + "get_logger", + "ConventionalRPError", + "ParserError", + "RuleError", + "ProcessorError", + "ValidationError", + "ConfigurationError", + "__version__", +] if sys.version_info >= (3, 8): - # For Python 3.8+ __version__ = version("conventionalrp") elif sys.version_info < (3, 8): from pkg_resources import get_distribution - # For Python < 3.8 __version__ = get_distribution("conventionalrp").version + +_default_logger = setup_logging(level="INFO") + diff --git a/src/conventionalrp/__main__.py b/src/conventionalrp/__main__.py index fa4f45e..e69de29 100644 --- a/src/conventionalrp/__main__.py +++ b/src/conventionalrp/__main__.py @@ -1,9 +0,0 @@ -from ._core import sum_as_string - - -def main(): - print(sum_as_string(1, 2)) - - -if __name__ == "__main__": - main() diff --git a/src/conventionalrp/_core.pyi b/src/conventionalrp/_core.pyi index 0805887..6f87269 100644 --- a/src/conventionalrp/_core.pyi +++ b/src/conventionalrp/_core.pyi @@ -1,3 +1,237 @@ -def sum_as_string(a: int, b: int) -> str: ... +""" +提供高性能的文本解析和匹配功能 +""" -class Base: ... +from typing import Dict, List, Optional, Tuple + +class Base: + """基础类(向后兼容)""" + ... + +class Token: + """ + 解析后的 Token + + 表示文本中的一个语义单元,包含类型、内容和元数据 + """ + + token_type: str + content: str + metadata: Dict[str, str] + + def __init__(self, token_type: str, content: str) -> None: + """ + 创建新的 Token + + Args: + token_type: Token 类型 + content: Token 内容 + """ + ... + + def add_metadata(self, key: str, value: str) -> None: + """ + 添加元数据 + + Args: + key: 元数据键 + value: 元数据值 + """ + ... + + def get_metadata(self, key: str) -> Optional[str]: + """ + 获取元数据 + + Args: + key: 元数据键 + + Returns: + 元数据值,如果不存在返回 None + """ + ... + + def to_dict(self) -> Dict[str, any]: + """ + 转换为 Python 字典 + + Returns: + 包含 Token 所有信息的字典 + """ + ... + + def __repr__(self) -> str: ... + +class RegexRule: + """ + 正则表达式规则 + + 用于文本匹配和提取 + """ + + rule_type: str + priority: int + + def __init__(self, pattern: str, rule_type: str, priority: int) -> None: + """ + 创建正则规则 + + Args: + pattern: 正则表达式模式 + rule_type: 规则类型 + priority: 优先级(数字越大优先级越高) + + Raises: + ValueError: 如果正则表达式无效 + """ + ... + + def matches(self, text: str) -> bool: + """ + 测试文本是否匹配 + + Args: + text: 待测试的文本 + + Returns: + 是否匹配 + """ + ... + + def extract(self, text: str) -> Optional[List[str]]: + """ + 提取匹配的捕获组 + + Args: + text: 待提取的文本 + + Returns: + 捕获组列表,如果不匹配返回 None + """ + ... + + def find_all(self, text: str) -> List[Tuple[int, int, str]]: + """ + 查找所有匹配 + + Args: + text: 待搜索的文本 + + Returns: + 列表,每个元素为 (start, end, matched_text) + """ + ... + +class TextParser: + """ + 高性能文本解析器 + + 支持多规则、优先级排序的文本解析 + """ + + def __init__(self) -> None: + """创建新的解析器""" + ... + + def add_rule(self, pattern: str, rule_type: str, priority: int) -> None: + """ + 添加解析规则 + + Args: + pattern: 正则表达式模式 + rule_type: 规则类型 + priority: 优先级 + + Raises: + ValueError: 如果正则表达式无效 + """ + ... + + def parse_line(self, text: str) -> List[Tuple[str, str, int, int]]: + """ + 解析单行文本 + + Args: + text: 待解析的文本 + + Returns: + 列表,每个元素为 (type, content, start, end) + """ + ... + + def parse_lines(self, lines: List[str]) -> List[List[Dict[str, any]]]: + """ + 批量解析多行文本 + + Args: + lines: 文本行列表 + + Returns: + 每行的解析结果列表 + """ + ... + + def clear_rules(self) -> None: + """清除所有规则""" + ... + + def rule_count(self) -> int: + """ + 获取规则数量 + + Returns: + 当前规则数量 + """ + ... + +class FastMatcher: + """ + 快速字符串匹配器 + + 用于高效的多模式字符串匹配 + """ + + def __init__(self, patterns: List[str]) -> None: + """ + 创建匹配器 + + Args: + patterns: 模式列表 + """ + ... + + def contains_any(self, text: str) -> bool: + """ + 检查文本是否包含任意模式 + + Args: + text: 待检查的文本 + + Returns: + 是否包含任意模式 + """ + ... + + def find_matches(self, text: str) -> List[str]: + """ + 查找所有匹配的模式 + + Args: + text: 待搜索的文本 + + Returns: + 匹配的模式列表 + """ + ... + + def count_matches(self, text: str) -> Dict[str, int]: + """ + 统计每个模式的出现次数 + + Args: + text: 待统计的文本 + + Returns: + 字典,键为模式,值为出现次数 + """ + ... diff --git a/src/conventionalrp/base.py b/src/conventionalrp/base.py deleted file mode 100644 index e69de29..0000000 --- a/src/conventionalrp/base.py +++ /dev/null diff --git a/src/conventionalrp/core/__init__.py b/src/conventionalrp/core/__init__.py index 91d0f8f..08829b8 100644 --- a/src/conventionalrp/core/__init__.py +++ b/src/conventionalrp/core/__init__.py @@ -1,3 +1,6 @@ -""" -This file initializes the core module of the conventionalrp SDK. -""" +from .parser import Parser +from .processor import Processor +from .rules import Rule, RuleEngine + +__all__ = ["Parser", "Processor", "Rule", "RuleEngine"] + diff --git a/src/conventionalrp/core/processor.py b/src/conventionalrp/core/processor.py index bc74ffb..12ca32b 100644 --- a/src/conventionalrp/core/processor.py +++ b/src/conventionalrp/core/processor.py @@ -1,68 +1,104 @@ -from typing import List, Dict, Any, Optional +from typing import List, Dict, Any, Optional, Callable +import logging +from .rules import RuleEngine, Rule + +logger = logging.getLogger(__name__) class Processor: - """处理器,用于处理解析后的token""" - def __init__(self, rules: Optional[Dict[str, Any]] = None): - """ - 初始化处理器 - - Args: - rules: 处理规则(可选) - """ self.rules = rules or {} - - def process_tokens(self, tokens: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - 处理token列表 + self.rule_engine = RuleEngine() + self.custom_processors: List[Callable] = [] + + self._load_rules_to_engine() - Args: - tokens: 解析后的token列表 + logger.info("Processor initialized with %d rules", + self.rule_engine.rule_count()) + + def _load_rules_to_engine(self): + if not isinstance(self.rules, dict): + return + + rules_list = self.rules.get("rules", []) + for rule_dict in rules_list: + if not isinstance(rule_dict, dict): + continue - Returns: - 处理后的数据列表 - """ + try: + self.rule_engine.add_rule_dict( + name=rule_dict.get("name", "unnamed"), + condition=rule_dict.get("condition", {}), + action=rule_dict.get("action", {}), + priority=rule_dict.get("priority", 50) + ) + except Exception as e: + logger.warning("Failed to load rule: %s", e) + + def add_rule(self, rule: Rule): + self.rule_engine.add_rule(rule) + logger.debug("Added rule: %s", rule.name) + + def add_processor(self, processor: Callable[[Dict[str, Any]], Dict[str, Any]]): + self.custom_processors.append(processor) + logger.debug("Added custom processor") + + def process_tokens( + self, + tokens: List[Dict[str, Any]], + apply_all_rules: bool = False + ) -> List[Dict[str, Any]]: + if not tokens: + logger.warning("Empty token list provided") + return [] + + logger.info("Processing %d tokens", len(tokens)) processed_data = [] - for token in tokens: - processed_token = self.apply_rules(token) - processed_data.append(processed_token) - return processed_data - - def apply_rules(self, token: Dict[str, Any]) -> Dict[str, Any]: - """ - 对单个token应用规则 - Args: - token: 单个token - - Returns: - 处理后的token - """ - # 基础实现:直接返回token - # 可以在此添加更多处理逻辑 + for i, token in enumerate(tokens): + try: + processed_token = self.process_single_token(token, apply_all_rules) + processed_data.append(processed_token) + except Exception as e: + logger.error("Error processing token %d: %s", i, e) + # 发生错误时保留原始 token + processed_data.append(token) + + logger.info("Successfully processed %d tokens", len(processed_data)) + return processed_data + + def process_single_token( + self, + token: Dict[str, Any], + apply_all_rules: bool = False + ) -> Dict[str, Any]: processed = token.copy() - # 添加处理时间戳 + if self.rule_engine.rule_count() > 0: + processed = self.rule_engine.process(processed, apply_all_rules) + + for processor in self.custom_processors: + try: + processed = processor(processed) + except Exception as e: + logger.error("Custom processor failed: %s", e) + if "timestamp" in processed: processed["processed"] = True return processed - - def generate_output(self, processed_data: List[Dict[str, Any]], format_type: str) -> str: - """ - 生成指定格式的输出 + + def apply_rules(self, token: Dict[str, Any]) -> Dict[str, Any]: + return self.process_single_token(token) + + def generate_output( + self, + processed_data: List[Dict[str, Any]], + format_type: str + ) -> str: + logger.info("Generating %s output for %d items", + format_type, len(processed_data)) - Args: - processed_data: 处理后的数据 - format_type: 输出格式 (json/html/markdown) - - Returns: - 格式化后的字符串 - - Raises: - ValueError: 不支持的格式类型 - """ if format_type == "json": return self.generate_json_output(processed_data) elif format_type == "html": @@ -71,20 +107,24 @@ class Processor: return self.generate_markdown_output(processed_data) else: raise ValueError(f"Unsupported format type: {format_type}") - + def generate_json_output(self, processed_data: List[Dict[str, Any]]) -> str: - """生成JSON格式输出""" import json return json.dumps(processed_data, ensure_ascii=False, indent=2) - + def generate_html_output(self, processed_data: List[Dict[str, Any]]) -> str: - """生成HTML格式输出""" return ( "<html><body>" + "".join(f"<p>{data}</p>" for data in processed_data) + "</body></html>" ) - + def generate_markdown_output(self, processed_data: List[Dict[str, Any]]) -> str: - """生成Markdown格式输出""" return "\n".join(f"- {data}" for data in processed_data) + + def get_statistics(self) -> Dict[str, Any]: + return { + "rule_count": self.rule_engine.rule_count(), + "custom_processor_count": len(self.custom_processors), + "has_rules_config": bool(self.rules), + } diff --git a/src/conventionalrp/core/rules.py b/src/conventionalrp/core/rules.py new file mode 100644 index 0000000..f198d4e --- /dev/null +++ b/src/conventionalrp/core/rules.py @@ -0,0 +1,238 @@ +from typing import Dict, Any, Callable, List, Optional +from enum import Enum +import re + + +class RuleCondition(Enum): + """规则条件类型""" + EQUALS = "equals" + CONTAINS = "contains" + MATCHES = "matches" + STARTS_WITH = "starts_with" + ENDS_WITH = "ends_with" + IN_LIST = "in_list" + GREATER_THAN = "greater_than" + LESS_THAN = "less_than" + + +class Rule: + def __init__( + self, + name: str, + condition: Dict[str, Any], + action: Dict[str, Any], + priority: int = 50 + ): + self.name = name + self.condition = condition + self.action = action + self.priority = priority + self._compiled_patterns = {} + + self._precompile_patterns() + + def _precompile_patterns(self): + """预编译正则表达式以提高性能""" + if isinstance(self.condition, dict): + for key, value in self.condition.items(): + if isinstance(value, dict) and value.get("type") == "matches": + pattern = value.get("pattern") + if pattern: + self._compiled_patterns[key] = re.compile(pattern) + + def matches(self, data: Dict[str, Any]) -> bool: + """ + 检查数据是否匹配规则条件 + """ + if not isinstance(self.condition, dict): + return False + + for field, condition_spec in self.condition.items(): + if not self._check_field_condition(data, field, condition_spec): + return False + + return True + + def _check_field_condition( + self, + data: Dict[str, Any], + field: str, + condition: Any + ) -> bool: + """检查单个字段的条件""" + value = data.get(field) + + if not isinstance(condition, dict): + return value == condition + + condition_type = condition.get("type") + expected_value = condition.get("value") + + if condition_type == "equals": + return value == expected_value + elif condition_type == "contains": + return expected_value in str(value) if value else False + elif condition_type == "matches": + if field in self._compiled_patterns: + pattern = self._compiled_patterns[field] + return bool(pattern.search(str(value))) if value else False + return False + elif condition_type == "starts_with": + return str(value).startswith(expected_value) if value else False + elif condition_type == "ends_with": + return str(value).endswith(expected_value) if value else False + elif condition_type == "in_list": + return value in expected_value if isinstance(expected_value, list) else False + elif condition_type == "greater_than": + try: + return float(value) > float(expected_value) + except (ValueError, TypeError): + return False + elif condition_type == "less_than": + try: + return float(value) < float(expected_value) + except (ValueError, TypeError): + return False + + return False + + def apply(self, data: Dict[str, Any]) -> Dict[str, Any]: + """ + 对匹配的数据应用规则动作 + """ + result = data.copy() + + if not isinstance(self.action, dict): + return result + + action_type = self.action.get("type") + + if action_type == "set_field": + field = self.action.get("field") + value = self.action.get("value") + if field: + result[field] = value + + elif action_type == "add_field": + field = self.action.get("field") + value = self.action.get("value") + if field and field not in result: + result[field] = value + + elif action_type == "remove_field": + field = self.action.get("field") + if field and field in result: + del result[field] + + elif action_type == "transform": + field = self.action.get("field") + func_name = self.action.get("function") + if field and field in result and func_name: + result[field] = self._apply_transform(result[field], func_name) + + elif action_type == "add_tag": + tag = self.action.get("tag") + if tag: + if "tags" not in result: + result["tags"] = [] + if tag not in result["tags"]: + result["tags"].append(tag) + + elif action_type == "copy_field": + source = self.action.get("source") + target = self.action.get("target") + if source and target and source in result: + result[target] = result[source] + + return result + + def _apply_transform(self, value: Any, func_name: str) -> Any: + transforms = { + "upper": lambda x: str(x).upper(), + "lower": lambda x: str(x).lower(), + "strip": lambda x: str(x).strip(), + "int": lambda x: int(x), + "float": lambda x: float(x), + "len": lambda x: len(x) if hasattr(x, '__len__') else 0, + } + + func = transforms.get(func_name) + if func: + try: + return func(value) + except Exception: + return value + return value + + def __repr__(self) -> str: + return f"Rule(name={self.name}, priority={self.priority})" + + +class RuleEngine: + """ + 规则引擎 + """ + + def __init__(self): + self.rules: List[Rule] = [] + self._sorted = False + + def add_rule(self, rule: Rule): + self.rules.append(rule) + self._sorted = False + + def add_rule_dict( + self, + name: str, + condition: Dict[str, Any], + action: Dict[str, Any], + priority: int = 50 + ): + """ + 从字典添加规则 + """ + rule = Rule(name, condition, action, priority) + self.add_rule(rule) + + def _ensure_sorted(self): + """确保规则按优先级排序""" + if not self._sorted: + self.rules.sort(key=lambda r: r.priority, reverse=True) + self._sorted = True + + def process( + self, + data: Dict[str, Any], + apply_all: bool = False + ) -> Dict[str, Any]: + self._ensure_sorted() + result = data.copy() + + for rule in self.rules: + if rule.matches(result): + result = rule.apply(result) + if not apply_all: + break + + return result + + def process_batch( + self, + data_list: List[Dict[str, Any]], + apply_all: bool = False + ) -> List[Dict[str, Any]]: + return [self.process(data, apply_all) for data in data_list] + + def find_matching_rules(self, data: Dict[str, Any]) -> List[Rule]: + self._ensure_sorted() + return [rule for rule in self.rules if rule.matches(data)] + + def clear_rules(self): + self.rules.clear() + self._sorted = False + + def rule_count(self) -> int: + return len(self.rules) + + def __repr__(self) -> str: + return f"RuleEngine(rules={len(self.rules)})" diff --git a/src/conventionalrp/extractors/__init__.py b/src/conventionalrp/extractors/__init__.py index e693e03..e69de29 100644 --- a/src/conventionalrp/extractors/__init__.py +++ b/src/conventionalrp/extractors/__init__.py @@ -1,3 +0,0 @@ -""" -This file initializes the extractors module. -""" diff --git a/src/conventionalrp/extractors/rule_extractor.py b/src/conventionalrp/extractors/rule_extractor.py index bfc60c8..12b9e4b 100644 --- a/src/conventionalrp/extractors/rule_extractor.py +++ b/src/conventionalrp/extractors/rule_extractor.py @@ -15,12 +15,6 @@ class RuleExtractor(BaseExtractor): """规则提取器,用于从配置文件加载解析规则""" def __init__(self, config_file: Optional[str] = None): - """ - 初始化规则提取器 - - Args: - config_file: 规则配置文件路径(可选) - """ self.config_file = config_file self.rules: Dict[str, Any] = {} if config_file: @@ -29,16 +23,6 @@ class RuleExtractor(BaseExtractor): def load_rules_from_file(self, config_file: str) -> Dict[str, Any]: """ 从文件加载规则 - - Args: - config_file: 规则配置文件路径 - - Returns: - 解析后的规则字典 - - Raises: - FileNotFoundError: 文件不存在 - ValueError: 文件内容为空或格式错误 """ if not Path(config_file).exists(): raise FileNotFoundError(f"Rule file not found: {config_file}") @@ -56,12 +40,6 @@ class RuleExtractor(BaseExtractor): def load_rules(self, config_file: str) -> Dict[str, Any]: """ 加载规则(兼容旧接口) - - Args: - config_file: 规则配置文件路径 - - Returns: - 解析后的规则字典 """ self.rules = self.load_rules_from_file(config_file) return self.rules @@ -69,8 +47,5 @@ class RuleExtractor(BaseExtractor): def extract(self) -> Dict[str, Any]: """ 提取规则 - - Returns: - 规则字典 """ return self.rules diff --git a/src/conventionalrp/html_renderer.py b/src/conventionalrp/html_renderer.py deleted file mode 100644 index e69de29..0000000 --- a/src/conventionalrp/html_renderer.py +++ /dev/null diff --git a/src/conventionalrp/json_renderer.py b/src/conventionalrp/json_renderer.py deleted file mode 100644 index e69de29..0000000 --- a/src/conventionalrp/json_renderer.py +++ /dev/null diff --git a/src/conventionalrp/markdown_renderer.py b/src/conventionalrp/markdown_renderer.py deleted file mode 100644 index e69de29..0000000 --- a/src/conventionalrp/markdown_renderer.py +++ /dev/null diff --git a/src/conventionalrp/plugins/__init__.py b/src/conventionalrp/plugins/__init__.py index c7bbee6..e759fd3 100644 --- a/src/conventionalrp/plugins/__init__.py +++ b/src/conventionalrp/plugins/__init__.py @@ -1,3 +1,17 @@ -""" -This file initializes the plugins module. -""" +from .base import ( + Plugin, + ParserPlugin, + ProcessorPlugin, + RendererPlugin, + AnalyzerPlugin, +) +from .plugin_manager import PluginManager + +__all__ = [ + "Plugin", + "ParserPlugin", + "ProcessorPlugin", + "RendererPlugin", + "AnalyzerPlugin", + "PluginManager", +] diff --git a/src/conventionalrp/plugins/base.py b/src/conventionalrp/plugins/base.py new file mode 100644 index 0000000..f9e92a7 --- /dev/null +++ b/src/conventionalrp/plugins/base.py @@ -0,0 +1,103 @@ +from typing import Any, Dict, List, Optional +from abc import ABC, abstractmethod +import logging + +logger = logging.getLogger(__name__) + + +class Plugin(ABC): + """ + 插件基类 + + 所有插件必须继承此类并实现必要的方法 + """ + + def __init__(self, name: str, version: str = "1.0.0"): + self.name = name + self.version = version + self.enabled = True + self.logger = logging.getLogger(f"conventionalrp.plugins.{name}") + + @abstractmethod + def initialize(self, config: Optional[Dict[str, Any]] = None): + pass + + @abstractmethod + def process(self, data: Any) -> Any: + pass + + def on_enable(self): + """插件启用时调用""" + self.logger.info(f"Plugin {self.name} enabled") + + def on_disable(self): + """插件禁用时调用""" + self.logger.info(f"Plugin {self.name} disabled") + + def get_metadata(self) -> Dict[str, Any]: + return { + "name": self.name, + "version": self.version, + "enabled": self.enabled, + "type": self.__class__.__name__, + } + + def __repr__(self) -> str: + return f"Plugin(name={self.name}, version={self.version}, enabled={self.enabled})" + + +class ParserPlugin(Plugin): + def __init__(self, name: str, version: str = "1.0.0"): + super().__init__(name, version) + self.priority = 50 + + @abstractmethod + def can_parse(self, text: str) -> bool: + """ + 判断是否可以解析给定文本 + """ + pass + + @abstractmethod + def parse(self, text: str) -> List[Dict[str, Any]]: + """ + 解析文本 + """ + pass + + +class ProcessorPlugin(Plugin): + @abstractmethod + def process_token(self, token: Dict[str, Any]) -> Dict[str, Any]: + """ + 处理单个 token + """ + pass + + def process(self, data: Any) -> Any: + """ + 处理数据(实现基类方法) + """ + if isinstance(data, dict): + return self.process_token(data) + elif isinstance(data, list): + return [self.process_token(token) for token in data] + return data + + +class RendererPlugin(Plugin): + @abstractmethod + def render(self, data: Any) -> str: + pass + + def process(self, data: Any) -> Any: + return self.render(data) + + +class AnalyzerPlugin(Plugin): + @abstractmethod + def analyze(self, data: Any) -> Dict[str, Any]: + pass + + def process(self, data: Any) -> Any: + return self.analyze(data) diff --git a/src/conventionalrp/plugins/plugin_manager.py b/src/conventionalrp/plugins/plugin_manager.py index 0d49a9c..0230bda 100644 --- a/src/conventionalrp/plugins/plugin_manager.py +++ b/src/conventionalrp/plugins/plugin_manager.py @@ -1,19 +1,204 @@ +""" +插件管理器 +""" + import os +import sys import importlib +import importlib.util +from pathlib import Path +from typing import Dict, List, Optional, Type, Any +import logging + +from .base import Plugin + +logger = logging.getLogger(__name__) class PluginManager: - def __init__(self, plugin_dir: str): - self.plugin_dir = plugin_dir - self.plugins = [] + def __init__(self, plugin_dirs: Optional[List[str]] = None): + self.plugin_dirs = plugin_dirs or [] + self.plugins: Dict[str, Plugin] = {} + self.plugin_classes: Dict[str, Type[Plugin]] = {} + + logger.info("PluginManager initialized with %d directories", + len(self.plugin_dirs)) + + def add_plugin_dir(self, directory: str): - def load_plugins(self): - for plugin in os.listdir(self.plugin_dir): - if plugin.endswith(".py"): - plugin_name = plugin.split(".")[0] - module = importlib.import_module(f"{self.plugin_dir}.{plugin_name}") - self.plugins.append(module) + if directory not in self.plugin_dirs: + self.plugin_dirs.append(directory) + logger.info(f"Added plugin directory: {directory}") + + def discover_plugins(self) -> List[str]: + discovered = [] + + for plugin_dir in self.plugin_dirs: + path = Path(plugin_dir) + if not path.exists(): + logger.warning(f"Plugin directory does not exist: {plugin_dir}") + continue + + if str(path.parent) not in sys.path: + sys.path.insert(0, str(path.parent)) - def run_plugins(self): - for plugin in self.plugins: - plugin.run() + for py_file in path.glob("*.py"): + if py_file.name.startswith("_"): + continue + + module_name = py_file.stem + discovered.append(module_name) + logger.debug(f"Discovered plugin module: {module_name}") + + logger.info(f"Discovered {len(discovered)} plugin modules") + return discovered + + def load_plugin_from_file(self, file_path: str) -> Optional[Type[Plugin]]: + try: + module_name = Path(file_path).stem + spec = importlib.util.spec_from_file_location(module_name, file_path) + + if spec is None or spec.loader is None: + logger.error(f"Failed to load plugin spec: {file_path}") + return None + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for name in dir(module): + obj = getattr(module, name) + if (isinstance(obj, type) and + issubclass(obj, Plugin) and + obj is not Plugin): + logger.info(f"Loaded plugin class: {name} from {file_path}") + return obj + + logger.warning(f"No Plugin subclass found in: {file_path}") + return None + + except Exception as e: + logger.error(f"Error loading plugin from {file_path}: {e}") + return None + + def load_plugin(self, module_name: str) -> Optional[str]: + try: + module = importlib.import_module(module_name) + + # 查找 Plugin 子类 + for name in dir(module): + obj = getattr(module, name) + if (isinstance(obj, type) and + issubclass(obj, Plugin) and + obj is not Plugin): + + plugin_class = obj + self.plugin_classes[name] = plugin_class + logger.info(f"Loaded plugin class: {name}") + return name + + logger.warning(f"No Plugin subclass found in module: {module_name}") + return None + + except Exception as e: + logger.error(f"Error loading plugin module {module_name}: {e}") + return None + + def register_plugin( + self, + plugin: Plugin, + replace: bool = False + ) -> bool: + if plugin.name in self.plugins and not replace: + logger.warning(f"Plugin {plugin.name} already registered") + return False + + self.plugins[plugin.name] = plugin + plugin.on_enable() + logger.info(f"Registered plugin: {plugin.name}") + return True + + def unregister_plugin(self, plugin_name: str) -> bool: + if plugin_name not in self.plugins: + logger.warning(f"Plugin {plugin_name} not found") + return False + + plugin = self.plugins[plugin_name] + plugin.on_disable() + del self.plugins[plugin_name] + logger.info(f"Unregistered plugin: {plugin_name}") + return True + + def get_plugin(self, plugin_name: str) -> Optional[Plugin]: + return self.plugins.get(plugin_name) + + def list_plugins(self) -> List[Dict[str, Any]]: + return [plugin.get_metadata() for plugin in self.plugins.values()] + + def enable_plugin(self, plugin_name: str) -> bool: + plugin = self.plugins.get(plugin_name) + if plugin is None: + logger.warning(f"Plugin {plugin_name} not found") + return False + + if not plugin.enabled: + plugin.enabled = True + plugin.on_enable() + logger.info(f"Enabled plugin: {plugin_name}") + + return True + + def disable_plugin(self, plugin_name: str) -> bool: + plugin = self.plugins.get(plugin_name) + if plugin is None: + logger.warning(f"Plugin {plugin_name} not found") + return False + + if plugin.enabled: + plugin.enabled = False + plugin.on_disable() + logger.info(f"Disabled plugin: {plugin_name}") + + return True + + def execute_plugins( + self, + data: Any, + plugin_type: Optional[Type[Plugin]] = None + ) -> Any: + result = data + + for plugin in self.plugins.values(): + if not plugin.enabled: + continue + + if plugin_type is not None and not isinstance(plugin, plugin_type): + continue + + try: + result = plugin.process(result) + logger.debug(f"Executed plugin: {plugin.name}") + except Exception as e: + logger.error(f"Error executing plugin {plugin.name}: {e}") + + return result + + def clear_plugins(self): + for plugin_name in list(self.plugins.keys()): + self.unregister_plugin(plugin_name) + + self.plugin_classes.clear() + logger.info("Cleared all plugins") + + def get_statistics(self) -> Dict[str, Any]: + enabled_count = sum(1 for p in self.plugins.values() if p.enabled) + + return { + "total_plugins": len(self.plugins), + "enabled_plugins": enabled_count, + "disabled_plugins": len(self.plugins) - enabled_count, + "plugin_classes": len(self.plugin_classes), + "plugin_directories": len(self.plugin_dirs), + } + + def __repr__(self) -> str: + return f"PluginManager(plugins={len(self.plugins)}, enabled={sum(1 for p in self.plugins.values() if p.enabled)})" diff --git a/src/conventionalrp/renderers/__init__.py b/src/conventionalrp/renderers/__init__.py index 6d5ac7a..e69de29 100644 --- a/src/conventionalrp/renderers/__init__.py +++ b/src/conventionalrp/renderers/__init__.py @@ -1,3 +0,0 @@ -""" -This file initializes the renderers module. -""" diff --git a/src/conventionalrp/renderers/html_renderer.py b/src/conventionalrp/renderers/html_renderer.py index 75efcd3..d435fc9 100644 --- a/src/conventionalrp/renderers/html_renderer.py +++ b/src/conventionalrp/renderers/html_renderer.py @@ -1,22 +1,423 @@ +from typing import Any, Dict, List, Optional from .base import BaseRenderer class HTMLRenderer(BaseRenderer): - def __init__(self): + THEMES = { + "light": { + "bg_color": "#ffffff", + "text_color": "#333333", + "header_bg": "#f5f5f5", + "border_color": "#e0e0e0", + "dialogue_color": "#4CAF50", + "dice_color": "#2196F3", + "narration_color": "#FF9800", + "system_color": "#9E9E9E", + "success_color": "#43a047", + "failure_color": "#e53935", + "code_bg": "#f5f5f5", + }, + "dark": { + "bg_color": "#1e1e1e", + "text_color": "#d4d4d4", + "header_bg": "#2d2d30", + "border_color": "#3e3e42", + "dialogue_color": "#6adb8d", + "dice_color": "#5fb3f5", + "narration_color": "#ffb74d", + "system_color": "#bdbdbd", + "success_color": "#66bb6a", + "failure_color": "#ef5350", + "code_bg": "#2d2d30", + }, + "fantasy": { + "bg_color": "#f9f6f1", + "text_color": "#3e2723", + "header_bg": "#d7ccc8", + "border_color": "#bcaaa4", + "dialogue_color": "#8d6e63", + "dice_color": "#5d4037", + "narration_color": "#795548", + "system_color": "#a1887f", + "success_color": "#7cb342", + "failure_color": "#c62828", + "code_bg": "#efebe9", + }, + } + + def __init__(self, theme: str = "light", custom_css: Optional[str] = None): super().__init__() - self.title = "TRPG Log Output" - - def render(self, data): - html_content = f"<html><head><title>{self.title}</title></head><body>" - html_content += "<h1>TRPG Log Output</h1>" - html_content += "<ul>" - - for entry in data: - html_content += f"<li>{entry}</li>" - - html_content += "</ul></body></html>" - return html_content - + self.title = "Rendered Log" + self.theme = theme if theme in self.THEMES else "light" + self.custom_css = custom_css + + def _get_css(self) -> str: + colors = self.THEMES[self.theme] + + css = f""" + * {{ + margin: 0; + padding: 0; + box-sizing: border-box; + }} + + body {{ + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: {colors['bg_color']}; + color: {colors['text_color']}; + line-height: 1.6; + padding: 20px; + max-width: 1200px; + margin: 0 auto; + }} + + header {{ + background: linear-gradient(135deg, {colors['header_bg']} 0%, {colors['border_color']} 100%); + padding: 30px; + border-radius: 10px; + margin-bottom: 30px; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + }} + + h1 {{ + font-size: 2.5em; + font-weight: 700; + margin-bottom: 10px; + color: {colors['text_color']}; + }} + + .subtitle {{ + font-size: 1.1em; + opacity: 0.8; + }} + + .stats {{ + display: flex; + gap: 20px; + margin-top: 20px; + flex-wrap: wrap; + }} + + .stat-item {{ + background: {colors['bg_color']}; + padding: 10px 20px; + border-radius: 5px; + font-size: 0.9em; + border: 1px solid {colors['border_color']}; + }} + + .stat-label {{ + opacity: 0.7; + margin-right: 5px; + }} + + .stat-value {{ + font-weight: bold; + }} + + .content-wrapper {{ + background: {colors['bg_color']}; + border-radius: 10px; + padding: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + }} + + .token {{ + margin: 15px 0; + padding: 15px; + border-left: 4px solid {colors['border_color']}; + border-radius: 5px; + background: {colors['header_bg']}; + transition: transform 0.2s, box-shadow 0.2s; + }} + + .token:hover {{ + transform: translateX(5px); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + }} + + .token.dialogue {{ + border-left-color: {colors['dialogue_color']}; + }} + + .token.dice {{ + border-left-color: {colors['dice_color']}; + }} + + .token.narration {{ + border-left-color: {colors['narration_color']}; + }} + + .token.system {{ + border-left-color: {colors['system_color']}; + }} + + .token.success {{ + border-left-color: {colors['success_color']}; + background: {colors['success_color']}15; + }} + + .token.failure {{ + border-left-color: {colors['failure_color']}; + background: {colors['failure_color']}15; + }} + + .token-header {{ + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; + }} + + .type-badge {{ + display: inline-block; + padding: 3px 10px; + border-radius: 12px; + font-size: 0.75em; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + }} + + .type-badge.dialogue {{ + background: {colors['dialogue_color']}; + color: white; + }} + + .type-badge.dice {{ + background: {colors['dice_color']}; + color: white; + }} + + .type-badge.narration {{ + background: {colors['narration_color']}; + color: white; + }} + + .type-badge.system {{ + background: {colors['system_color']}; + color: white; + }} + + .speaker {{ + font-weight: 700; + font-size: 1.1em; + color: {colors['text_color']}; + }} + + .content {{ + margin-top: 8px; + line-height: 1.8; + font-size: 1em; + }} + + .metadata {{ + display: flex; + gap: 15px; + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid {colors['border_color']}; + font-size: 0.85em; + opacity: 0.7; + flex-wrap: wrap; + }} + + .metadata-item {{ + display: flex; + align-items: center; + gap: 5px; + }} + + .tags {{ + display: flex; + gap: 5px; + flex-wrap: wrap; + }} + + .tag {{ + background: {colors['code_bg']}; + padding: 2px 8px; + border-radius: 3px; + font-size: 0.85em; + border: 1px solid {colors['border_color']}; + }} + + code {{ + background: {colors['code_bg']}; + padding: 2px 6px; + border-radius: 3px; + font-family: 'Courier New', monospace; + font-size: 0.9em; + }} + + .dice-result {{ + display: inline-block; + background: {colors['dice_color']}; + color: white; + padding: 3px 10px; + border-radius: 5px; + font-weight: bold; + margin-left: 5px; + }} + + footer {{ + margin-top: 40px; + padding: 20px; + text-align: center; + opacity: 0.6; + font-size: 0.9em; + }} + + @media (max-width: 768px) {{ + body {{ + padding: 10px; + }} + + h1 {{ + font-size: 1.8em; + }} + + .token {{ + padding: 10px; + }} + }} + """ + + if self.custom_css: + css += "\n" + self.custom_css + + return css + + def render(self, data: List[Dict[str, Any]]) -> str: + if data and not isinstance(data[0], dict): + data = [{"type": "text", "content": str(item)} for item in data] + + stats = self._calculate_stats(data) + + html = f"""<!DOCTYPE html> +<html lang="zh-CN"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>{self.title}</title> + <style>{self._get_css()}</style> +</head> +<body> + <header> + <h1>🎲 {self.title}</h1> + <div class="subtitle">完整的游戏日志与数据分析</div> + <div class="stats"> + <div class="stat-item"> + <span class="stat-label">总条目:</span> + <span class="stat-value">{stats['total']}</span> + </div> + <div class="stat-item"> + <span class="stat-label">对话:</span> + <span class="stat-value">{stats['dialogue']}</span> + </div> + <div class="stat-item"> + <span class="stat-label">骰子:</span> + <span class="stat-value">{stats['dice']}</span> + </div> + <div class="stat-item"> + <span class="stat-label">旁白:</span> + <span class="stat-value">{stats['narration']}</span> + </div> + <div class="stat-item"> + <span class="stat-label">系统:</span> + <span class="stat-value">{stats['system']}</span> + </div> + </div> + </header> + + <div class="content-wrapper"> +""" + + for item in data: + html += self._render_token(item) + + html += """ </div> + + <footer> + <p>Generated by ConventionalRP HTML Renderer</p> + <p>Theme: """ + self.theme.capitalize() + """</p> + </footer> +</body> +</html>""" + + return html + + def _render_token(self, token: Dict[str, Any]) -> str: + token_type = token.get("type", "unknown") + speaker = token.get("speaker", "") + content = str(token.get("content", "")) + + content = (content + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """)) + + if token_type == "dice" and "result" in token: + content += f' <span class="dice-result">{token["result"]}</span>' + + html = f' <div class="token {token_type}">\n' + html += ' <div class="token-header">\n' + html += f' <span class="type-badge {token_type}">{token_type}</span>\n' + + if speaker: + html += f' <span class="speaker">{speaker}</span>\n' + + html += ' </div>\n' + html += f' <div class="content">{content}</div>\n' + + metadata_items = [] + + if "timestamp" in token: + metadata_items.append(f'⏰ {token["timestamp"]}') + + if "tags" in token and token["tags"]: + tags_html = '<div class="tags">' + for tag in token["tags"]: + tags_html += f'<span class="tag">{tag}</span>' + tags_html += '</div>' + metadata_items.append(tags_html) + + if "combat_data" in token: + combat = token["combat_data"] + if combat.get("type") == "damage": + metadata_items.append(f'⚔️ 伤害: {combat["amount"]}') + elif combat.get("type") == "healing": + metadata_items.append(f'💚 治疗: {combat["amount"]}') + + if metadata_items: + html += ' <div class="metadata">\n' + for item in metadata_items: + html += f' <div class="metadata-item">{item}</div>\n' + html += ' </div>\n' + + html += ' </div>\n' + + return html + + def _calculate_stats(self, data: List[Dict[str, Any]]) -> Dict[str, int]: + stats = { + "total": len(data), + "dialogue": 0, + "dice": 0, + "narration": 0, + "system": 0, + } + + for item in data: + token_type = item.get("type", "unknown") + if token_type in stats: + stats[token_type] += 1 + + return stats + def set_style(self, style): - # Implement style setting if needed - pass + """设置样式(向后兼容)""" + if style in self.THEMES: + self.theme = style + diff --git a/src/conventionalrp/renderers/json_renderer.py b/src/conventionalrp/renderers/json_renderer.py index 8dcdd6d..42d5fd4 100644 --- a/src/conventionalrp/renderers/json_renderer.py +++ b/src/conventionalrp/renderers/json_renderer.py @@ -1,11 +1,78 @@ +import json +from typing import Any, Dict, List from .base import BaseRenderer class JSONRenderer(BaseRenderer): - def render(self, data): - import json - - return json.dumps(data, ensure_ascii=False, indent=4) + def __init__(self, pretty: bool = True, indent: int = 2, sort_keys: bool = False): + super().__init__() + self.pretty = pretty + self.indent = indent if pretty else None + self.sort_keys = sort_keys + self.style = {} + + def render(self, data: Any) -> str: + if self.pretty and isinstance(data, list): + output = { + "metadata": { + "total_entries": len(data), + "renderer": "ConventionalRP JSONRenderer", + "version": "1.0.0", + }, + "statistics": self._calculate_stats(data), + "data": data, + } + else: + output = data + + return json.dumps( + output, + ensure_ascii=False, + indent=self.indent, + sort_keys=self.sort_keys, + ) + + def _calculate_stats(self, data: List[Dict[str, Any]]) -> Dict[str, Any]: + stats = { + "dialogue": 0, + "dice": 0, + "narration": 0, + "system": 0, + "success": 0, + "failure": 0, + "other": 0, + } + + speakers = set() + + for item in data: + if not isinstance(item, dict): + continue + + token_type = item.get("type", "unknown") + if token_type in stats: + stats[token_type] += 1 + else: + stats["other"] += 1 + + if "speaker" in item and item["speaker"]: + speakers.add(item["speaker"]) + + stats["unique_speakers"] = len(speakers) + stats["speakers"] = sorted(list(speakers)) + + return stats def set_style(self, style): - self.style = style # Placeholder for potential styling options in the future + self.style = style + + # 从 style 中提取设置 + if isinstance(style, dict): + if "pretty" in style: + self.pretty = style["pretty"] + self.indent = 2 if self.pretty else None + if "indent" in style: + self.indent = style["indent"] + if "sort_keys" in style: + self.sort_keys = style["sort_keys"] + diff --git a/src/conventionalrp/renderers/markdown_renderer.py b/src/conventionalrp/renderers/markdown_renderer.py index 9df59a2..7080913 100644 --- a/src/conventionalrp/renderers/markdown_renderer.py +++ b/src/conventionalrp/renderers/markdown_renderer.py @@ -1,18 +1,19 @@ +""" +Markdown 渲染器 +""" + +from typing import Any, Dict, List, Union from .base import BaseRenderer -from typing import List, Dict, Any, Union class MarkdownRenderer(BaseRenderer): + def __init__(self, enable_syntax_hints: bool = True, enable_emoji: bool = True): + super().__init__() + self.enable_syntax_hints = enable_syntax_hints + self.enable_emoji = enable_emoji + self.style = {} + def render(self, data: Union[List[Dict[str, Any]], Dict[str, Any]]) -> str: - """ - Renders the given data in Markdown format. - - Args: - data: The data to render (can be list or dict). - - Returns: - str: The rendered Markdown string. - """ if isinstance(data, list): return self._render_list(data) elif isinstance(data, dict): @@ -21,40 +22,141 @@ class MarkdownRenderer(BaseRenderer): return str(data) def _render_list(self, data: List[Dict[str, Any]]) -> str: - """渲染列表数据为 Markdown""" - markdown_output = "# TRPG Log\n\n" - - for i, entry in enumerate(data, 1): - if entry.get("type") == "metadata": - markdown_output += f"## Entry {i}\n\n" - markdown_output += f"**Timestamp**: {entry.get('timestamp', 'N/A')} \n" - markdown_output += f"**Speaker**: {entry.get('speaker', 'N/A')} \n\n" - - content_items = entry.get("content", []) - if content_items: - markdown_output += "**Content**:\n\n" - for content in content_items: - content_type = content.get("type", "unknown") - content_text = content.get("content", "") - markdown_output += f"- [{content_type}] {content_text}\n" - markdown_output += "\n" - else: - markdown_output += f"- {entry}\n" + if data and not isinstance(data[0], dict): + data = [{"type": "text", "content": str(item)} for item in data] - return markdown_output + stats = self._calculate_stats(data) + + emoji_prefix = "🎲 " if self.enable_emoji else "" + md = f"# {emoji_prefix}\n\n" + + md += "## Statistics\n\n" + md += "| Type | Count |\n" + md += "|------|------|\n" + md += f"| Total | {stats['total']} |\n" + md += f"| Dialogue | {stats['dialogue']} |\n" + md += f"| Dice | {stats['dice']} |\n" + md += f"| Narration | {stats['narration']} |\n" + md += f"| System | {stats['system']} |\n" + md += f"| Metadata | {stats['metadata']} |\n\n" + + md += "---\n\n" + md += "## Detailed Log\n\n" + + # Render each token + for i, item in enumerate(data, 1): + md += self._render_token(item, i) + md += "\n" + + md += "---\n\n" + md += "_Generated by ConventionalRP Markdown Renderer_\n" + + return md + + def _render_token(self, token: Dict[str, Any], index: int) -> str: + token_type = token.get("type", "unknown") + + # 处理元数据类型(向后兼容) + if token_type == "metadata": + return self._render_metadata_token(token, index) + + speaker = token.get("speaker", "") + content = str(token.get("content", "")) + + type_emojis = { + "dialogue": "💬", + "dice": "🎲", + "narration": "📖", + "system": "⚙️", + "success": "✅", + "failure": "❌", + "text": "📄", + "unknown": "❓", + } + + emoji = type_emojis.get(token_type, "•") if self.enable_emoji else "-" + + md = f"### {emoji} [{index}] {token_type.upper()}\n\n" + + if speaker: + md += f"**说话者:** {speaker}\n\n" + + if token_type == "dice" and self.enable_syntax_hints: + md += f"```dice\n{content}\n```\n\n" + + if "result" in token: + md += f"**结果:** `{token['result']}`\n\n" + else: + md += f"{content}\n\n" + + metadata_lines = [] + + if "timestamp" in token: + metadata_lines.append(f"Timestamp: `{token['timestamp']}`") + + if "tags" in token and token["tags"]: + tags_str = " ".join([f"`{tag}`" for tag in token["tags"]]) + metadata_lines.append(f"Tags: {tags_str}") + + if "combat_data" in token: + combat = token["combat_data"] + if combat.get("type") == "damage": + metadata_lines.append(f"Damage: **{combat['amount']}** (Total: {combat.get('total_damage', '?')})") + elif combat.get("type") == "healing": + metadata_lines.append(f"Healing: **{combat['amount']}** (Total: {combat.get('total_healing', '?')})") + + if metadata_lines: + md += "> " + "\n> ".join(metadata_lines) + "\n\n" + + return md + + def _render_metadata_token(self, token: Dict[str, Any], index: int) -> str: + """渲染元数据类型的 token(向后兼容)""" + md = f"### Entry {index}\n\n" + md += f"**Timestamp**: {token.get('timestamp', 'N/A')} \n" + md += f"**Speaker**: {token.get('speaker', 'N/A')} \n\n" + + content_items = token.get("content", []) + if content_items: + md += "**Content**:\n\n" + for content in content_items: + content_type = content.get("type", "unknown") + content_text = content.get("content", "") + md += f"- [{content_type}] {content_text}\n" + md += "\n" + + return md def _render_dict(self, data: Dict[str, Any]) -> str: - """渲染字典数据为 Markdown""" markdown_output = "" for key, value in data.items(): markdown_output += f"## {key}\n\n{value}\n\n" return markdown_output + + def _calculate_stats(self, data: List[Dict[str, Any]]) -> Dict[str, int]: + stats = { + "total": len(data), + "dialogue": 0, + "dice": 0, + "narration": 0, + "system": 0, + "metadata": 0, + } + + for item in data: + token_type = item.get("type", "unknown") + if token_type in stats: + stats[token_type] += 1 + + return stats def set_style(self, style): - """ - Sets the style for the Markdown renderer. + self.style = style + + # 从 style 中提取设置 + if isinstance(style, dict): + if "emoji" in style: + self.enable_emoji = style["emoji"] + if "syntax_hints" in style: + self.enable_syntax_hints = style["syntax_hints"] - Args: - style (dict): A dictionary of style options. - """ - self.style = style # Currently, Markdown does not support styling, but this can be extended. diff --git a/src/conventionalrp/tokenizer.py b/src/conventionalrp/tokenizer.py deleted file mode 100644 index e69de29..0000000 --- a/src/conventionalrp/tokenizer.py +++ /dev/null diff --git a/src/conventionalrp/utils/__init__.py b/src/conventionalrp/utils/__init__.py index 53b7600..dadb3cd 100644 --- a/src/conventionalrp/utils/__init__.py +++ b/src/conventionalrp/utils/__init__.py @@ -1 +1,32 @@ -"""This file initializes the utils module.""" +from .exceptions import ( + ConventionalRPError, + ParserError, + RuleError, + ProcessorError, + ValidationError, + ConfigurationError, + safe_execute, + format_error, + validate_not_none, + validate_type, + validate_not_empty, +) +from .logging_config import setup_logging, get_logger, LogContext + +__all__ = [ + "ConventionalRPError", + "ParserError", + "RuleError", + "ProcessorError", + "ValidationError", + "ConfigurationError", + "safe_execute", + "format_error", + "validate_not_none", + "validate_type", + "validate_not_empty", + "setup_logging", + "get_logger", + "LogContext", +] + diff --git a/src/conventionalrp/utils/exceptions.py b/src/conventionalrp/utils/exceptions.py new file mode 100644 index 0000000..28fa179 --- /dev/null +++ b/src/conventionalrp/utils/exceptions.py @@ -0,0 +1,104 @@ +from typing import Optional, Any, Dict +import traceback +import logging + +logger = logging.getLogger(__name__) + + +class ConventionalRPError(Exception): + """基础异常类""" + + def __init__( + self, + message: str, + details: Optional[Dict[str, Any]] = None, + cause: Optional[Exception] = None + ): + super().__init__(message) + self.message = message + self.details = details or {} + self.cause = cause + + def __str__(self) -> str: + result = self.message + if self.details: + details_str = ", ".join(f"{k}={v}" for k, v in self.details.items()) + result += f" ({details_str})" + if self.cause: + result += f"\nCaused by: {str(self.cause)}" + return result + + +class ParserError(ConventionalRPError): + """解析错误""" + pass + + +class RuleError(ConventionalRPError): + """规则相关错误""" + pass + + +class ProcessorError(ConventionalRPError): + """处理器错误""" + pass + + +class ValidationError(ConventionalRPError): + """验证错误""" + pass + + +class ConfigurationError(ConventionalRPError): + """配置错误""" + pass + + +def safe_execute(func, *args, default=None, error_msg="Operation failed", **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + logger.error(f"{error_msg}: {e}") + return default + + +def format_error(error: Exception, include_traceback: bool = False) -> str: + error_type = type(error).__name__ + error_msg = str(error) + + result = f"[{error_type}] {error_msg}" + + if include_traceback: + tb = traceback.format_exc() + result += f"\n\nTraceback:\n{tb}" + + return result + + +def validate_not_none(value: Any, name: str): + if value is None: + raise ValidationError( + f"{name} cannot be None", + details={"parameter": name} + ) + + +def validate_type(value: Any, expected_type: type, name: str): + if not isinstance(value, expected_type): + raise ValidationError( + f"{name} must be of type {expected_type.__name__}, " + f"got {type(value).__name__}", + details={ + "parameter": name, + "expected_type": expected_type.__name__, + "actual_type": type(value).__name__ + } + ) + + +def validate_not_empty(value: Any, name: str): + if not value: + raise ValidationError( + f"{name} cannot be empty", + details={"parameter": name, "value": value} + ) diff --git a/src/conventionalrp/utils/logging_config.py b/src/conventionalrp/utils/logging_config.py new file mode 100644 index 0000000..956d6d4 --- /dev/null +++ b/src/conventionalrp/utils/logging_config.py @@ -0,0 +1,77 @@ +import logging +import sys +from pathlib import Path +from typing import Optional +from datetime import datetime + + +def setup_logging( + level: str = "INFO", + log_file: Optional[str] = None, + format_string: Optional[str] = None, + include_timestamp: bool = True, + include_module: bool = True +) -> logging.Logger: + log_level = getattr(logging, level.upper(), logging.INFO) + + if format_string is None: + parts = [] + if include_timestamp: + parts.append("%(asctime)s") + parts.append("[%(levelname)s]") + if include_module: + parts.append("%(name)s") + parts.append("%(message)s") + format_string = " - ".join(parts) + + logging.basicConfig( + level=log_level, + format=format_string, + datefmt="%Y-%m-%d %H:%M:%S", + handlers=[] + ) + + logger = logging.getLogger("conventionalrp") + logger.setLevel(log_level) + + logger.handlers.clear() + + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(log_level) + console_formatter = logging.Formatter(format_string, datefmt="%Y-%m-%d %H:%M:%S") + console_handler.setFormatter(console_formatter) + logger.addHandler(console_handler) + + if log_file: + log_path = Path(log_file) + log_path.parent.mkdir(parents=True, exist_ok=True) + + file_handler = logging.FileHandler(log_file, encoding="utf-8") + file_handler.setLevel(log_level) + file_handler.setFormatter(console_formatter) + logger.addHandler(file_handler) + + logger.info(f"Logging to file: {log_file}") + + logger.info(f"Logging configured at {level} level") + return logger + + +def get_logger(name: str) -> logging.Logger: + return logging.getLogger(f"conventionalrp.{name}") + + +class LogContext: + def __init__(self, logger: logging.Logger, level: str): + self.logger = logger + self.level = getattr(logging, level.upper()) + self.original_level = None + + def __enter__(self): + self.original_level = self.logger.level + self.logger.setLevel(self.level) + return self.logger + + def __exit__(self, exc_type, exc_val, exc_tb): + self.logger.setLevel(self.original_level) + return False diff --git a/src/conventionalrp/utils/text_processing.py b/src/conventionalrp/utils/text_processing.py deleted file mode 100644 index e69de29..0000000 --- a/src/conventionalrp/utils/text_processing.py +++ /dev/null @@ -1,19 +1,253 @@ use pyo3::prelude::*; +use pyo3::types::{PyDict, PyList}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; -#[pyfunction] -fn sum_as_string(a: usize, b: usize) -> PyResult<String> { - Ok((a + b).to_string()) +#[derive(Debug, Clone, Serialize, Deserialize)] +#[pyclass] +pub enum TokenType { + Metadata, + DiceRoll, + Dialogue, + Action, + Ooc, + System, + Text, + Unknown, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[pyclass] +pub struct Token { + #[pyo3(get, set)] + pub token_type: String, + #[pyo3(get, set)] + pub content: String, + #[pyo3(get, set)] + pub metadata: HashMap<String, String>, +} + +#[pymethods] +impl Token { + #[new] + fn new(token_type: String, content: String) -> Self { + Token { + token_type, + content, + metadata: HashMap::new(), + } + } + + fn add_metadata(&mut self, key: String, value: String) { + self.metadata.insert(key, value); + } + + fn get_metadata(&self, key: &str) -> Option<String> { + self.metadata.get(key).cloned() + } + fn to_dict(&self, py: Python) -> PyResult<Py<PyAny>> { + let dict = PyDict::new(py); + dict.set_item("type", &self.token_type)?; + dict.set_item("content", &self.content)?; + + let metadata_dict = PyDict::new(py); + for (k, v) in &self.metadata { + metadata_dict.set_item(k, v)?; + } + dict.set_item("metadata", metadata_dict)?; + + Ok(dict.into()) + } + + fn __repr__(&self) -> String { + format!("Token(type={}, content={})", self.token_type, self.content) + } +} + +#[pyclass] +pub struct RegexRule { + pattern: Regex, + #[pyo3(get, set)] + pub rule_type: String, + #[pyo3(get, set)] + pub priority: i32, +} + +#[pymethods] +impl RegexRule { + #[new] + fn new(pattern: String, rule_type: String, priority: i32) -> PyResult<Self> { + let regex = Regex::new(&pattern) + .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>( + format!("Invalid regex pattern: {}", e) + ))?; + + Ok(RegexRule { + pattern: regex, + rule_type, + priority, + }) + } + + fn matches(&self, text: &str) -> bool { + self.pattern.is_match(text) + } + + + fn extract(&self, text: &str) -> Option<Vec<String>> { + self.pattern.captures(text).map(|caps| { + caps.iter() + .skip(1) // 跳过完整匹配 + .filter_map(|m| m.map(|m| m.as_str().to_string())) + .collect() + }) + } + + fn find_all(&self, text: &str, py: Python) -> PyResult<Py<PyAny>> { + let matches: Vec<(usize, usize, String)> = self.pattern + .find_iter(text) + .map(|m| (m.start(), m.end(), m.as_str().to_string())) + .collect(); + + let list = PyList::empty(py); + for (start, end, matched) in matches { + let dict = PyDict::new(py); + dict.set_item("start", start)?; + dict.set_item("end", end)?; + dict.set_item("text", matched)?; + list.append(dict)?; + } + + Ok(list.into()) + } +} + +#[pyclass] +pub struct TextParser { + rules: Vec<(Regex, String, i32)>, // (pattern, type, priority) +} + +#[pymethods] +impl TextParser { + #[new] + fn new() -> Self { + TextParser { rules: Vec::new() } + } + + fn add_rule(&mut self, pattern: String, rule_type: String, priority: i32) -> PyResult<()> { + let regex = Regex::new(&pattern) + .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>( + format!("Invalid regex: {}", e) + ))?; + + self.rules.push((regex, rule_type, priority)); + self.rules.sort_by(|a, b| b.2.cmp(&a.2)); + + Ok(()) + } + + fn parse_line(&self, text: &str) -> Vec<(String, String, usize, usize)> { + let mut results = Vec::new(); + let mut processed_ranges: Vec<(usize, usize)> = Vec::new(); + for (pattern, rule_type, _priority) in &self.rules { + for mat in pattern.find_iter(text) { + let start = mat.start(); + let end = mat.end(); + + let overlaps = processed_ranges.iter().any(|(s, e)| { + (start >= *s && start < *e) || (end > *s && end <= *e) || (start <= *s && end >= *e) + }); + + if !overlaps { + results.push(( + rule_type.clone(), + mat.as_str().to_string(), + start, + end, + )); + processed_ranges.push((start, end)); + } + } + } + + results.sort_by_key(|r| r.2); + results + } + fn parse_lines(&self, lines: Vec<String>, py: Python) -> PyResult<Py<PyAny>> { + let list = PyList::empty(py); + + for line in lines { + let parsed = self.parse_line(&line); + let line_result = PyList::empty(py); + + for (rule_type, content, start, end) in parsed { + let dict = PyDict::new(py); + dict.set_item("type", rule_type)?; + dict.set_item("content", content)?; + dict.set_item("start", start)?; + dict.set_item("end", end)?; + line_result.append(dict)?; + } + + list.append(line_result)?; + } + + Ok(list.into()) + } + + fn clear_rules(&mut self) { + self.rules.clear(); + } + + fn rule_count(&self) -> usize { + self.rules.len() + } } #[pyclass] -pub struct Base {} +pub struct FastMatcher { + patterns: Vec<String>, +} + +#[pymethods] +impl FastMatcher { + #[new] + fn new(patterns: Vec<String>) -> Self { + FastMatcher { patterns } + } + + + fn contains_any(&self, text: &str) -> bool { + self.patterns.iter().any(|p| text.contains(p)) + } + + + fn find_matches(&self, text: &str) -> Vec<String> { + self.patterns + .iter() + .filter(|p| text.contains(*p)) + .cloned() + .collect() + } + + fn count_matches(&self, text: &str, py: Python) -> PyResult<Py<PyAny>> { + let dict = PyDict::new(py); + for pattern in &self.patterns { + let count = text.matches(pattern.as_str()).count(); + dict.set_item(pattern, count)?; + } + Ok(dict.into()) + } +} -/// A Python module implemented in Rust. The name of this function must match -/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to -/// import the module. #[pymodule] fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; - m.add_class::<Base>()?; + // 添加类 + m.add_class::<Token>()?; + m.add_class::<RegexRule>()?; + m.add_class::<TextParser>()?; + m.add_class::<FastMatcher>()?; + Ok(()) }
\ No newline at end of file diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 97a8028..0000000 --- a/tests/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# ConventionalRP 测试套件 - -本目录包含 ConventionalRP SDK 的所有单元测试。 - -## 测试文件 - -- `test_parser.py` - Parser 解析器测试 -- `test_processor.py` - Processor 处理器测试 -- `test_rule_extractor.py` - RuleExtractor 规则提取器测试 -- `test_renderers.py` - 渲染器测试(HTML/JSON/Markdown) -- `test_pyo3.py` - PyO3 Rust 扩展测试 - -## 运行测试 - -### 运行所有测试 - -```bash -python tests/run_tests.py -``` - -### 运行单个测试文件 - -```bash -python -m unittest tests/test_parser.py -python -m unittest tests/test_processor.py -``` - -### 运行特定测试类 - -```bash -python -m unittest tests.test_parser.TestParser -``` - -### 运行特定测试方法 - -```bash -python -m unittest tests.test_parser.TestParser.test_load_rules_success -``` - -## 测试覆盖率 - -要查看测试覆盖率,请安装 `coverage` 并运行: - -```bash -pip install coverage -coverage run -m unittest discover -s tests -p "test_*.py" -coverage report -coverage html # 生成 HTML 报告 -``` - -## 测试数据 - -测试使用临时文件来模拟规则文件和日志文件,测试完成后会自动清理。 - -## 添加新测试 - -创建新的测试文件时,请遵循以下约定: - -1. 文件名以 `test_` 开头 -2. 测试类继承自 `unittest.TestCase` -3. 测试方法以 `test_` 开头 -4. 使用 `setUp()` 和 `tearDown()` 方法管理测试状态 -5. 添加清晰的文档字符串说明测试目的 diff --git a/tests/run_tests.py b/tests/run_tests.py index 4cfc2d4..b776062 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -1,34 +1,22 @@ -#!/usr/bin/env python3 -""" -测试套件运行器 -运行所有单元测试 -""" - import sys import unittest from pathlib import Path -# 添加 src 目录到路径 src_path = Path(__file__).parent.parent / "src" sys.path.insert(0, str(src_path)) def run_all_tests(): - """运行所有测试""" - # 创建测试加载器 loader = unittest.TestLoader() - # 从当前目录加载所有测试 suite = loader.discover( start_dir=Path(__file__).parent, pattern='test_*.py' ) - # 运行测试 runner = unittest.TextTestRunner(verbosity=2) result = runner.run(suite) - # 返回结果 return 0 if result.wasSuccessful() else 1 diff --git a/tests/test_parser.py b/tests/test_parser.py index 595d0b4..1d41a1e 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,8 +1,3 @@ -#!/usr/bin/env python3 -""" -Parser 模块单元测试 -""" - import unittest import tempfile from pathlib import Path @@ -10,13 +5,9 @@ from conventionalrp.core.parser import Parser class TestParser(unittest.TestCase): - """Parser 类的单元测试""" - def setUp(self): - """设置测试环境""" self.parser = Parser() - # 创建临时规则文件 self.temp_rules = tempfile.NamedTemporaryFile( mode='w', suffix='.json5', @@ -56,7 +47,6 @@ class TestParser(unittest.TestCase): }''') self.temp_rules.close() - # 创建临时日志文件 self.temp_log = tempfile.NamedTemporaryFile( mode='w', suffix='.txt', @@ -70,30 +60,25 @@ class TestParser(unittest.TestCase): self.temp_log.close() def tearDown(self): - """清理测试环境""" Path(self.temp_rules.name).unlink(missing_ok=True) Path(self.temp_log.name).unlink(missing_ok=True) def test_load_rules_success(self): - """测试成功加载规则文件""" self.parser.load_rules(self.temp_rules.name) self.assertIn("metadata", self.parser.rules) self.assertIn("content", self.parser.rules) def test_load_rules_file_not_found(self): - """测试加载不存在的规则文件""" with self.assertRaises(FileNotFoundError): self.parser.load_rules("nonexistent_file.json5") def test_parse_log_success(self): - """测试成功解析日志""" self.parser.load_rules(self.temp_rules.name) result = self.parser.parse_log(self.temp_log.name) self.assertIsInstance(result, list) self.assertGreater(len(result), 0) - # 检查第一条记录 first_entry = result[0] self.assertIn("timestamp", first_entry) self.assertIn("speaker", first_entry) @@ -101,13 +86,11 @@ class TestParser(unittest.TestCase): self.assertEqual(first_entry["speaker"], "艾莉娅") def test_parse_log_file_not_found(self): - """测试解析不存在的日志文件""" self.parser.load_rules(self.temp_rules.name) with self.assertRaises(FileNotFoundError): self.parser.parse_log("nonexistent_log.txt") def test_match_metadata(self): - """测试元数据匹配""" self.parser.load_rules(self.temp_rules.name) line = "[2025-10-24 14:30:01] <艾莉娅> 测试内容" result = self.parser._match_metadata(line) @@ -118,7 +101,6 @@ class TestParser(unittest.TestCase): self.assertEqual(result["speaker"], "艾莉娅") def test_parse_line_content_dialogue(self): - """测试解析对话内容""" self.parser.load_rules(self.temp_rules.name) line = "「这是一段对话」" result = self.parser._parse_line_content(line) @@ -128,12 +110,10 @@ class TestParser(unittest.TestCase): self.assertEqual(result[0]["type"], "dialogue") def test_parse_line_content_dice_roll(self): - """测试解析骰子投掷""" self.parser.load_rules(self.temp_rules.name) line = "检定结果: [d20 = 18]" result = self.parser._parse_line_content(line) - # 应该包含文本和骰子投掷 dice_tokens = [t for t in result if t["type"] == "dice_roll"] self.assertGreater(len(dice_tokens), 0) diff --git a/tests/test_processor.py b/tests/test_processor.py index c08fc52..adaf2db 100644 --- a/tests/test_processor.py +++ b/tests/test_processor.py @@ -1,17 +1,9 @@ -#!/usr/bin/env python3 -""" -Processor 模块单元测试 -""" - import unittest from conventionalrp.core.processor import Processor class TestProcessor(unittest.TestCase): - """Processor 类的单元测试""" - def setUp(self): - """设置测试环境""" self.processor = Processor() self.sample_tokens = [ { @@ -33,30 +25,26 @@ class TestProcessor(unittest.TestCase): ] def test_init_without_rules(self): - """测试无规则初始化""" processor = Processor() self.assertEqual(processor.rules, {}) def test_init_with_rules(self): - """测试带规则初始化""" rules = {"test_rule": "value"} processor = Processor(rules) self.assertEqual(processor.rules, rules) def test_process_tokens(self): - """测试处理 token 列表""" result = self.processor.process_tokens(self.sample_tokens) self.assertIsInstance(result, list) self.assertEqual(len(result), len(self.sample_tokens)) - # 检查处理标记 + # Check processing marks for token in result: if "timestamp" in token: self.assertTrue(token.get("processed")) def test_apply_rules(self): - """测试应用规则到单个 token""" token = self.sample_tokens[0] result = self.processor.apply_rules(token) @@ -65,7 +53,6 @@ class TestProcessor(unittest.TestCase): self.assertTrue(result.get("processed")) def test_generate_json_output(self): - """测试生成 JSON 输出""" output = self.processor.generate_json_output(self.sample_tokens) self.assertIsInstance(output, str) @@ -73,7 +60,6 @@ class TestProcessor(unittest.TestCase): self.assertIn("speaker", output) def test_generate_html_output(self): - """测试生成 HTML 输出""" output = self.processor.generate_html_output(self.sample_tokens) self.assertIsInstance(output, str) @@ -81,29 +67,25 @@ class TestProcessor(unittest.TestCase): self.assertIn("</html>", output) def test_generate_markdown_output(self): - """测试生成 Markdown 输出""" output = self.processor.generate_markdown_output(self.sample_tokens) self.assertIsInstance(output, str) self.assertIn("-", output) def test_generate_output_json(self): - """测试生成输出 - JSON 格式""" output = self.processor.generate_output(self.sample_tokens, "json") self.assertIsInstance(output, str) def test_generate_output_html(self): - """测试生成输出 - HTML 格式""" + output = self.processor.generate_output(self.sample_tokens, "html") output = self.processor.generate_output(self.sample_tokens, "html") self.assertIsInstance(output, str) def test_generate_output_markdown(self): - """测试生成输出 - Markdown 格式""" output = self.processor.generate_output(self.sample_tokens, "markdown") self.assertIsInstance(output, str) def test_generate_output_unsupported_format(self): - """测试生成输出 - 不支持的格式""" with self.assertRaises(ValueError) as context: self.processor.generate_output(self.sample_tokens, "pdf") diff --git a/tests/test_pyo3.py b/tests/test_pyo3.py deleted file mode 100644 index 9668519..0000000 --- a/tests/test_pyo3.py +++ /dev/null @@ -1,4 +0,0 @@ -from conventionalrp._core import sum_as_string - -if __name__ == "__main__": - print(sum_as_string(1, 2)) diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 13e4540..8095550 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,8 +1,3 @@ -#!/usr/bin/env python3 -""" -Renderers 模块单元测试 -""" - import unittest import json from conventionalrp.renderers.html_renderer import HTMLRenderer @@ -11,10 +6,7 @@ from conventionalrp.renderers.markdown_renderer import MarkdownRenderer class TestRenderers(unittest.TestCase): - """测试所有渲染器""" - def setUp(self): - """设置测试数据""" self.sample_data = [ { "type": "metadata", @@ -40,7 +32,6 @@ class TestRenderers(unittest.TestCase): } def test_html_renderer_basic(self): - """测试 HTML 渲染器基本功能""" renderer = HTMLRenderer() output = renderer.render(self.sample_data) @@ -50,51 +41,45 @@ class TestRenderers(unittest.TestCase): self.assertIn("<title>", output) def test_html_renderer_set_style(self): - """测试 HTML 渲染器设置样式""" renderer = HTMLRenderer() renderer.set_style("custom_style") - # 当前实现为占位符,仅测试不抛出异常 + # now style is set, just ensure no exceptions self.assertIsNotNone(renderer) def test_json_renderer_basic(self): - """测试 JSON 渲染器基本功能""" renderer = JSONRenderer() output = renderer.render(self.sample_data) self.assertIsInstance(output, str) - # 验证输出是有效的 JSON + # Output should be valid JSON parsed = json.loads(output) self.assertIsInstance(parsed, list) self.assertEqual(len(parsed), len(self.sample_data)) def test_json_renderer_unicode(self): - """测试 JSON 渲染器处理 Unicode""" renderer = JSONRenderer() output = renderer.render(self.sample_data) - # 应该保留中文字符 + # should preserve Chinese characters self.assertIn("艾莉娅", output) self.assertIn("测试", output) def test_markdown_renderer_basic(self): - """测试 Markdown 渲染器基本功能""" renderer = MarkdownRenderer() output = renderer.render(self.dict_data) self.assertIsInstance(output, str) - self.assertIn("##", output) # 应该有标题标记 - self.assertIn("测试标题", output) + self.assertIn("##", output) + self.assertIn("test content", output) def test_markdown_renderer_set_style(self): - """测试 Markdown 渲染器设置样式""" renderer = MarkdownRenderer() style = {"heading_level": 2} renderer.set_style(style) self.assertEqual(renderer.style, style) def test_all_renderers_empty_data(self): - """测试所有渲染器处理空数据""" empty_data = [] html_renderer = HTMLRenderer() diff --git a/tests/test_rule_extractor.py b/tests/test_rule_extractor.py index 6c4d585..2c2815c 100644 --- a/tests/test_rule_extractor.py +++ b/tests/test_rule_extractor.py @@ -1,8 +1,3 @@ -#!/usr/bin/env python3 -""" -RuleExtractor 模块单元测试 -""" - import unittest import tempfile import json5 @@ -11,11 +6,7 @@ from conventionalrp.extractors.rule_extractor import RuleExtractor class TestRuleExtractor(unittest.TestCase): - """RuleExtractor 类的单元测试""" - def setUp(self): - """设置测试环境""" - # 创建临时规则文件 self.temp_rules = tempfile.NamedTemporaryFile( mode='w', suffix='.json5', @@ -30,23 +21,19 @@ class TestRuleExtractor(unittest.TestCase): self.temp_rules.close() def tearDown(self): - """清理测试环境""" Path(self.temp_rules.name).unlink(missing_ok=True) def test_init_without_file(self): - """测试不带配置文件的初始化""" extractor = RuleExtractor() self.assertEqual(extractor.rules, {}) self.assertIsNone(extractor.config_file) def test_init_with_file(self): - """测试带配置文件的初始化""" extractor = RuleExtractor(self.temp_rules.name) self.assertIsNotNone(extractor.rules) self.assertIn("test_rule", extractor.rules) def test_load_rules_from_file_success(self): - """测试成功加载规则文件""" extractor = RuleExtractor() rules = extractor.load_rules_from_file(self.temp_rules.name) @@ -55,13 +42,11 @@ class TestRuleExtractor(unittest.TestCase): self.assertEqual(rules["test_rule"], "test_value") def test_load_rules_from_file_not_found(self): - """测试加载不存在的文件""" extractor = RuleExtractor() with self.assertRaises(FileNotFoundError): extractor.load_rules_from_file("nonexistent.json5") def test_load_rules_empty_file(self): - """测试加载空文件""" empty_file = tempfile.NamedTemporaryFile( mode='w', suffix='.json5', @@ -79,7 +64,6 @@ class TestRuleExtractor(unittest.TestCase): Path(empty_file.name).unlink(missing_ok=True) def test_load_rules_method(self): - """测试 load_rules 方法""" extractor = RuleExtractor() rules = extractor.load_rules(self.temp_rules.name) @@ -87,7 +71,6 @@ class TestRuleExtractor(unittest.TestCase): self.assertEqual(extractor.rules, rules) def test_extract_method(self): - """测试 extract 方法""" extractor = RuleExtractor(self.temp_rules.name) extracted = extractor.extract() diff --git a/tests/test_rust_core.py b/tests/test_rust_core.py new file mode 100644 index 0000000..1848e68 --- /dev/null +++ b/tests/test_rust_core.py @@ -0,0 +1,99 @@ +import sys +from pathlib import Path + +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root / "src")) + +from conventionalrp import _core + +def test_token(): + token = _core.Token("dialogue", "「你好世界」") + token.add_metadata("speaker", "艾莉娅") + token.add_metadata("timestamp", "2025-10-24 14:30:01") + + print(f"Token: {token}") + print(f"Type: {token.token_type}") + print(f"Content: {token.content}") + print(f"Speaker: {token.get_metadata('speaker')}") + print(f"Dict: {token.to_dict()}") + + +def test_regex_rule(): + rule = _core.RegexRule(r"\[d(\d+)\s*=\s*(\d+)\]", "dice_roll", 90) + + text = "检定结果: [d20 = 18]" + print(f"Text: {text}") + print(f"Matches: {rule.matches(text)}") + print(f"Extracted: {rule.extract(text)}") + print(f"Find all: {rule.find_all(text)}") + + +def test_text_parser(): + parser = _core.TextParser() + + parser.add_rule(r"\[d(\d+)\s*=\s*(\d+)\]", "dice_roll", 90) + parser.add_rule(r"「(.+?)」", "dialogue", 80) + parser.add_rule(r"\*\*(.+?)\*\*", "action", 70) + + print(f"规则数量: {parser.rule_count()}") + + line = "艾莉娅说「我要投掷」然后 **投掷骰子** 结果是 [d20 = 18]" + print(f"\n解析文本: {line}") + result = parser.parse_line(line) + print(f"解析结果: {result}") + + lines = [ + "「你好」", + "**挥剑** [d20 = 15]", + "普通文本" + ] + results = parser.parse_lines(lines) + for i, line_result in enumerate(results): + print(f" 行 {i+1}: {line_result}") + + +def test_fast_matcher(): + patterns = ["骰子", "投掷", "检定"] + matcher = _core.FastMatcher(patterns) + + text = "艾莉娅进行投掷骰子检定" + print(f"Text: {text}") + print(f"Contains any: {matcher.contains_any(text)}") + print(f"Find matches: {matcher.find_matches(text)}") + print(f"Count matches: {matcher.count_matches(text)}") + + +def benchmark_parser(): + import time + + parser = _core.TextParser() + parser.add_rule(r"\[d(\d+)\s*=\s*(\d+)\]", "dice_roll", 90) + parser.add_rule(r"「(.+?)」", "dialogue", 80) + parser.add_rule(r"\*\*(.+?)\*\*", "action", 70) + + test_lines = [ + "「你好」**挥剑** [d20 = 15]" for _ in range(1000) + ] + + start = time.time() + results = parser.parse_lines(test_lines) + elapsed = time.time() - start + + print(f"Process {len(test_lines)} lines") + print(f"time: {elapsed:.4f} s") + print(f"speed: {len(test_lines)/elapsed:.0f} lines/s") + + +if __name__ == "__main__": + try: + test_token() + test_regex_rule() + test_text_parser() + test_fast_matcher() + benchmark_parser() + + except Exception as e: + print(f"\nTest failed: {e}") + import traceback + traceback.print_exc() + sys.exit(1) diff --git a/uv.lock b/uv.lock deleted file mode 100644 index e38f683..0000000 --- a/uv.lock +++ /dev/null @@ -1,1027 +0,0 @@ -version = 1 -revision = 1 -requires-python = ">=3.9" -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version < '3.10'", -] - -[[package]] -name = "alabaster" -version = "0.7.16" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, -] - -[[package]] -name = "alabaster" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, -] - -[[package]] -name = "anyio" -version = "4.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, -] - -[[package]] -name = "babel" -version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, -] - -[[package]] -name = "beautifulsoup4" -version = "4.13.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, -] - -[[package]] -name = "certifi" -version = "2025.1.31" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, - { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, - { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, - { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, - { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, - { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, - { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, - { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, - { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, - { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, - { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, - { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, - { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, -] - -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "conventionalrp" -source = { editable = "." } - -[package.dev-dependencies] -dev = [ - { name = "docutils" }, - { name = "furo" }, - { name = "json5" }, - { name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "ruff" }, - { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "sphinx-autobuild" }, - { name = "sphinx-intl" }, - { name = "sphinxcontrib-httpdomain" }, - { name = "tomli" }, -] - -[package.metadata] - -[package.metadata.requires-dev] -dev = [ - { name = "docutils", specifier = ">=0.21.2" }, - { name = "furo", specifier = ">=2024.8.6" }, - { name = "json5", specifier = ">=0.10.0" }, - { name = "myst-parser", specifier = ">=3.0.1" }, - { name = "ruff", specifier = ">=0.9.6" }, - { name = "sphinx", specifier = ">=7.4.7" }, - { name = "sphinx-autobuild", specifier = ">=2024.10.3" }, - { name = "sphinx-intl", specifier = ">=2.3.1" }, - { name = "sphinxcontrib-httpdomain", specifier = ">=1.8.1" }, - { name = "tomli", specifier = ">=2.2.1" }, -] - -[[package]] -name = "docutils" -version = "0.21.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, -] - -[[package]] -name = "furo" -version = "2024.8.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "pygments" }, - { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "sphinx-basic-ng" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a0/e2/d351d69a9a9e4badb4a5be062c2d0e87bd9e6c23b5e57337fef14bef34c8/furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01", size = 1661506 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/48/e791a7ed487dbb9729ef32bb5d1af16693d8925f4366befef54119b2e576/furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c", size = 341333 }, -] - -[[package]] -name = "h11" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, -] - -[[package]] -name = "imagesize" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, -] - -[[package]] -name = "importlib-metadata" -version = "8.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/08/c1395a292bb23fd03bdf572a1357c5a733d3eecbab877641ceacab23db6e/importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580", size = 55767 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, -] - -[[package]] -name = "jinja2" -version = "3.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, -] - -[[package]] -name = "json5" -version = "0.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/3d/bbe62f3d0c05a689c711cff57b2e3ac3d3e526380adb7c781989f075115c/json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559", size = 48202 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/42/797895b952b682c3dafe23b1834507ee7f02f4d6299b65aaa61425763278/json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa", size = 34049 }, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, -] - -[[package]] -name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, - { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344 }, - { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389 }, - { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607 }, - { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728 }, - { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826 }, - { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843 }, - { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219 }, - { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946 }, - { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063 }, - { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, -] - -[[package]] -name = "mdit-py-plugins" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, -] - -[[package]] -name = "myst-parser" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "docutils", marker = "python_full_version < '3.10'" }, - { name = "jinja2", marker = "python_full_version < '3.10'" }, - { name = "markdown-it-py", marker = "python_full_version < '3.10'" }, - { name = "mdit-py-plugins", marker = "python_full_version < '3.10'" }, - { name = "pyyaml", marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/64/e2f13dac02f599980798c01156393b781aec983b52a6e4057ee58f07c43a/myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87", size = 92392 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1", size = 83163 }, -] - -[[package]] -name = "myst-parser" -version = "4.0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -dependencies = [ - { name = "docutils", marker = "python_full_version >= '3.10'" }, - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "markdown-it-py", marker = "python_full_version >= '3.10'" }, - { name = "mdit-py-plugins", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579 }, -] - -[[package]] -name = "packaging" -version = "24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, -] - -[[package]] -name = "pygments" -version = "2.19.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, - { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, - { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, - { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, - { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, - { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, - { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, - { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, - { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, - { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, -] - -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, -] - -[[package]] -name = "ruff" -version = "0.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/e1/e265aba384343dd8ddd3083f5e33536cd17e1566c41453a5517b5dd443be/ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9", size = 3639454 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/e3/3d2c022e687e18cf5d93d6bfa2722d46afc64eaa438c7fbbdd603b3597be/ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba", size = 11714128 }, - { url = "https://files.pythonhosted.org/packages/e1/22/aff073b70f95c052e5c58153cba735748c9e70107a77d03420d7850710a0/ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504", size = 11682539 }, - { url = "https://files.pythonhosted.org/packages/75/a7/f5b7390afd98a7918582a3d256cd3e78ba0a26165a467c1820084587cbf9/ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83", size = 11132512 }, - { url = "https://files.pythonhosted.org/packages/a6/e3/45de13ef65047fea2e33f7e573d848206e15c715e5cd56095589a7733d04/ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc", size = 11929275 }, - { url = "https://files.pythonhosted.org/packages/7d/f2/23d04cd6c43b2e641ab961ade8d0b5edb212ecebd112506188c91f2a6e6c/ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b", size = 11466502 }, - { url = "https://files.pythonhosted.org/packages/b5/6f/3a8cf166f2d7f1627dd2201e6cbc4cb81f8b7d58099348f0c1ff7b733792/ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e", size = 12676364 }, - { url = "https://files.pythonhosted.org/packages/f5/c4/db52e2189983c70114ff2b7e3997e48c8318af44fe83e1ce9517570a50c6/ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666", size = 13335518 }, - { url = "https://files.pythonhosted.org/packages/66/44/545f8a4d136830f08f4d24324e7db957c5374bf3a3f7a6c0bc7be4623a37/ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5", size = 12823287 }, - { url = "https://files.pythonhosted.org/packages/c5/26/8208ef9ee7431032c143649a9967c3ae1aae4257d95e6f8519f07309aa66/ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5", size = 14592374 }, - { url = "https://files.pythonhosted.org/packages/31/70/e917781e55ff39c5b5208bda384fd397ffd76605e68544d71a7e40944945/ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217", size = 12500173 }, - { url = "https://files.pythonhosted.org/packages/84/f5/e4ddee07660f5a9622a9c2b639afd8f3104988dc4f6ba0b73ffacffa9a8c/ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6", size = 11906555 }, - { url = "https://files.pythonhosted.org/packages/f1/2b/6ff2fe383667075eef8656b9892e73dd9b119b5e3add51298628b87f6429/ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897", size = 11538958 }, - { url = "https://files.pythonhosted.org/packages/3c/db/98e59e90de45d1eb46649151c10a062d5707b5b7f76f64eb1e29edf6ebb1/ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08", size = 12117247 }, - { url = "https://files.pythonhosted.org/packages/ec/bc/54e38f6d219013a9204a5a2015c09e7a8c36cedcd50a4b01ac69a550b9d9/ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656", size = 12554647 }, - { url = "https://files.pythonhosted.org/packages/a5/7d/7b461ab0e2404293c0627125bb70ac642c2e8d55bf590f6fce85f508f1b2/ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d", size = 9949214 }, - { url = "https://files.pythonhosted.org/packages/ee/30/c3cee10f915ed75a5c29c1e57311282d1a15855551a64795c1b2bbe5cf37/ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa", size = 10999914 }, - { url = "https://files.pythonhosted.org/packages/e8/a8/d71f44b93e3aa86ae232af1f2126ca7b95c0f515ec135462b3e1f351441c/ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a", size = 10177499 }, -] - -[[package]] -name = "setuptools" -version = "75.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782 }, -] - -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, -] - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, -] - -[[package]] -name = "soupsieve" -version = "2.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, -] - -[[package]] -name = "sphinx" -version = "7.4.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "babel", marker = "python_full_version < '3.10'" }, - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version < '3.10'" }, - { name = "imagesize", marker = "python_full_version < '3.10'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, - { name = "jinja2", marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "requests", marker = "python_full_version < '3.10'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.10'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.10'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.10'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.10'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.10'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.10'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624 }, -] - -[[package]] -name = "sphinx" -version = "8.1.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] -dependencies = [ - { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "babel", marker = "python_full_version >= '3.10'" }, - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version >= '3.10'" }, - { name = "imagesize", marker = "python_full_version >= '3.10'" }, - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.10'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, -] - -[[package]] -name = "sphinx-autobuild" -version = "2024.10.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, - { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "starlette" }, - { name = "uvicorn" }, - { name = "watchfiles" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908 }, -] - -[[package]] -name = "sphinx-basic-ng" -version = "1.0.0b2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496 }, -] - -[[package]] -name = "sphinx-intl" -version = "2.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "click" }, - { name = "setuptools" }, - { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a5/01/4f64b7d28e3e0cbf25a70a79f26c4683c987ca7c6c265492115cfb302ca4/sphinx_intl-2.3.1.tar.gz", hash = "sha256:e2d04b907f57a029d46b4da344fd791e660d935eab6ced8a274fc65446389af1", size = 28006 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/5d/6a9d1460ef6e079d00350eb6203efd6a6a899692e927b9b18d652490b5e5/sphinx_intl-2.3.1-py3-none-any.whl", hash = "sha256:e3f4be80743e0e4c778fbc07e72b062c13c88c50645a69b62557a3f57a24b7fc", size = 13801 }, -] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, -] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, -] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, -] - -[[package]] -name = "sphinxcontrib-httpdomain" -version = "1.8.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, - { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/ef/82d3cfafb7febce4f7df8dcf3cde9d072350b41066e05a4f559b4e9105d0/sphinxcontrib-httpdomain-1.8.1.tar.gz", hash = "sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b", size = 19266 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/49/aad47b8cf27a0d7703f1311aad8c368bb22866ddee1a2d2cd3f69bc45e0c/sphinxcontrib_httpdomain-1.8.1-py2.py3-none-any.whl", hash = "sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5", size = 25513 }, -] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, -] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, -] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, -] - -[[package]] -name = "starlette" -version = "0.45.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ff/fb/2984a686808b89a6781526129a4b51266f678b2d2b97ab2d325e56116df8/starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f", size = 2574076 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/61/f2b52e107b1fc8944b33ef56bf6ac4ebbe16d91b94d2b87ce013bf63fb84/starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d", size = 71507 }, -] - -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, -] - -[[package]] -name = "urllib3" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, -] - -[[package]] -name = "uvicorn" -version = "0.34.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, -] - -[[package]] -name = "watchfiles" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/26/c705fc77d0a9ecdb9b66f1e2976d95b81df3cae518967431e7dbf9b5e219/watchfiles-1.0.4.tar.gz", hash = "sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205", size = 94625 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/02/22fcaed0396730b0d362bc8d1ffb3be2658fd473eecbb2ba84243e157f11/watchfiles-1.0.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ba5bb3073d9db37c64520681dd2650f8bd40902d991e7b4cfaeece3e32561d08", size = 395212 }, - { url = "https://files.pythonhosted.org/packages/e9/3d/ec5a2369a46edf3ebe092c39d9ae48e8cb6dacbde51c4b4f98936c524269/watchfiles-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f25d0ba0fe2b6d2c921cf587b2bf4c451860086534f40c384329fb96e2044d1", size = 384815 }, - { url = "https://files.pythonhosted.org/packages/df/b4/898991cececbe171e67142c31905510203649569d9817848f47c4177ee42/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47eb32ef8c729dbc4f4273baece89398a4d4b5d21a1493efea77a17059f4df8a", size = 450680 }, - { url = "https://files.pythonhosted.org/packages/58/f7/d4aa3000e812cfb5e5c2c6c0a3ec9d0a46a42489a8727edd160631c4e210/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:076f293100db3b0b634514aa0d294b941daa85fc777f9c698adb1009e5aca0b1", size = 455923 }, - { url = "https://files.pythonhosted.org/packages/dd/95/7e2e4c6aba1b02fb5c76d2f6a450b85215921ec5f8f7ad5efd075369563f/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1eacd91daeb5158c598fe22d7ce66d60878b6294a86477a4715154990394c9b3", size = 482339 }, - { url = "https://files.pythonhosted.org/packages/bb/67/4265b0fabcc2ef2c9e3e8802ba7908cf718a357ebfb49c72e53787156a48/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13c2ce7b72026cfbca120d652f02c7750f33b4c9395d79c9790b27f014c8a5a2", size = 519908 }, - { url = "https://files.pythonhosted.org/packages/0d/96/b57802d5f8164bdf070befb4fd3dec4edba5a364ec0670965a97eb8098ce/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90192cdc15ab7254caa7765a98132a5a41471cf739513cc9bcf7d2ffcc0ec7b2", size = 501410 }, - { url = "https://files.pythonhosted.org/packages/8b/18/6db0de4e8911ba14e31853201b40c0fa9fea5ecf3feb86b0ad58f006dfc3/watchfiles-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278aaa395f405972e9f523bd786ed59dfb61e4b827856be46a42130605fd0899", size = 452876 }, - { url = "https://files.pythonhosted.org/packages/df/df/092a961815edf723a38ba2638c49491365943919c3526cc9cf82c42786a6/watchfiles-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a462490e75e466edbb9fc4cd679b62187153b3ba804868452ef0577ec958f5ff", size = 615353 }, - { url = "https://files.pythonhosted.org/packages/f3/cf/b85fe645de4ff82f3f436c5e9032379fce37c303f6396a18f9726cc34519/watchfiles-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8d0d0630930f5cd5af929040e0778cf676a46775753e442a3f60511f2409f48f", size = 613187 }, - { url = "https://files.pythonhosted.org/packages/f6/d4/a9fea27aef4dd69689bc3556718c1157a7accb72aa035ece87c1fa8483b5/watchfiles-1.0.4-cp310-cp310-win32.whl", hash = "sha256:cc27a65069bcabac4552f34fd2dce923ce3fcde0721a16e4fb1b466d63ec831f", size = 270799 }, - { url = "https://files.pythonhosted.org/packages/df/02/dbe9d4439f15dd4ad0720b6e039bde9d66d1f830331f34c18eb70fa6608e/watchfiles-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:8b1f135238e75d075359cf506b27bf3f4ca12029c47d3e769d8593a2024ce161", size = 284145 }, - { url = "https://files.pythonhosted.org/packages/0f/bb/8461adc4b1fed009546fb797fc0d5698dcfe5e289cb37e1b8f16a93cdc30/watchfiles-1.0.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2a9f93f8439639dc244c4d2902abe35b0279102bca7bbcf119af964f51d53c19", size = 394869 }, - { url = "https://files.pythonhosted.org/packages/55/88/9ebf36b3547176d1709c320de78c1fa3263a46be31b5b1267571d9102686/watchfiles-1.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eea33ad8c418847dd296e61eb683cae1c63329b6d854aefcd412e12d94ee235", size = 384905 }, - { url = "https://files.pythonhosted.org/packages/03/8a/04335ce23ef78d8c69f0913e8b20cf7d9233e3986543aeef95ef2d6e43d2/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31f1a379c9dcbb3f09cf6be1b7e83b67c0e9faabed0471556d9438a4a4e14202", size = 449944 }, - { url = "https://files.pythonhosted.org/packages/17/4e/c8d5dcd14fe637f4633616dabea8a4af0a10142dccf3b43e0f081ba81ab4/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab594e75644421ae0a2484554832ca5895f8cab5ab62de30a1a57db460ce06c6", size = 456020 }, - { url = "https://files.pythonhosted.org/packages/5e/74/3e91e09e1861dd7fbb1190ce7bd786700dc0fbc2ccd33bb9fff5de039229/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc2eb5d14a8e0d5df7b36288979176fbb39672d45184fc4b1c004d7c3ce29317", size = 482983 }, - { url = "https://files.pythonhosted.org/packages/a1/3d/e64de2d1ce4eb6a574fd78ce3a28c279da263be9ef3cfcab6f708df192f2/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f68d8e9d5a321163ddacebe97091000955a1b74cd43724e346056030b0bacee", size = 520320 }, - { url = "https://files.pythonhosted.org/packages/2c/bd/52235f7063b57240c66a991696ed27e2a18bd6fcec8a1ea5a040b70d0611/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9ce064e81fe79faa925ff03b9f4c1a98b0bbb4a1b8c1b015afa93030cb21a49", size = 500988 }, - { url = "https://files.pythonhosted.org/packages/3a/b0/ff04194141a5fe650c150400dd9e42667916bc0f52426e2e174d779b8a74/watchfiles-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b77d5622ac5cc91d21ae9c2b284b5d5c51085a0bdb7b518dba263d0af006132c", size = 452573 }, - { url = "https://files.pythonhosted.org/packages/3d/9d/966164332c5a178444ae6d165082d4f351bd56afd9c3ec828eecbf190e6a/watchfiles-1.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1941b4e39de9b38b868a69b911df5e89dc43767feeda667b40ae032522b9b5f1", size = 615114 }, - { url = "https://files.pythonhosted.org/packages/94/df/f569ae4c1877f96ad4086c153a8eee5a19a3b519487bf5c9454a3438c341/watchfiles-1.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f8c4998506241dedf59613082d1c18b836e26ef2a4caecad0ec41e2a15e4226", size = 613076 }, - { url = "https://files.pythonhosted.org/packages/15/ae/8ce5f29e65d5fa5790e3c80c289819c55e12be2e1b9f5b6a0e55e169b97d/watchfiles-1.0.4-cp311-cp311-win32.whl", hash = "sha256:4ebbeca9360c830766b9f0df3640b791be569d988f4be6c06d6fae41f187f105", size = 271013 }, - { url = "https://files.pythonhosted.org/packages/a4/c6/79dc4a7c598a978e5fafa135090aaf7bbb03b8dec7bada437dfbe578e7ed/watchfiles-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:05d341c71f3d7098920f8551d4df47f7b57ac5b8dad56558064c3431bdfc0b74", size = 284229 }, - { url = "https://files.pythonhosted.org/packages/37/3d/928633723211753f3500bfb138434f080363b87a1b08ca188b1ce54d1e05/watchfiles-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:32b026a6ab64245b584acf4931fe21842374da82372d5c039cba6bf99ef722f3", size = 276824 }, - { url = "https://files.pythonhosted.org/packages/5b/1a/8f4d9a1461709756ace48c98f07772bc6d4519b1e48b5fa24a4061216256/watchfiles-1.0.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:229e6ec880eca20e0ba2f7e2249c85bae1999d330161f45c78d160832e026ee2", size = 391345 }, - { url = "https://files.pythonhosted.org/packages/bc/d2/6750b7b3527b1cdaa33731438432e7238a6c6c40a9924049e4cebfa40805/watchfiles-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5717021b199e8353782dce03bd8a8f64438832b84e2885c4a645f9723bf656d9", size = 381515 }, - { url = "https://files.pythonhosted.org/packages/4e/17/80500e42363deef1e4b4818729ed939aaddc56f82f4e72b2508729dd3c6b/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0799ae68dfa95136dde7c472525700bd48777875a4abb2ee454e3ab18e9fc712", size = 449767 }, - { url = "https://files.pythonhosted.org/packages/10/37/1427fa4cfa09adbe04b1e97bced19a29a3462cc64c78630787b613a23f18/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43b168bba889886b62edb0397cab5b6490ffb656ee2fcb22dec8bfeb371a9e12", size = 455677 }, - { url = "https://files.pythonhosted.org/packages/c5/7a/39e9397f3a19cb549a7d380412fd9e507d4854eddc0700bfad10ef6d4dba/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb2c46e275fbb9f0c92e7654b231543c7bbfa1df07cdc4b99fa73bedfde5c844", size = 482219 }, - { url = "https://files.pythonhosted.org/packages/45/2d/7113931a77e2ea4436cad0c1690c09a40a7f31d366f79c6f0a5bc7a4f6d5/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:857f5fc3aa027ff5e57047da93f96e908a35fe602d24f5e5d8ce64bf1f2fc733", size = 518830 }, - { url = "https://files.pythonhosted.org/packages/f9/1b/50733b1980fa81ef3c70388a546481ae5fa4c2080040100cd7bf3bf7b321/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55ccfd27c497b228581e2838d4386301227fc0cb47f5a12923ec2fe4f97b95af", size = 497997 }, - { url = "https://files.pythonhosted.org/packages/2b/b4/9396cc61b948ef18943e7c85ecfa64cf940c88977d882da57147f62b34b1/watchfiles-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c11ea22304d17d4385067588123658e9f23159225a27b983f343fcffc3e796a", size = 452249 }, - { url = "https://files.pythonhosted.org/packages/fb/69/0c65a5a29e057ad0dc691c2fa6c23b2983c7dabaa190ba553b29ac84c3cc/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:74cb3ca19a740be4caa18f238298b9d472c850f7b2ed89f396c00a4c97e2d9ff", size = 614412 }, - { url = "https://files.pythonhosted.org/packages/7f/b9/319fcba6eba5fad34327d7ce16a6b163b39741016b1996f4a3c96b8dd0e1/watchfiles-1.0.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7cce76c138a91e720d1df54014a047e680b652336e1b73b8e3ff3158e05061e", size = 611982 }, - { url = "https://files.pythonhosted.org/packages/f1/47/143c92418e30cb9348a4387bfa149c8e0e404a7c5b0585d46d2f7031b4b9/watchfiles-1.0.4-cp312-cp312-win32.whl", hash = "sha256:b045c800d55bc7e2cadd47f45a97c7b29f70f08a7c2fa13241905010a5493f94", size = 271822 }, - { url = "https://files.pythonhosted.org/packages/ea/94/b0165481bff99a64b29e46e07ac2e0df9f7a957ef13bec4ceab8515f44e3/watchfiles-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:c2acfa49dd0ad0bf2a9c0bb9a985af02e89345a7189be1efc6baa085e0f72d7c", size = 285441 }, - { url = "https://files.pythonhosted.org/packages/11/de/09fe56317d582742d7ca8c2ca7b52a85927ebb50678d9b0fa8194658f536/watchfiles-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:22bb55a7c9e564e763ea06c7acea24fc5d2ee5dfc5dafc5cfbedfe58505e9f90", size = 277141 }, - { url = "https://files.pythonhosted.org/packages/08/98/f03efabec64b5b1fa58c0daab25c68ef815b0f320e54adcacd0d6847c339/watchfiles-1.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9", size = 390954 }, - { url = "https://files.pythonhosted.org/packages/16/09/4dd49ba0a32a45813debe5fb3897955541351ee8142f586303b271a02b40/watchfiles-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60", size = 381133 }, - { url = "https://files.pythonhosted.org/packages/76/59/5aa6fc93553cd8d8ee75c6247763d77c02631aed21551a97d94998bf1dae/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407", size = 449516 }, - { url = "https://files.pythonhosted.org/packages/4c/aa/df4b6fe14b6317290b91335b23c96b488d365d65549587434817e06895ea/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d", size = 454820 }, - { url = "https://files.pythonhosted.org/packages/5e/71/185f8672f1094ce48af33252c73e39b48be93b761273872d9312087245f6/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d", size = 481550 }, - { url = "https://files.pythonhosted.org/packages/85/d7/50ebba2c426ef1a5cb17f02158222911a2e005d401caf5d911bfca58f4c4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b", size = 518647 }, - { url = "https://files.pythonhosted.org/packages/f0/7a/4c009342e393c545d68987e8010b937f72f47937731225b2b29b7231428f/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590", size = 497547 }, - { url = "https://files.pythonhosted.org/packages/0f/7c/1cf50b35412d5c72d63b2bf9a4fffee2e1549a245924960dd087eb6a6de4/watchfiles-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902", size = 452179 }, - { url = "https://files.pythonhosted.org/packages/d6/a9/3db1410e1c1413735a9a472380e4f431ad9a9e81711cda2aaf02b7f62693/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1", size = 614125 }, - { url = "https://files.pythonhosted.org/packages/f2/e1/0025d365cf6248c4d1ee4c3d2e3d373bdd3f6aff78ba4298f97b4fad2740/watchfiles-1.0.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303", size = 611911 }, - { url = "https://files.pythonhosted.org/packages/55/55/035838277d8c98fc8c917ac9beeb0cd6c59d675dc2421df5f9fcf44a0070/watchfiles-1.0.4-cp313-cp313-win32.whl", hash = "sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80", size = 271152 }, - { url = "https://files.pythonhosted.org/packages/f0/e5/96b8e55271685ddbadc50ce8bc53aa2dff278fb7ac4c2e473df890def2dc/watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc", size = 285216 }, - { url = "https://files.pythonhosted.org/packages/15/81/54484fc2fa715abe79694b975692af963f0878fb9d72b8251aa542bf3f10/watchfiles-1.0.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d3452c1ec703aa1c61e15dfe9d482543e4145e7c45a6b8566978fbb044265a21", size = 394967 }, - { url = "https://files.pythonhosted.org/packages/14/b3/557f0cd90add86586fe3deeebd11e8299db6bc3452b44a534f844c6ab831/watchfiles-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b75fee5a16826cf5c46fe1c63116e4a156924d668c38b013e6276f2582230f0", size = 384707 }, - { url = "https://files.pythonhosted.org/packages/03/a3/34638e1bffcb85a405e7b005e30bb211fd9be2ab2cb1847f2ceb81bef27b/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e997802d78cdb02623b5941830ab06f8860038faf344f0d288d325cc9c5d2ff", size = 450442 }, - { url = "https://files.pythonhosted.org/packages/8f/9f/6a97460dd11a606003d634c7158d9fea8517e98daffc6f56d0f5fde2e86a/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0611d244ce94d83f5b9aff441ad196c6e21b55f77f3c47608dcf651efe54c4a", size = 455959 }, - { url = "https://files.pythonhosted.org/packages/9d/bb/e0648c6364e4d37ec692bc3f0c77507d17d8bb8f75689148819142010bbf/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9745a4210b59e218ce64c91deb599ae8775c8a9da4e95fb2ee6fe745fc87d01a", size = 483187 }, - { url = "https://files.pythonhosted.org/packages/dd/ad/d9290586a25288a81dfa8ad6329cf1de32aa1a9798ace45259eb95dcfb37/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4810ea2ae622add560f4aa50c92fef975e475f7ac4900ce5ff5547b2434642d8", size = 519733 }, - { url = "https://files.pythonhosted.org/packages/4e/a9/150c1666825cc9637093f8cae7fc6f53b3296311ab8bd65f1389acb717cb/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d103cd01458f22462dedeb5a3382b7f2c57d07ff033fbc9465919e5e1d0f3", size = 502275 }, - { url = "https://files.pythonhosted.org/packages/44/dc/5bfd21e20a330aca1706ac44713bc322838061938edf4b53130f97a7b211/watchfiles-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbd912a61543a36aef85e34f212e5d2486e7c53ebfdb70d1e0b060cc50dd0bf", size = 452907 }, - { url = "https://files.pythonhosted.org/packages/50/fe/8f4fc488f1699f564687b697456eb5c0cb8e2b0b8538150511c234c62094/watchfiles-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0bc80d91ddaf95f70258cf78c471246846c1986bcc5fd33ccc4a1a67fcb40f9a", size = 615927 }, - { url = "https://files.pythonhosted.org/packages/ad/19/2e45f6f6eec89dd97a4d281635e3d73c17e5f692e7432063bdfdf9562c89/watchfiles-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab0311bb2ffcd9f74b6c9de2dda1612c13c84b996d032cd74799adb656af4e8b", size = 613435 }, - { url = "https://files.pythonhosted.org/packages/91/17/dc5ac62ca377827c24321d68050efc2eaee2ebaf3f21d055bbce2206d309/watchfiles-1.0.4-cp39-cp39-win32.whl", hash = "sha256:02a526ee5b5a09e8168314c905fc545c9bc46509896ed282aeb5a8ba9bd6ca27", size = 270810 }, - { url = "https://files.pythonhosted.org/packages/82/2b/dad851342492d538e7ffe72a8c756f747dd147988abb039ac9d6577d2235/watchfiles-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:a5ae5706058b27c74bac987d615105da17724172d5aaacc6c362a40599b6de43", size = 284866 }, - { url = "https://files.pythonhosted.org/packages/6f/06/175d5ac6b838fb319008c0cd981d7bf289317c510154d411d3584ca2b67b/watchfiles-1.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdcc92daeae268de1acf5b7befcd6cfffd9a047098199056c72e4623f531de18", size = 396269 }, - { url = "https://files.pythonhosted.org/packages/86/ee/5db93b0b57dc0587abdbac4149296ee73275f615d790a82cb5598af0557f/watchfiles-1.0.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8d3d9203705b5797f0af7e7e5baa17c8588030aaadb7f6a86107b7247303817", size = 386010 }, - { url = "https://files.pythonhosted.org/packages/75/61/fe0dc5fedf152bfc085a53711f740701f6bdb8ab6b5c950402b681d4858b/watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdef5a1be32d0b07dcea3318a0be95d42c98ece24177820226b56276e06b63b0", size = 450913 }, - { url = "https://files.pythonhosted.org/packages/9f/dd/3c7731af3baf1a9957afc643d176f94480921a690ec3237c9f9d11301c08/watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342622287b5604ddf0ed2d085f3a589099c9ae8b7331df3ae9845571586c4f3d", size = 453474 }, - { url = "https://files.pythonhosted.org/packages/6b/b4/c3998f54c91a35cee60ee6d3a855a069c5dff2bae6865147a46e9090dccd/watchfiles-1.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9fe37a2de80aa785d340f2980276b17ef697ab8db6019b07ee4fd28a8359d2f3", size = 395565 }, - { url = "https://files.pythonhosted.org/packages/3f/05/ac1a4d235beb9ddfb8ac26ce93a00ba6bd1b1b43051ef12d7da957b4a9d1/watchfiles-1.0.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9d1ef56b56ed7e8f312c934436dea93bfa3e7368adfcf3df4c0da6d4de959a1e", size = 385406 }, - { url = "https://files.pythonhosted.org/packages/4c/ea/36532e7d86525f4e52a10efed182abf33efb106a93d49f5fbc994b256bcd/watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b42cac65beae3a362629950c444077d1b44f1790ea2772beaea95451c086bb", size = 450424 }, - { url = "https://files.pythonhosted.org/packages/7a/e9/3cbcf4d70cd0b6d3f30631deae1bf37cc0be39887ca327a44462fe546bf5/watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e0227b8ed9074c6172cf55d85b5670199c99ab11fd27d2c473aa30aec67ee42", size = 452488 }, -] - -[[package]] -name = "websockets" -version = "14.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/54/8359678c726243d19fae38ca14a334e740782336c9f19700858c4eb64a1e/websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5", size = 164394 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/fa/76607eb7dcec27b2d18d63f60a32e60e2b8629780f343bb83a4dbb9f4350/websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885", size = 163089 }, - { url = "https://files.pythonhosted.org/packages/9e/00/ad2246b5030575b79e7af0721810fdaecaf94c4b2625842ef7a756fa06dd/websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397", size = 160741 }, - { url = "https://files.pythonhosted.org/packages/72/f7/60f10924d333a28a1ff3fcdec85acf226281331bdabe9ad74947e1b7fc0a/websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610", size = 160996 }, - { url = "https://files.pythonhosted.org/packages/63/7c/c655789cf78648c01ac6ecbe2d6c18f91b75bdc263ffee4d08ce628d12f0/websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3", size = 169974 }, - { url = "https://files.pythonhosted.org/packages/fb/5b/013ed8b4611857ac92ac631079c08d9715b388bd1d88ec62e245f87a39df/websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980", size = 168985 }, - { url = "https://files.pythonhosted.org/packages/cd/33/aa3e32fd0df213a5a442310754fe3f89dd87a0b8e5b4e11e0991dd3bcc50/websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8", size = 169297 }, - { url = "https://files.pythonhosted.org/packages/93/17/dae0174883d6399f57853ac44abf5f228eaba86d98d160f390ffabc19b6e/websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7", size = 169677 }, - { url = "https://files.pythonhosted.org/packages/42/e2/0375af7ac00169b98647c804651c515054b34977b6c1354f1458e4116c1e/websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f", size = 169089 }, - { url = "https://files.pythonhosted.org/packages/73/8d/80f71d2a351a44b602859af65261d3dde3a0ce4e76cf9383738a949e0cc3/websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d", size = 169026 }, - { url = "https://files.pythonhosted.org/packages/48/97/173b1fa6052223e52bb4054a141433ad74931d94c575e04b654200b98ca4/websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d", size = 163967 }, - { url = "https://files.pythonhosted.org/packages/c0/5b/2fcf60f38252a4562b28b66077e0d2b48f91fef645d5f78874cd1dec807b/websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2", size = 164413 }, - { url = "https://files.pythonhosted.org/packages/15/b6/504695fb9a33df0ca56d157f5985660b5fc5b4bf8c78f121578d2d653392/websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166", size = 163088 }, - { url = "https://files.pythonhosted.org/packages/81/26/ebfb8f6abe963c795122439c6433c4ae1e061aaedfc7eff32d09394afbae/websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f", size = 160745 }, - { url = "https://files.pythonhosted.org/packages/a1/c6/1435ad6f6dcbff80bb95e8986704c3174da8866ddb751184046f5c139ef6/websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910", size = 160995 }, - { url = "https://files.pythonhosted.org/packages/96/63/900c27cfe8be1a1f2433fc77cd46771cf26ba57e6bdc7cf9e63644a61863/websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c", size = 170543 }, - { url = "https://files.pythonhosted.org/packages/00/8b/bec2bdba92af0762d42d4410593c1d7d28e9bfd952c97a3729df603dc6ea/websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473", size = 169546 }, - { url = "https://files.pythonhosted.org/packages/6b/a9/37531cb5b994f12a57dec3da2200ef7aadffef82d888a4c29a0d781568e4/websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473", size = 169911 }, - { url = "https://files.pythonhosted.org/packages/60/d5/a6eadba2ed9f7e65d677fec539ab14a9b83de2b484ab5fe15d3d6d208c28/websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56", size = 170183 }, - { url = "https://files.pythonhosted.org/packages/76/57/a338ccb00d1df881c1d1ee1f2a20c9c1b5b29b51e9e0191ee515d254fea6/websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142", size = 169623 }, - { url = "https://files.pythonhosted.org/packages/64/22/e5f7c33db0cb2c1d03b79fd60d189a1da044e2661f5fd01d629451e1db89/websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d", size = 169583 }, - { url = "https://files.pythonhosted.org/packages/aa/2e/2b4662237060063a22e5fc40d46300a07142afe30302b634b4eebd717c07/websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a", size = 163969 }, - { url = "https://files.pythonhosted.org/packages/94/a5/0cda64e1851e73fc1ecdae6f42487babb06e55cb2f0dc8904b81d8ef6857/websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b", size = 164408 }, - { url = "https://files.pythonhosted.org/packages/c1/81/04f7a397653dc8bec94ddc071f34833e8b99b13ef1a3804c149d59f92c18/websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c", size = 163096 }, - { url = "https://files.pythonhosted.org/packages/ec/c5/de30e88557e4d70988ed4d2eabd73fd3e1e52456b9f3a4e9564d86353b6d/websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967", size = 160758 }, - { url = "https://files.pythonhosted.org/packages/e5/8c/d130d668781f2c77d106c007b6c6c1d9db68239107c41ba109f09e6c218a/websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990", size = 160995 }, - { url = "https://files.pythonhosted.org/packages/a6/bc/f6678a0ff17246df4f06765e22fc9d98d1b11a258cc50c5968b33d6742a1/websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda", size = 170815 }, - { url = "https://files.pythonhosted.org/packages/d8/b2/8070cb970c2e4122a6ef38bc5b203415fd46460e025652e1ee3f2f43a9a3/websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95", size = 169759 }, - { url = "https://files.pythonhosted.org/packages/81/da/72f7caabd94652e6eb7e92ed2d3da818626e70b4f2b15a854ef60bf501ec/websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3", size = 170178 }, - { url = "https://files.pythonhosted.org/packages/31/e0/812725b6deca8afd3a08a2e81b3c4c120c17f68c9b84522a520b816cda58/websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9", size = 170453 }, - { url = "https://files.pythonhosted.org/packages/66/d3/8275dbc231e5ba9bb0c4f93144394b4194402a7a0c8ffaca5307a58ab5e3/websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267", size = 169830 }, - { url = "https://files.pythonhosted.org/packages/a3/ae/e7d1a56755ae15ad5a94e80dd490ad09e345365199600b2629b18ee37bc7/websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe", size = 169824 }, - { url = "https://files.pythonhosted.org/packages/b6/32/88ccdd63cb261e77b882e706108d072e4f1c839ed723bf91a3e1f216bf60/websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205", size = 163981 }, - { url = "https://files.pythonhosted.org/packages/b3/7d/32cdb77990b3bdc34a306e0a0f73a1275221e9a66d869f6ff833c95b56ef/websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce", size = 164421 }, - { url = "https://files.pythonhosted.org/packages/82/94/4f9b55099a4603ac53c2912e1f043d6c49d23e94dd82a9ce1eb554a90215/websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e", size = 163102 }, - { url = "https://files.pythonhosted.org/packages/8e/b7/7484905215627909d9a79ae07070057afe477433fdacb59bf608ce86365a/websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad", size = 160766 }, - { url = "https://files.pythonhosted.org/packages/a3/a4/edb62efc84adb61883c7d2c6ad65181cb087c64252138e12d655989eec05/websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03", size = 160998 }, - { url = "https://files.pythonhosted.org/packages/f5/79/036d320dc894b96af14eac2529967a6fc8b74f03b83c487e7a0e9043d842/websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f", size = 170780 }, - { url = "https://files.pythonhosted.org/packages/63/75/5737d21ee4dd7e4b9d487ee044af24a935e36a9ff1e1419d684feedcba71/websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5", size = 169717 }, - { url = "https://files.pythonhosted.org/packages/2c/3c/bf9b2c396ed86a0b4a92ff4cdaee09753d3ee389be738e92b9bbd0330b64/websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a", size = 170155 }, - { url = "https://files.pythonhosted.org/packages/75/2d/83a5aca7247a655b1da5eb0ee73413abd5c3a57fc8b92915805e6033359d/websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20", size = 170495 }, - { url = "https://files.pythonhosted.org/packages/79/dd/699238a92761e2f943885e091486378813ac8f43e3c84990bc394c2be93e/websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2", size = 169880 }, - { url = "https://files.pythonhosted.org/packages/c8/c9/67a8f08923cf55ce61aadda72089e3ed4353a95a3a4bc8bf42082810e580/websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307", size = 169856 }, - { url = "https://files.pythonhosted.org/packages/17/b1/1ffdb2680c64e9c3921d99db460546194c40d4acbef999a18c37aa4d58a3/websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc", size = 163974 }, - { url = "https://files.pythonhosted.org/packages/14/13/8b7fc4cb551b9cfd9890f0fd66e53c18a06240319915533b033a56a3d520/websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f", size = 164420 }, - { url = "https://files.pythonhosted.org/packages/6f/eb/367e0ed7b8a960b4fc12c7c6bf3ebddf06875037de641637994849560d47/websockets-14.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cd5706caec1686c5d233bc76243ff64b1c0dc445339bd538f30547e787c11fe", size = 163087 }, - { url = "https://files.pythonhosted.org/packages/96/f7/1f18d028ec4a2c14598dfec6a73381a915c27464b693873198c1de872095/websockets-14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec607328ce95a2f12b595f7ae4c5d71bf502212bddcea528290b35c286932b12", size = 160740 }, - { url = "https://files.pythonhosted.org/packages/5c/db/b4b353fb9c3f0eaa8138ea4c76e6fa555b6d2821ed2d51d0ac3c320bc57e/websockets-14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da85651270c6bfb630136423037dd4975199e5d4114cae6d3066641adcc9d1c7", size = 160992 }, - { url = "https://files.pythonhosted.org/packages/b9/b1/9149e420c61f375e432654d5c1545e563b90ac1f829ee1a8d1dccaf0869d/websockets-14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ecadc7ce90accf39903815697917643f5b7cfb73c96702318a096c00aa71f5", size = 169757 }, - { url = "https://files.pythonhosted.org/packages/2b/33/0bb58204191e113212360f1392b6b1e9f85f62c7ca5b3b15f52f2f835516/websockets-14.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1979bee04af6a78608024bad6dfcc0cc930ce819f9e10342a29a05b5320355d0", size = 168762 }, - { url = "https://files.pythonhosted.org/packages/be/3d/c3c192f16210d7b7535fbf4ee9a299612f4dccff665587617b13fa0a6aa3/websockets-14.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dddacad58e2614a24938a50b85969d56f88e620e3f897b7d80ac0d8a5800258", size = 169060 }, - { url = "https://files.pythonhosted.org/packages/a6/73/75efa8d9e4b1b257818a7b7a0b9ac84a07c91120b52148941370ef2c8f16/websockets-14.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:89a71173caaf75fa71a09a5f614f450ba3ec84ad9fca47cb2422a860676716f0", size = 169457 }, - { url = "https://files.pythonhosted.org/packages/a4/11/300cf36cfd6990ffb218394862f0513be8c21917c9ff5e362f94599caedd/websockets-14.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6af6a4b26eea4fc06c6818a6b962a952441e0e39548b44773502761ded8cc1d4", size = 168860 }, - { url = "https://files.pythonhosted.org/packages/c0/3d/5fd82500714ab7c09f003bde671dad1a3a131ac77b6b11ada72e466de4f6/websockets-14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:80c8efa38957f20bba0117b48737993643204645e9ec45512579132508477cfc", size = 168825 }, - { url = "https://files.pythonhosted.org/packages/88/16/715580eb6caaacc232f303e9619103a42dcd354b0854baa5ed26aacaf828/websockets-14.2-cp39-cp39-win32.whl", hash = "sha256:2e20c5f517e2163d76e2729104abc42639c41cf91f7b1839295be43302713661", size = 163960 }, - { url = "https://files.pythonhosted.org/packages/63/a7/a1035cb198eaa12eaa9621aaaa3ec021b0e3bac96e1df9ceb6bfe5e53e5f/websockets-14.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4c8cef610e8d7c70dea92e62b6814a8cd24fbd01d7103cc89308d2bfe1659ef", size = 164424 }, - { url = "https://files.pythonhosted.org/packages/10/3d/91d3d2bb1325cd83e8e2c02d0262c7d4426dc8fa0831ef1aa4d6bf2041af/websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29", size = 160773 }, - { url = "https://files.pythonhosted.org/packages/33/7c/cdedadfef7381939577858b1b5718a4ab073adbb584e429dd9d9dc9bfe16/websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c", size = 161007 }, - { url = "https://files.pythonhosted.org/packages/ca/35/7a20a3c450b27c04e50fbbfc3dfb161ed8e827b2a26ae31c4b59b018b8c6/websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2", size = 162264 }, - { url = "https://files.pythonhosted.org/packages/e8/9c/e3f9600564b0c813f2448375cf28b47dc42c514344faed3a05d71fb527f9/websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c", size = 161873 }, - { url = "https://files.pythonhosted.org/packages/3f/37/260f189b16b2b8290d6ae80c9f96d8b34692cf1bb3475df54c38d3deb57d/websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a", size = 161818 }, - { url = "https://files.pythonhosted.org/packages/ff/1e/e47dedac8bf7140e59aa6a679e850c4df9610ae844d71b6015263ddea37b/websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3", size = 164465 }, - { url = "https://files.pythonhosted.org/packages/f7/c0/8e9325c4987dcf66d4a0d63ec380d4aefe8dcc1e521af71ad17adf2c1ae2/websockets-14.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bbe03eb853e17fd5b15448328b4ec7fb2407d45fb0245036d06a3af251f8e48f", size = 160773 }, - { url = "https://files.pythonhosted.org/packages/5a/6e/c9a7f2edd4afddc4f8cccfc4e12468b7f6ec40f28d1b1e966a8d0298b875/websockets-14.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3c4aa3428b904d5404a0ed85f3644d37e2cb25996b7f096d77caeb0e96a3b42", size = 161006 }, - { url = "https://files.pythonhosted.org/packages/f3/10/b90ece894828c954e674a81cb0db250e6c324c54db30a8b19e96431f928f/websockets-14.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a4cebf1ceaf0b65ffc42c54856214165fb8ceeba3935852fc33f6b0c55e7f", size = 162260 }, - { url = "https://files.pythonhosted.org/packages/52/93/1147b6b5464a5fb6e8987da3ec7991dcc44f9090f67d9c841d7382fed429/websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad1c1d02357b7665e700eca43a31d52814ad9ad9b89b58118bdabc365454b574", size = 161868 }, - { url = "https://files.pythonhosted.org/packages/32/ab/f7d80b4049bff0aa617507330db3a27389d0e70df54e29f7a3d76bbd2086/websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f390024a47d904613577df83ba700bd189eedc09c57af0a904e5c39624621270", size = 161813 }, - { url = "https://files.pythonhosted.org/packages/cd/cc/adc9fb85f031b8df8e9f3d96cc004df25d2643e503953af5223c5b6825b7/websockets-14.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c1426c021c38cf92b453cdf371228d3430acd775edee6bac5a4d577efc72365", size = 164457 }, - { url = "https://files.pythonhosted.org/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416 }, -] - -[[package]] -name = "zipp" -version = "3.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, -] |
