diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/README.md | 190 | ||||
| -rw-r--r-- | examples/basic_usage.py | 102 | ||||
| -rw-r--r-- | examples/custom_plugin.py | 154 | ||||
| -rw-r--r-- | examples/output/session_output.html | 1 | ||||
| -rw-r--r-- | examples/output/session_output.json | 121 | ||||
| -rw-r--r-- | examples/output/session_output.md | 87 | ||||
| -rw-r--r-- | examples/rules/dnd5e_rules.json5 | 78 |
7 files changed, 697 insertions, 36 deletions
diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..d823f5b --- /dev/null +++ b/examples/README.md @@ -0,0 +1,190 @@ +# 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 7a4e53d..c327cb0 100644 --- a/examples/basic_usage.py +++ b/examples/basic_usage.py @@ -1,32 +1,94 @@ +#!/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")) + from conventionalrp.core.parser import Parser from conventionalrp.core.processor import Processor from conventionalrp.extractors.rule_extractor import RuleExtractor from conventionalrp.renderers.html_renderer import HTMLRenderer +from conventionalrp.renderers.json_renderer import JSONRenderer +from conventionalrp.renderers.markdown_renderer import MarkdownRenderer def main(): - # Initialize the parser and load rules + # 获取示例文件路径 + 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("path/to/rules.json") - - # Parse the TRPG log - log_data = "Your TRPG log data here" - parsed_tokens = parser.parse_log(log_data) - - # Initialize the rule extractor - extractor = RuleExtractor() - rules = extractor.extract("path/to/rules.json") - - # Process the parsed tokens + parser.load_rules(str(rules_file)) + print(f"✓ 规则加载成功: {rules_file.name}") + + # 步骤 2: 解析日志 + print("\n[步骤 2] 解析 TRPG 日志...") + parsed_data = parser.parse_log(str(log_file)) + print(f"✓ 日志解析完成,共 {len(parsed_data)} 条记录") + + # 步骤 3: 处理解析结果 + print("\n[步骤 3] 处理解析后的数据...") processor = Processor() - processed_data = processor.process_tokens(parsed_tokens, rules) - - # Render the output in HTML format - renderer = HTMLRenderer() - output = renderer.render(processed_data) - - # Print or save the output - print(output) + processed_data = processor.process_tokens(parsed_data) + print(f"✓ 数据处理完成") + + # 步骤 4: 渲染输出 + print("\n[步骤 4] 渲染输出...") + + # JSON 格式 + json_renderer = JSONRenderer() + json_output = json_renderer.render(processed_data) + json_file = example_dir / "output" / "session_output.json" + 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}") + + # HTML 格式 + html_renderer = HTMLRenderer() + html_output = html_renderer.render(processed_data) + 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}") + + # 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) if __name__ == "__main__": diff --git a/examples/custom_plugin.py b/examples/custom_plugin.py index ecb9e71..dd96311 100644 --- a/examples/custom_plugin.py +++ b/examples/custom_plugin.py @@ -1,27 +1,149 @@ -from conventionalrp.plugins.plugin_manager import PluginManager +#!/usr/bin/env python3 +""" +自定义插件示例 +演示如何创建和使用自定义插件来扩展 ConventionalRP 的功能 +""" +import sys +from typing import List, Dict, Any +from pathlib import Path -class CustomPlugin: - def __init__(self): - self.name = "Custom Plugin" +# 添加 src 目录到 Python 路径 +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root / "src")) - def process(self, data): - # Custom processing logic - processed_data = data.upper() # Example transformation - return processed_data +from conventionalrp.core.parser import Parser +from conventionalrp.core.processor import Processor -def main(): - plugin_manager = PluginManager() - custom_plugin = CustomPlugin() +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": {}, + "dice_types": {} + } + + for entry in parsed_data: + speaker = entry.get("speaker", "Unknown") + for content in entry.get("content", []): + if content.get("type") == "dice_roll": + stats["total_rolls"] += 1 + + # 按角色统计 + if speaker not in stats["by_character"]: + stats["by_character"][speaker] = 0 + stats["by_character"][speaker] += 1 + + # 按骰子类型统计 + dice_type = content.get("dice_type", "unknown") + if dice_type not in stats["dice_types"]: + stats["dice_types"][dice_type] = 0 + stats["dice_types"][dice_type] += 1 + + return stats - plugin_manager.register_plugin(custom_plugin) - # Example data to process - data = "This is a sample TRPG log." - result = custom_plugin.process(data) +class DialogueExtractor: + """对话提取插件""" + + def __init__(self): + 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: + speaker = entry.get("speaker", "Unknown") + timestamp = entry.get("timestamp", "") + + for content in entry.get("content", []): + if content.get("type") == "dialogue": + dialogues.append({ + "speaker": speaker, + "timestamp": timestamp, + "dialogue": content.get("dialogue_text", content.get("content", "")) + }) + + return dialogues + - print(f"Processed Data: {result}") +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] 解析日志...") + parser = Parser() + parser.load_rules(str(rules_file)) + parsed_data = parser.parse_log(str(log_file)) + print(f"✓ 解析完成,共 {len(parsed_data)} 条记录") + + # 使用骰子分析插件 + print("\n[2] 运行骰子统计分析插件...") + dice_analyzer = DiceRollAnalyzer() + dice_stats = dice_analyzer.analyze(parsed_data) + + print(f"\n骰子统计结果:") + 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 按骰子类型统计:") + for dice_type, count in dice_stats['dice_types'].items(): + print(f" d{dice_type}: {count} 次") + + # 使用对话提取插件 + 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"\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(" - ... 以及更多!") if __name__ == "__main__": diff --git a/examples/output/session_output.html b/examples/output/session_output.html new file mode 100644 index 0000000..461a59f --- /dev/null +++ b/examples/output/session_output.html @@ -0,0 +1 @@ +<html><head><title>TRPG Log Output</title></head><body><h1>TRPG Log Output</h1><ul><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:01', 'speaker': '艾莉娅', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:05', 'speaker': '艾莉娅', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:05', 'speaker': 'DiceBot', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:15', 'speaker': 'DM', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:30', 'speaker': '艾莉娅', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:35', 'speaker': '艾莉娅', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:35', 'speaker': 'DiceBot', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:30:45', 'speaker': 'DM', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:00', 'speaker': '索恩', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:10', 'speaker': 'DM', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:25', 'speaker': '索恩', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:30', 'speaker': '艾莉娅', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:45', 'speaker': '莉莉安', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:50', 'speaker': 'DM', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:55', 'speaker': '莉莉安', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:31:55', 'speaker': 'DiceBot', 'content': [], 'processed': True}</li><li>{'type': 'metadata', 'timestamp': '2025-10-24 14:32:10', 'speaker': 'DM', 'content': [], 'processed': True}</li></ul></body></html>
\ No newline at end of file diff --git a/examples/output/session_output.json b/examples/output/session_output.json new file mode 100644 index 0000000..076ef15 --- /dev/null +++ b/examples/output/session_output.json @@ -0,0 +1,121 @@ +[ + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:01", + "speaker": "艾莉娅", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:05", + "speaker": "艾莉娅", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:05", + "speaker": "DiceBot", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:15", + "speaker": "DM", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:30", + "speaker": "艾莉娅", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:35", + "speaker": "艾莉娅", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:35", + "speaker": "DiceBot", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:30:45", + "speaker": "DM", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:00", + "speaker": "索恩", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:10", + "speaker": "DM", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:25", + "speaker": "索恩", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:30", + "speaker": "艾莉娅", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:45", + "speaker": "莉莉安", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:50", + "speaker": "DM", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:55", + "speaker": "莉莉安", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:31:55", + "speaker": "DiceBot", + "content": [], + "processed": true + }, + { + "type": "metadata", + "timestamp": "2025-10-24 14:32:10", + "speaker": "DM", + "content": [], + "processed": true + } +]
\ No newline at end of file diff --git a/examples/output/session_output.md b/examples/output/session_output.md new file mode 100644 index 0000000..6f4a09b --- /dev/null +++ b/examples/output/session_output.md @@ -0,0 +1,87 @@ +# TRPG Log + +## Entry 1 + +**Timestamp**: 2025-10-24 14:30:01 +**Speaker**: 艾莉娅 + +## Entry 2 + +**Timestamp**: 2025-10-24 14:30:05 +**Speaker**: 艾莉娅 + +## Entry 3 + +**Timestamp**: 2025-10-24 14:30:05 +**Speaker**: DiceBot + +## Entry 4 + +**Timestamp**: 2025-10-24 14:30:15 +**Speaker**: DM + +## Entry 5 + +**Timestamp**: 2025-10-24 14:30:30 +**Speaker**: 艾莉娅 + +## Entry 6 + +**Timestamp**: 2025-10-24 14:30:35 +**Speaker**: 艾莉娅 + +## Entry 7 + +**Timestamp**: 2025-10-24 14:30:35 +**Speaker**: DiceBot + +## Entry 8 + +**Timestamp**: 2025-10-24 14:30:45 +**Speaker**: DM + +## Entry 9 + +**Timestamp**: 2025-10-24 14:31:00 +**Speaker**: 索恩 + +## Entry 10 + +**Timestamp**: 2025-10-24 14:31:10 +**Speaker**: DM + +## Entry 11 + +**Timestamp**: 2025-10-24 14:31:25 +**Speaker**: 索恩 + +## Entry 12 + +**Timestamp**: 2025-10-24 14:31:30 +**Speaker**: 艾莉娅 + +## Entry 13 + +**Timestamp**: 2025-10-24 14:31:45 +**Speaker**: 莉莉安 + +## Entry 14 + +**Timestamp**: 2025-10-24 14:31:50 +**Speaker**: DM + +## Entry 15 + +**Timestamp**: 2025-10-24 14:31:55 +**Speaker**: 莉莉安 + +## Entry 16 + +**Timestamp**: 2025-10-24 14:31:55 +**Speaker**: DiceBot + +## Entry 17 + +**Timestamp**: 2025-10-24 14:32:10 +**Speaker**: DM + diff --git a/examples/rules/dnd5e_rules.json5 b/examples/rules/dnd5e_rules.json5 new file mode 100644 index 0000000..96f7cd1 --- /dev/null +++ b/examples/rules/dnd5e_rules.json5 @@ -0,0 +1,78 @@ +{ + // D&D 5E TRPG 日志解析规则 + metadata: [ + { + type: "metadata", + patterns: [ + "^\\[(.+?)\\]\\s*<(.+?)>\\s*(.*)$", // [时间] <角色名> 内容 + "^(.+?)\\s*\\|\\s*(.+?)\\s*:\\s*(.*)$" // 时间 | 角色名: 内容 + ], + groups: ["timestamp", "speaker", "content"], + priority: 100 + } + ], + + content: [ + { + type: "dice_roll", + match_type: "enclosed", + patterns: [ + "\\[d(\\d+)\\s*=\\s*(\\d+)\\]", // [d20 = 15] + "\\.r(\\d*)d(\\d+)(?:[+\\-](\\d+))?", // .r1d20+5 + "\\((\\d+)d(\\d+)(?:[+\\-](\\d+))?\\s*=\\s*(\\d+)\\)" // (1d20+5 = 18) + ], + groups: ["dice_type", "result"], + priority: 90 + }, + { + type: "action", + match_type: "enclosed", + patterns: [ + "\\*\\*(.+?)\\*\\*", // **动作** + "\\*(.+?)\\*" // *动作* + ], + groups: ["action_text"], + priority: 80 + }, + { + type: "ooc", + match_type: "enclosed", + patterns: [ + "\\(\\((.+?)\\)\\)", // ((OOC对话)) + "//(.+?)$" // //OOC注释 + ], + groups: ["ooc_text"], + priority: 70 + }, + { + type: "dialogue", + match_type: "enclosed", + patterns: [ + "「(.+?)」", + "\u201c(.+?)\u201d", + "\"(.+?)\"" + ], + groups: ["dialogue_text"], + priority: 60 + }, + { + type: "system", + match_type: "prefix", + patterns: [ + "^\\[系统\\](.+)", + "^\\[System\\](.+)" + ], + groups: ["system_message"], + priority: 50 + }, + { + type: "text", + match_type: "prefix", + patterns: [ + "^(.+)$" + ], + groups: ["text_content"], + priority: 1 + } + ] +} |
