ricecoder_github/managers/
documentation_operations.rs1use crate::errors::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use tracing::{debug, info};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct DocumentationCommit {
11 pub message: String,
13 pub files: Vec<String>,
15 pub hash: Option<String>,
17 pub timestamp: Option<String>,
19}
20
21impl DocumentationCommit {
22 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 pub fn with_file(mut self, file: impl Into<String>) -> Self {
34 self.files.push(file.into());
35 self
36 }
37
38 pub fn with_files(mut self, files: Vec<String>) -> Self {
40 self.files.extend(files);
41 self
42 }
43
44 pub fn with_hash(mut self, hash: impl Into<String>) -> Self {
46 self.hash = Some(hash.into());
47 self
48 }
49
50 pub fn with_timestamp(mut self, timestamp: impl Into<String>) -> Self {
52 self.timestamp = Some(timestamp.into());
53 self
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct DocumentationTemplate {
60 pub name: String,
62 pub content: String,
64 pub variables: Vec<String>,
66}
67
68impl DocumentationTemplate {
69 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 pub fn with_variable(mut self, var: impl Into<String>) -> Self {
80 self.variables.push(var.into());
81 self
82 }
83
84 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#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct MaintenanceTask {
100 pub name: String,
102 pub description: String,
104 pub files: Vec<String>,
106 pub status: MaintenanceStatus,
108 pub progress: u32,
110}
111
112impl MaintenanceTask {
113 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 pub fn with_file(mut self, file: impl Into<String>) -> Self {
126 self.files.push(file.into());
127 self
128 }
129
130 pub fn with_status(mut self, status: MaintenanceStatus) -> Self {
132 self.status = status;
133 self
134 }
135
136 pub fn with_progress(mut self, progress: u32) -> Self {
138 self.progress = progress.min(100);
139 self
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
145#[serde(rename_all = "lowercase")]
146pub enum MaintenanceStatus {
147 Pending,
149 InProgress,
151 Completed,
153 Failed,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct PublishingResult {
160 pub success: bool,
162 pub commit_hash: Option<String>,
164 pub files_published: Vec<String>,
166 pub error: Option<String>,
168}
169
170impl PublishingResult {
171 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 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 pub fn with_file(mut self, file: impl Into<String>) -> Self {
193 self.files_published.push(file.into());
194 self
195 }
196}
197
198#[derive(Debug, Clone)]
200pub struct DocumentationOperations {
201 pub templates: HashMap<String, DocumentationTemplate>,
203 pub maintenance_tasks: HashMap<String, MaintenanceTask>,
205 pub commit_history: Vec<DocumentationCommit>,
207}
208
209impl DocumentationOperations {
210 pub fn new() -> Self {
212 Self {
213 templates: HashMap::new(),
214 maintenance_tasks: HashMap::new(),
215 commit_history: Vec::new(),
216 }
217 }
218
219 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 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 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 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 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 pub fn get_template(&self, name: &str) -> Option<&DocumentationTemplate> {
266 self.templates.get(name)
267 }
268
269 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 pub fn get_maintenance_tasks(&self) -> Vec<&MaintenanceTask> {
283 self.maintenance_tasks.values().collect()
284 }
285
286 pub fn get_maintenance_task(&self, name: &str) -> Option<&MaintenanceTask> {
288 self.maintenance_tasks.get(name)
289 }
290
291 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 pub fn get_commit_history(&self) -> &[DocumentationCommit] {
306 &self.commit_history
307 }
308
309 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}