arct_core/
education.rs

1//! Educational content and explanation system
2
3use crate::command::{Command, CommandAnalyzer, DangerLevel};
4use crate::types::{Result, Severity};
5use serde::{Deserialize, Serialize};
6
7/// Comprehensive explanation for a command
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Explanation {
10    pub summary: String,
11    pub description: String,
12    pub flag_explanations: Vec<FlagExplanation>,
13    pub tips: Vec<LearningTip>,
14    pub warnings: Vec<Warning>,
15    pub examples: Vec<String>,
16    pub related_commands: Vec<String>,
17}
18
19/// Explanation for a specific flag
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct FlagExplanation {
22    pub flag: String,
23    pub description: String,
24    pub effect: String,
25}
26
27/// Educational tip or insight
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct LearningTip {
30    pub title: String,
31    pub content: String,
32    pub category: TipCategory,
33}
34
35/// Categories of learning tips
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum TipCategory {
38    BestPractice,
39    CommonMistake,
40    ProTip,
41    DidYouKnow,
42    SafetyWarning,
43}
44
45/// Warning about dangerous operations
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Warning {
48    pub message: String,
49    pub severity: Severity,
50    pub suggestion: Option<String>,
51}
52
53/// Main educator that generates explanations
54pub struct Educator {
55    analyzer: CommandAnalyzer,
56    #[allow(dead_code)] // Reserved for future tip deduplication feature
57    tip_history: Vec<String>,
58}
59
60impl Educator {
61    pub fn new() -> Self {
62        Self {
63            analyzer: CommandAnalyzer::new(),
64            tip_history: Vec::new(),
65        }
66    }
67
68    /// Generate a comprehensive explanation for a command
69    pub fn explain(&mut self, command: &Command) -> Result<Explanation> {
70        let info = self.analyzer.get_command_info(&command.program);
71
72        let (summary, description, related_commands) = if let Some(info) = info {
73            (
74                info.summary.to_string(),
75                info.description.to_string(),
76                info.related_commands.iter().map(|s| s.to_string()).collect(),
77            )
78        } else {
79            (
80                format!("Execute '{}'", command.program),
81                "This command is not in our knowledge base yet.".to_string(),
82                Vec::new(),
83            )
84        };
85
86        let flag_explanations = self.explain_flags(command);
87        let tips = self.generate_tips(command);
88        let warnings = self.generate_warnings(command);
89        let examples = self.get_examples(&command.program);
90
91        Ok(Explanation {
92            summary,
93            description,
94            flag_explanations,
95            tips,
96            warnings,
97            examples,
98            related_commands,
99        })
100    }
101
102    /// Generate explanations for all flags in the command
103    fn explain_flags(&self, command: &Command) -> Vec<FlagExplanation> {
104        let mut explanations = Vec::new();
105
106        if let Some(info) = self.analyzer.get_command_info(&command.program) {
107            for flag in &command.flags {
108                let flag_str = if let Some(ch) = flag.short {
109                    format!("-{}", ch)
110                } else if let Some(ref long) = flag.long {
111                    format!("--{}", long)
112                } else {
113                    continue;
114                };
115
116                // Find matching flag info
117                for flag_info in &info.common_flags {
118                    if flag_info.flag == flag_str {
119                        explanations.push(FlagExplanation {
120                            flag: flag_str.clone(),
121                            description: flag_info.description.to_string(),
122                            effect: format!("This modifies how {} behaves", command.program),
123                        });
124                        break;
125                    }
126                }
127            }
128        }
129
130        explanations
131    }
132
133    /// Generate contextual learning tips
134    fn generate_tips(&mut self, command: &Command) -> Vec<LearningTip> {
135        let mut tips = Vec::new();
136
137        // Add category-specific tips
138        match command.category {
139            crate::command::CommandCategory::Navigation => {
140                tips.push(LearningTip {
141                    title: "Navigation Pro Tip".to_string(),
142                    content: "Use 'cd -' to toggle between your current and previous directory quickly!".to_string(),
143                    category: TipCategory::ProTip,
144                });
145            }
146            crate::command::CommandCategory::FileManagement => {
147                if command.danger_level >= DangerLevel::Dangerous {
148                    tips.push(LearningTip {
149                        title: "Safety First".to_string(),
150                        content: "Always use the -i flag for interactive confirmation with destructive commands.".to_string(),
151                        category: TipCategory::SafetyWarning,
152                    });
153                }
154            }
155            _ => {}
156        }
157
158        // Add tips based on flags used
159        if command.flags.iter().any(|f| f.short == Some('r')) && command.program == "rm" {
160            tips.push(LearningTip {
161                title: "Recursive Deletion".to_string(),
162                content: "The -r flag removes directories and all their contents. Double-check the path before executing!".to_string(),
163                category: TipCategory::CommonMistake,
164            });
165        }
166
167        tips
168    }
169
170    /// Generate warnings for dangerous operations
171    fn generate_warnings(&self, command: &Command) -> Vec<Warning> {
172        let mut warnings = Vec::new();
173
174        match command.danger_level {
175            DangerLevel::Dangerous | DangerLevel::Critical => {
176                warnings.push(Warning {
177                    message: format!("'{}' can cause permanent data loss or system damage", command.program),
178                    severity: Severity::Warning,
179                    suggestion: Some("Consider using the -i flag for interactive confirmation".to_string()),
180                });
181            }
182            _ => {}
183        }
184
185        // Specific dangerous patterns
186        if command.program == "rm"
187            && command.flags.iter().any(|f| f.short == Some('r'))
188            && command.flags.iter().any(|f| f.short == Some('f'))
189        {
190            warnings.push(Warning {
191                message: "⚠️  'rm -rf' is extremely dangerous! This will permanently delete everything without confirmation.".to_string(),
192                severity: Severity::Critical,
193                suggestion: Some("Remove the -f flag and use -i for safety, or specify the exact path you want to delete.".to_string()),
194            });
195        }
196
197        if command.program == "chmod" && command.args.contains(&"777".to_string()) {
198            warnings.push(Warning {
199                message: "chmod 777 makes files readable, writable, and executable by everyone - a security risk!".to_string(),
200                severity: Severity::Warning,
201                suggestion: Some("Use more restrictive permissions like 755 or 644".to_string()),
202            });
203        }
204
205        warnings
206    }
207
208    /// Get example commands
209    fn get_examples(&self, program: &str) -> Vec<String> {
210        if let Some(info) = self.analyzer.get_command_info(program) {
211            info.examples
212                .iter()
213                .map(|ex| format!("{} - {}", ex.command, ex.description))
214                .collect()
215        } else {
216            Vec::new()
217        }
218    }
219
220    /// Get pre-execution hint (shown before command runs)
221    pub fn get_hint(&self, command: &Command) -> Option<String> {
222        match command.danger_level {
223            DangerLevel::Dangerous | DangerLevel::Critical => {
224                Some(format!(
225                    "⚠️  This command can modify or delete data. Use with caution!"
226                ))
227            }
228            _ => None,
229        }
230    }
231}
232
233impl Default for Educator {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_educator_explain() {
245        let mut educator = Educator::new();
246        let analyzer = CommandAnalyzer::new();
247        let cmd = analyzer.parse("ls -la").unwrap();
248        let explanation = educator.explain(&cmd).unwrap();
249
250        assert!(!explanation.summary.is_empty());
251        assert!(!explanation.flag_explanations.is_empty());
252    }
253
254    #[test]
255    fn test_dangerous_command_warning() {
256        let mut educator = Educator::new();
257        let analyzer = CommandAnalyzer::new();
258        let cmd = analyzer.parse("rm -rf /tmp/test").unwrap();
259        let explanation = educator.explain(&cmd).unwrap();
260
261        assert!(!explanation.warnings.is_empty());
262    }
263}