ricecoder_github/managers/
documentation_operations.rs

1//! Documentation Operations - Publishing and maintenance operations
2
3use crate::errors::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use tracing::{debug, info};
7
8/// Documentation commit information
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct DocumentationCommit {
11    /// Commit message
12    pub message: String,
13    /// Files changed
14    pub files: Vec<String>,
15    /// Commit hash (after creation)
16    pub hash: Option<String>,
17    /// Commit timestamp
18    pub timestamp: Option<String>,
19}
20
21impl DocumentationCommit {
22    /// Create a new documentation commit
23    pub fn new(message: impl Into<String>) -> Self {
24        Self {
25            message: message.into(),
26            files: Vec::new(),
27            hash: None,
28            timestamp: None,
29        }
30    }
31
32    /// Add a file to the commit
33    pub fn with_file(mut self, file: impl Into<String>) -> Self {
34        self.files.push(file.into());
35        self
36    }
37
38    /// Add files to the commit
39    pub fn with_files(mut self, files: Vec<String>) -> Self {
40        self.files.extend(files);
41        self
42    }
43
44    /// Set commit hash
45    pub fn with_hash(mut self, hash: impl Into<String>) -> Self {
46        self.hash = Some(hash.into());
47        self
48    }
49
50    /// Set commit timestamp
51    pub fn with_timestamp(mut self, timestamp: impl Into<String>) -> Self {
52        self.timestamp = Some(timestamp.into());
53        self
54    }
55}
56
57/// Documentation template
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct DocumentationTemplate {
60    /// Template name
61    pub name: String,
62    /// Template content
63    pub content: String,
64    /// Template variables (placeholders)
65    pub variables: Vec<String>,
66}
67
68impl DocumentationTemplate {
69    /// Create a new documentation template
70    pub fn new(name: impl Into<String>, content: impl Into<String>) -> Self {
71        Self {
72            name: name.into(),
73            content: content.into(),
74            variables: Vec::new(),
75        }
76    }
77
78    /// Add a variable
79    pub fn with_variable(mut self, var: impl Into<String>) -> Self {
80        self.variables.push(var.into());
81        self
82    }
83
84    /// Render template with variables
85    pub fn render(&self, values: &HashMap<String, String>) -> Result<String> {
86        let mut result = self.content.clone();
87
88        for (key, value) in values {
89            let placeholder = format!("{{{{{}}}}}", key);
90            result = result.replace(&placeholder, value);
91        }
92
93        Ok(result)
94    }
95}
96
97/// Documentation maintenance task
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct MaintenanceTask {
100    /// Task name
101    pub name: String,
102    /// Task description
103    pub description: String,
104    /// Files affected
105    pub files: Vec<String>,
106    /// Task status
107    pub status: MaintenanceStatus,
108    /// Completion percentage (0-100)
109    pub progress: u32,
110}
111
112impl MaintenanceTask {
113    /// Create a new maintenance task
114    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
115        Self {
116            name: name.into(),
117            description: description.into(),
118            files: Vec::new(),
119            status: MaintenanceStatus::Pending,
120            progress: 0,
121        }
122    }
123
124    /// Add a file
125    pub fn with_file(mut self, file: impl Into<String>) -> Self {
126        self.files.push(file.into());
127        self
128    }
129
130    /// Set status
131    pub fn with_status(mut self, status: MaintenanceStatus) -> Self {
132        self.status = status;
133        self
134    }
135
136    /// Set progress
137    pub fn with_progress(mut self, progress: u32) -> Self {
138        self.progress = progress.min(100);
139        self
140    }
141}
142
143/// Maintenance task status
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
145#[serde(rename_all = "lowercase")]
146pub enum MaintenanceStatus {
147    /// Task is pending
148    Pending,
149    /// Task is in progress
150    InProgress,
151    /// Task is completed
152    Completed,
153    /// Task failed
154    Failed,
155}
156
157/// Documentation publishing result
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct PublishingResult {
160    /// Publishing successful
161    pub success: bool,
162    /// Commit hash
163    pub commit_hash: Option<String>,
164    /// Files published
165    pub files_published: Vec<String>,
166    /// Error message if failed
167    pub error: Option<String>,
168}
169
170impl PublishingResult {
171    /// Create a successful publishing result
172    pub fn success(commit_hash: impl Into<String>) -> Self {
173        Self {
174            success: true,
175            commit_hash: Some(commit_hash.into()),
176            files_published: Vec::new(),
177            error: None,
178        }
179    }
180
181    /// Create a failed publishing result
182    pub fn failure(error: impl Into<String>) -> Self {
183        Self {
184            success: false,
185            commit_hash: None,
186            files_published: Vec::new(),
187            error: Some(error.into()),
188        }
189    }
190
191    /// Add a published file
192    pub fn with_file(mut self, file: impl Into<String>) -> Self {
193        self.files_published.push(file.into());
194        self
195    }
196}
197
198/// Documentation Operations
199#[derive(Debug, Clone)]
200pub struct DocumentationOperations {
201    /// Documentation templates
202    pub templates: HashMap<String, DocumentationTemplate>,
203    /// Maintenance tasks
204    pub maintenance_tasks: HashMap<String, MaintenanceTask>,
205    /// Commit history
206    pub commit_history: Vec<DocumentationCommit>,
207}
208
209impl DocumentationOperations {
210    /// Create new documentation operations
211    pub fn new() -> Self {
212        Self {
213            templates: HashMap::new(),
214            maintenance_tasks: HashMap::new(),
215            commit_history: Vec::new(),
216        }
217    }
218
219    /// Commit documentation updates to repository
220    pub fn commit_documentation(&mut self, commit: DocumentationCommit) -> Result<PublishingResult> {
221        debug!("Committing documentation: {}", commit.message);
222
223        if commit.files.is_empty() {
224            return Ok(PublishingResult::failure("No files to commit"));
225        }
226
227        // Simulate commit creation
228        let commit_hash = format!("commit_{}", self.commit_history.len() + 1);
229        let mut result = PublishingResult::success(&commit_hash);
230
231        for file in &commit.files {
232            result = result.with_file(file.clone());
233        }
234
235        // Store in history
236        let mut stored_commit = commit;
237        stored_commit.hash = Some(commit_hash);
238        self.commit_history.push(stored_commit);
239
240        info!("Documentation committed successfully");
241        Ok(result)
242    }
243
244    /// Track documentation coverage and gaps
245    pub fn track_coverage(&mut self, task: MaintenanceTask) -> Result<()> {
246        debug!("Tracking documentation coverage: {}", task.name);
247
248        self.maintenance_tasks.insert(task.name.clone(), task);
249
250        info!("Documentation coverage tracked");
251        Ok(())
252    }
253
254    /// Support documentation templates
255    pub fn add_template(&mut self, template: DocumentationTemplate) -> Result<()> {
256        debug!("Adding documentation template: {}", template.name);
257
258        self.templates.insert(template.name.clone(), template);
259
260        info!("Documentation template added");
261        Ok(())
262    }
263
264    /// Get template by name
265    pub fn get_template(&self, name: &str) -> Option<&DocumentationTemplate> {
266        self.templates.get(name)
267    }
268
269    /// Render template with values
270    pub fn render_template(&self, name: &str, values: &HashMap<String, String>) -> Result<String> {
271        debug!("Rendering template: {}", name);
272
273        let template = self
274            .templates
275            .get(name)
276            .ok_or_else(|| crate::errors::GitHubError::NotFound(format!("Template '{}' not found", name)))?;
277
278        template.render(values)
279    }
280
281    /// Get all maintenance tasks
282    pub fn get_maintenance_tasks(&self) -> Vec<&MaintenanceTask> {
283        self.maintenance_tasks.values().collect()
284    }
285
286    /// Get maintenance task by name
287    pub fn get_maintenance_task(&self, name: &str) -> Option<&MaintenanceTask> {
288        self.maintenance_tasks.get(name)
289    }
290
291    /// Update maintenance task status
292    pub fn update_task_status(&mut self, name: &str, status: MaintenanceStatus) -> Result<()> {
293        debug!("Updating maintenance task status: {} -> {:?}", name, status);
294
295        if let Some(task) = self.maintenance_tasks.get_mut(name) {
296            task.status = status;
297            info!("Maintenance task status updated");
298            Ok(())
299        } else {
300            Err(crate::errors::GitHubError::NotFound(format!("Task '{}' not found", name)))
301        }
302    }
303
304    /// Get commit history
305    pub fn get_commit_history(&self) -> &[DocumentationCommit] {
306        &self.commit_history
307    }
308
309    /// Get latest commit
310    pub fn get_latest_commit(&self) -> Option<&DocumentationCommit> {
311        self.commit_history.last()
312    }
313}
314
315impl Default for DocumentationOperations {
316    fn default() -> Self {
317        Self::new()
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn test_documentation_commit_builder() {
327        let commit = DocumentationCommit::new("Update docs")
328            .with_file("README.md")
329            .with_file("API.md")
330            .with_hash("abc123")
331            .with_timestamp("2025-01-01T00:00:00Z");
332
333        assert_eq!(commit.message, "Update docs");
334        assert_eq!(commit.files.len(), 2);
335        assert_eq!(commit.hash, Some("abc123".to_string()));
336    }
337
338    #[test]
339    fn test_documentation_template_rendering() {
340        let mut values = HashMap::new();
341        values.insert("project".to_string(), "MyProject".to_string());
342        values.insert("version".to_string(), "1.0.0".to_string());
343
344        let template = DocumentationTemplate::new(
345            "readme",
346            "# {{project}}\n\nVersion: {{version}}",
347        );
348
349        let rendered = template.render(&values).expect("Failed to render");
350        assert!(rendered.contains("# MyProject"));
351        assert!(rendered.contains("Version: 1.0.0"));
352    }
353
354    #[test]
355    fn test_maintenance_task_builder() {
356        let task = MaintenanceTask::new("Update API docs", "Update API documentation")
357            .with_file("API.md")
358            .with_status(MaintenanceStatus::InProgress)
359            .with_progress(50);
360
361        assert_eq!(task.name, "Update API docs");
362        assert_eq!(task.status, MaintenanceStatus::InProgress);
363        assert_eq!(task.progress, 50);
364    }
365
366    #[test]
367    fn test_documentation_operations_commit() {
368        let mut ops = DocumentationOperations::new();
369        let commit = DocumentationCommit::new("Initial commit")
370            .with_file("README.md");
371
372        let result = ops.commit_documentation(commit).expect("Failed to commit");
373        assert!(result.success);
374        assert!(result.commit_hash.is_some());
375        assert_eq!(ops.commit_history.len(), 1);
376    }
377
378    #[test]
379    fn test_documentation_operations_template() {
380        let mut ops = DocumentationOperations::new();
381        let template = DocumentationTemplate::new("test", "Hello {{name}}");
382
383        ops.add_template(template).expect("Failed to add template");
384        assert!(ops.get_template("test").is_some());
385    }
386
387    #[test]
388    fn test_maintenance_task_tracking() {
389        let mut ops = DocumentationOperations::new();
390        let task = MaintenanceTask::new("Task 1", "Description");
391
392        ops.track_coverage(task).expect("Failed to track");
393        assert_eq!(ops.maintenance_tasks.len(), 1);
394    }
395
396    #[test]
397    fn test_maintenance_status_update() {
398        let mut ops = DocumentationOperations::new();
399        let task = MaintenanceTask::new("Task 1", "Description");
400
401        ops.track_coverage(task).expect("Failed to track");
402        ops.update_task_status("Task 1", MaintenanceStatus::Completed)
403            .expect("Failed to update");
404
405        let updated = ops.get_maintenance_task("Task 1").unwrap();
406        assert_eq!(updated.status, MaintenanceStatus::Completed);
407    }
408
409    #[test]
410    fn test_publishing_result_builder() {
411        let result = PublishingResult::success("abc123")
412            .with_file("README.md")
413            .with_file("API.md");
414
415        assert!(result.success);
416        assert_eq!(result.files_published.len(), 2);
417    }
418
419    #[test]
420    fn test_publishing_result_failure() {
421        let result = PublishingResult::failure("Something went wrong");
422
423        assert!(!result.success);
424        assert!(result.error.is_some());
425    }
426}