actr_cli/commands/
doc.rs

1//! Doc command implementation - generate project documentation
2
3use crate::commands::Command;
4use crate::error::Result;
5use actr_config::{Config, ConfigParser};
6use async_trait::async_trait;
7use clap::Args;
8use std::path::Path;
9use tracing::{debug, info};
10
11#[derive(Args)]
12pub struct DocCommand {
13    /// Output directory for documentation (defaults to "./docs")
14    #[arg(short = 'o', long = "output")]
15    pub output_dir: Option<String>,
16}
17
18#[async_trait]
19impl Command for DocCommand {
20    async fn execute(&self) -> Result<()> {
21        let output_dir = self.output_dir.as_deref().unwrap_or("docs");
22
23        info!("📚 Generating project documentation to: {}", output_dir);
24
25        // Create output directory
26        std::fs::create_dir_all(output_dir)?;
27
28        // Load project configuration
29        let config = if Path::new("Actr.toml").exists() {
30            Some(ConfigParser::from_file("Actr.toml")?)
31        } else {
32            None
33        };
34
35        // Generate documentation files
36        self.generate_index_html(output_dir, &config).await?;
37        self.generate_api_html(output_dir, &config).await?;
38        self.generate_config_html(output_dir, &config).await?;
39
40        info!("✅ Documentation generated successfully");
41        info!("📄 Generated files:");
42        info!("  - {}/index.html (project overview)", output_dir);
43        info!("  - {}/api.html (API interface documentation)", output_dir);
44        info!(
45            "  - {}/config.html (configuration documentation)",
46            output_dir
47        );
48
49        Ok(())
50    }
51}
52
53impl DocCommand {
54    /// Generate project overview documentation
55    async fn generate_index_html(&self, output_dir: &str, config: &Option<Config>) -> Result<()> {
56        debug!("Generating index.html...");
57
58        let project_name = config
59            .as_ref()
60            .map(|c| c.package.name.as_str())
61            .unwrap_or("Actor-RTC Project");
62        // Note: package.version doesn't exist in new API, use default or read from Cargo.toml
63        let project_version = "0.1.0";
64        let project_description = config
65            .as_ref()
66            .and_then(|c| c.package.description.as_ref())
67            .map(|s| s.as_str())
68            .unwrap_or("An Actor-RTC project");
69
70        let html_content = format!(
71            r#"<!DOCTYPE html>
72<html lang="zh">
73<head>
74    <meta charset="UTF-8">
75    <meta name="viewport" content="width=device-width, initial-scale=1.0">
76    <title>{project_name} - 项目概览</title>
77    <style>
78        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; line-height: 1.6; }}
79        .header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
80        .content {{ max-width: 800px; margin: 0 auto; }}
81        .section {{ background: white; padding: 20px; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
82        .nav {{ display: flex; gap: 10px; margin: 20px 0; }}
83        .nav a {{ padding: 10px 20px; background: #f0f0f0; text-decoration: none; color: #333; border-radius: 4px; }}
84        .nav a:hover {{ background: #667eea; color: white; }}
85        h1, h2 {{ margin-top: 0; }}
86        .badge {{ background: #667eea; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.8em; }}
87    </style>
88</head>
89<body>
90    <div class="content">
91        <div class="header">
92            <h1>{project_name}</h1>
93            <p>{project_description}</p>
94            <span class="badge">v{project_version}</span>
95        </div>
96        
97        <div class="nav">
98            <a href="index.html">项目概览</a>
99            <a href="api.html">API 文档</a>
100            <a href="config.html">配置说明</a>
101        </div>
102        
103        <div class="section">
104            <h2>📋 项目信息</h2>
105            <p><strong>名称:</strong> {project_name}</p>
106            <p><strong>版本:</strong> {project_version}</p>
107            <p><strong>描述:</strong> {project_description}</p>
108        </div>
109        
110        <div class="section">
111            <h2>🚀 快速开始</h2>
112            <p>这是一个基于 Actor-RTC 框架的项目。以下是一些常用的开发命令:</p>
113            <pre><code># 生成代码
114actr gen --input proto --output src/generated
115
116# 运行项目
117actr run
118
119# 安装依赖
120actr install
121
122# 检查配置
123actr check</code></pre>
124        </div>
125        
126        <div class="section">
127            <h2>📁 项目结构</h2>
128            <pre><code>{project_name}/ 
129├── Actr.toml          # 项目配置文件
130├── src/               # 源代码目录
131│   ├── main.rs        # 程序入口点
132│   └── generated/     # 自动生成的代码
133├── proto/             # Protocol Buffers 定义
134└── docs/              # 项目文档</code></pre>
135        </div>
136        
137        <div class="section">
138            <h2>🔗 相关链接</h2>
139            <ul>
140                <li><a href="api.html">API 接口文档</a> - 查看服务接口定义</li>
141                <li><a href="config.html">配置说明</a> - 了解项目配置选项</li>
142            </ul>
143        </div>
144    </div>
145</body>
146</html>"#
147        );
148
149        let index_path = Path::new(output_dir).join("index.html");
150        std::fs::write(index_path, html_content)?;
151
152        Ok(())
153    }
154
155    /// Generate API documentation
156    async fn generate_api_html(&self, output_dir: &str, config: &Option<Config>) -> Result<()> {
157        debug!("Generating api.html...");
158
159        let project_name = config
160            .as_ref()
161            .map(|c| c.package.name.as_str())
162            .unwrap_or("Actor-RTC Project");
163
164        // Collect proto files information
165        let mut proto_info = Vec::new();
166        let proto_dir = Path::new("proto");
167
168        if proto_dir.exists() {
169            if let Ok(entries) = std::fs::read_dir(proto_dir) {
170                for entry in entries.flatten() {
171                    let path = entry.path();
172                    if path.extension().and_then(|s| s.to_str()) == Some("proto") {
173                        let filename = path.file_name().unwrap().to_string_lossy();
174                        let content = std::fs::read_to_string(&path).unwrap_or_default();
175                        proto_info.push((filename.to_string(), content));
176                    }
177                }
178            }
179        }
180
181        let mut proto_sections = String::new();
182        if proto_info.is_empty() {
183            proto_sections.push_str(
184                r#"<div class="section">
185                <p>暂无 Protocol Buffers 定义文件。</p>
186            </div>"#,
187            );
188        } else {
189            for (filename, content) in proto_info {
190                proto_sections.push_str(&format!(
191                    r#"<div class="section">
192                    <h3>📄 {}</h3>
193                    <pre><code>{}</code></pre>
194                </div>"#,
195                    filename,
196                    Self::html_escape(&content)
197                ));
198            }
199        }
200
201        let html_content = format!(
202            r#"<!DOCTYPE html>
203<html lang="zh">
204<head>
205    <meta charset="UTF-8">
206    <meta name="viewport" content="width=device-width, initial-scale=1.0">
207    <title>{project_name} - API 文档</title>
208    <style>
209        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; line-height: 1.6; }}
210        .header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
211        .content {{ max-width: 1000px; margin: 0 auto; }}
212        .section {{ background: white; padding: 20px; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
213        .nav {{ display: flex; gap: 10px; margin: 20px 0; }}
214        .nav a {{ padding: 10px 20px; background: #f0f0f0; text-decoration: none; color: #333; border-radius: 4px; }}
215        .nav a:hover {{ background: #667eea; color: white; }}
216        .nav a.active {{ background: #667eea; color: white; }}
217        h1, h2, h3 {{ margin-top: 0; }}
218        pre {{ background: #f5f5f5; padding: 15px; border-radius: 4px; overflow-x: auto; }}
219        code {{ font-family: 'Monaco', 'Consolas', monospace; }}
220    </style>
221</head>
222<body>
223    <div class="content">
224        <div class="header">
225            <h1>{project_name} - API 接口文档</h1>
226            <p>服务接口定义和协议规范</p>
227        </div>
228        
229        <div class="nav">
230            <a href="index.html">项目概览</a>
231            <a href="api.html" class="active">API 文档</a>
232            <a href="config.html">配置说明</a>
233        </div>
234        
235        <div class="section">
236            <h2>📋 Protocol Buffers 定义</h2>
237            <p>以下是项目中定义的 Protocol Buffers 文件:</p>
238        </div>
239        
240        {proto_sections}
241    </div>
242</body>
243</html>"#
244        );
245
246        let api_path = Path::new(output_dir).join("api.html");
247        std::fs::write(api_path, html_content)?;
248
249        Ok(())
250    }
251
252    /// Generate configuration documentation
253    async fn generate_config_html(&self, output_dir: &str, config: &Option<Config>) -> Result<()> {
254        debug!("Generating config.html...");
255
256        let project_name = config
257            .as_ref()
258            .map(|c| c.package.name.as_str())
259            .unwrap_or("Actor-RTC Project");
260
261        // Generate configuration example
262        // Note: Config doesn't implement Serialize, read raw Actr.toml instead
263        let config_example = if Path::new("Actr.toml").exists() {
264            std::fs::read_to_string("Actr.toml").unwrap_or_default()
265        } else {
266            r#"[project]
267name = "my-actor-service"
268version = "0.1.0"
269description = "An example Actor-RTC service"
270
271[build]
272output_dir = "generated"
273
274[dependencies]
275# Add your proto dependencies here
276
277[system.signaling]
278url = "ws://localhost:8081"
279
280[scripts]
281run = "cargo run"
282build = "cargo build"
283test = "cargo test""#
284                .to_string()
285        };
286
287        let html_content = format!(
288            r#"<!DOCTYPE html>
289<html lang="zh">
290<head>
291    <meta charset="UTF-8">
292    <meta name="viewport" content="width=device-width, initial-scale=1.0">
293    <title>{} - 配置说明</title>
294    <style>
295        body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; line-height: 1.6; }}
296        .header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
297        .content {{ max-width: 1000px; margin: 0 auto; }}
298        .section {{ background: white; padding: 20px; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
299        .nav {{ display: flex; gap: 10px; margin: 20px 0; }}
300        .nav a {{ padding: 10px 20px; background: #f0f0f0; text-decoration: none; color: #333; border-radius: 4px; }}
301        .nav a:hover {{ background: #667eea; color: white; }}
302        .nav a.active {{ background: #667eea; color: white; }}
303        h1, h2, h3 {{ margin-top: 0; }}
304        pre {{ background: #f5f5f5; padding: 15px; border-radius: 4px; overflow-x: auto; }}
305        code {{ font-family: 'Monaco', 'Consolas', monospace; background: #f0f0f0; padding: 2px 4px; border-radius: 2px; }}
306        .config-table {{ width: 100%; border-collapse: collapse; margin: 15px 0; }}
307        .config-table th, .config-table td {{ border: 1px solid #ddd; padding: 12px; text-align: left; }}
308        .config-table th {{ background: #f5f5f5; font-weight: bold; }}
309    </style>
310</head>
311<body>
312    <div class="content">
313        <div class="header">
314            <h1>{} - 配置说明</h1>
315            <p>项目配置选项和使用说明</p>
316        </div>
317        
318        <div class="nav">
319            <a href="index.html">项目概览</a>
320            <a href="api.html">API 文档</a>
321            <a href="config.html" class="active">配置说明</a>
322        </div>
323        
324        <div class="section">
325            <h2>📋 配置文件结构</h2>
326            <p><code>Actr.toml</code> 是项目的核心配置文件,包含以下主要部分:</p>
327            
328            <table class="config-table">
329                <tr>
330                    <th>配置段</th>
331                    <th>作用</th>
332                    <th>必需</th>
333                </tr>
334                <tr>
335                    <td><code>[project]</code></td>
336                    <td>项目基本信息(名称、版本、描述等)</td>
337                    <td>是</td>
338                </tr>
339                <tr>
340                    <td><code>[build]</code></td>
341                    <td>构建配置(输出目录等)</td>
342                    <td>是</td>
343                </tr>
344                <tr>
345                    <td><code>[dependencies]</code></td>
346                    <td>Protocol Buffers 依赖定义</td>
347                    <td>否</td>
348                </tr>
349                <tr>
350                    <td><code>[system.signaling]</code></td>
351                    <td>信令服务器配置</td>
352                    <td>否</td>
353                </tr>
354                <tr>
355                    <td><code>[system.routing]</code></td>
356                    <td>高级路由规则配置</td>
357                    <td>否</td>
358                </tr>
359                <tr>
360                    <td><code>[scripts]</code></td>
361                    <td>自定义脚本命令</td>
362                    <td>否</td>
363                </tr>
364            </table>
365        </div>
366        
367        <div class="section">
368            <h2>⚙️ 配置示例</h2>
369            <pre><code>{}</code></pre>
370        </div>
371        
372        <div class="section">
373            <h2>🔧 配置管理命令</h2>
374            <p>使用 <code>actr config</code> 命令可以方便地管理项目配置:</p>
375            <pre><code># 设置配置值
376actr config set project.description "我的Actor服务"
377actr config set system.signaling.url "wss://signal.example.com"
378
379# 查看配置值
380actr config get project.name
381actr config list
382
383# 查看完整配置
384actr config show
385
386# 删除配置项
387actr config unset system.signaling.url</code></pre>
388        </div>
389        
390        <div class="section">
391            <h2>📝 依赖配置</h2>
392            <p>在 <code>[dependencies]</code> 段中配置 Protocol Buffers 依赖:</p>
393            <pre><code># 本地文件路径
394user_service = "proto/user.proto"
395
396# HTTP URL
397api_service = "https://example.com/api/service.proto"
398
399# Actor 注册表 
400[dependencies.payment]
401uri = "actr://payment-service/payment.proto"
402fingerprint = "sha256:a1b2c3d4..."</code></pre>
403        </div>
404    </div>
405</body>
406</html>"#,
407            project_name,
408            project_name,
409            Self::html_escape(&config_example)
410        );
411
412        let config_path = Path::new(output_dir).join("config.html");
413        std::fs::write(config_path, html_content)?;
414
415        Ok(())
416    }
417
418    /// Simple HTML escape function
419    fn html_escape(text: &str) -> String {
420        text.replace("&", "&amp;")
421            .replace("<", "&lt;")
422            .replace(">", "&gt;")
423            .replace("\"", "&quot;")
424            .replace("'", "&#x27;")
425    }
426}