1use crate::command::{Command, CommandAnalyzer, DangerLevel};
4use crate::types::{Result, Severity};
5use serde::{Deserialize, Serialize};
6
7#[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#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct FlagExplanation {
22 pub flag: String,
23 pub description: String,
24 pub effect: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct LearningTip {
30 pub title: String,
31 pub content: String,
32 pub category: TipCategory,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum TipCategory {
38 BestPractice,
39 CommonMistake,
40 ProTip,
41 DidYouKnow,
42 SafetyWarning,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Warning {
48 pub message: String,
49 pub severity: Severity,
50 pub suggestion: Option<String>,
51}
52
53pub struct Educator {
55 analyzer: CommandAnalyzer,
56 #[allow(dead_code)] 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 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 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 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 fn generate_tips(&mut self, command: &Command) -> Vec<LearningTip> {
135 let mut tips = Vec::new();
136
137 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 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 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 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 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 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}