Skip to main content

rush_sync_server/commands/help/
command.rs

1use crate::commands::command::Command;
2use crate::core::prelude::*;
3
4#[derive(Debug, Default)]
5pub struct HelpCommand;
6
7impl HelpCommand {
8    pub fn new() -> Self {
9        Self
10    }
11}
12
13impl Command for HelpCommand {
14    fn name(&self) -> &'static str {
15        "help"
16    }
17
18    fn description(&self) -> &'static str {
19        "Show all available commands"
20    }
21
22    fn matches(&self, command: &str) -> bool {
23        let cmd = command.trim().to_lowercase();
24        cmd == "help" || cmd == "?" || cmd == "commands" || cmd == "list-commands"
25    }
26
27    fn execute_sync(&self, args: &[&str]) -> Result<String> {
28        let handler = crate::commands::CommandHandler::new();
29
30        match args.first() {
31            Some(&"--simple" | &"-s") => Ok(self.create_simple_list(&handler)),
32            Some(&"--detailed" | &"-d") => Ok(self.create_detailed_list(&handler)),
33            None => Ok(self.create_formatted_list(&handler)),
34            Some(&command_name) => Ok(self.show_command_help(command_name, &handler)),
35        }
36    }
37
38    fn priority(&self) -> u8 {
39        95
40    }
41}
42
43impl HelpCommand {
44    /// Get detailed usage info for a command
45    fn get_command_usage(command_name: &str) -> Option<&'static str> {
46        match command_name {
47            "create" => Some(
48                "  create                   Create server with auto name/port\n  \
49                 create <name>             Create server with custom name\n  \
50                 create <name> <port>      Create with name and port\n  \
51                 create <count>            Bulk create (1-100 servers)\n  \
52                 create <name> <port> <n>  Bulk with base name, port, count\n\n  \
53                 Examples:\n    \
54                 create                    -> rss-001 on next free port\n    \
55                 create mysite             -> mysite on next free port\n    \
56                 create mysite 8080        -> mysite on port 8080\n    \
57                 create 50                 -> 50 servers (rss-001..rss-050)\n    \
58                 create web 8001 10        -> web-001:8001 .. web-010:8010",
59            ),
60            "start" => Some(
61                "  start <id|name|number>   Start a single server\n  \
62                 start <start>-<end>       Start range of servers\n  \
63                 start all                 Start all stopped servers\n  \
64                 --workers N, -w N         Set workers per server (1-16)\n\n  \
65                 Note: 'start all' and ranges skip browser opening.\n  \
66                 Bulk operations show time + memory benchmarks.\n\n  \
67                 Examples:\n    \
68                 start rss-001             -> start by name\n    \
69                 start 1                   -> start server #1\n    \
70                 start 1-10               -> start servers 1 through 10\n    \
71                 start all                 -> start all stopped servers\n    \
72                 start rss-001 --workers 3 -> start with 3 workers\n    \
73                 start all -w 2            -> start all with 2 workers each",
74            ),
75            "stop" => Some(
76                "  stop <id|name|number>    Stop a single server\n  \
77                 stop <start>-<end>        Stop range of servers\n  \
78                 stop all                  Stop all running servers\n\n  \
79                 Examples:\n    \
80                 stop rss-001              -> stop by name\n    \
81                 stop 1                    -> stop server #1\n    \
82                 stop 1-5                  -> stop servers 1 through 5\n    \
83                 stop all                  -> stop all running servers",
84            ),
85            "list" => Some(
86                "  list                     Show all servers (sorted by port)\n\n  \
87                 Filter:\n    \
88                 list running              Only running servers\n    \
89                 list stopped              Only stopped servers\n    \
90                 list failed               Only failed servers\n\n  \
91                 Sort:\n    \
92                 list -port asc            By port ascending (default)\n    \
93                 list -port desc           By port descending\n    \
94                 list -name asc            By name A-Z\n    \
95                 list -name desc           By name Z-A\n\n  \
96                 Special:\n    \
97                 list memory               Disk + RAM usage per server\n\n  \
98                 Combine: list running -name asc\n  \
99                 Aliases: list servers, list server",
100            ),
101            "restart" => Some(
102                "  restart                  Restart application (with confirm)\n  \
103                 restart -f, --force       Force restart without confirm\n  \
104                 restart -h, --help        Show help\n\n  \
105                 Aliases: reboot, reset",
106            ),
107            "cleanup" => Some(
108                "  cleanup                  Clean stopped servers (confirm)\n  \
109                 cleanup stopped           Clean stopped servers\n  \
110                 cleanup failed            Clean failed servers\n  \
111                 cleanup logs              Clean all log files\n  \
112                 cleanup www               Clean all www directories\n  \
113                 cleanup www <name>        Clean specific server www\n  \
114                 cleanup all               Clean everything\n  \
115                 cleanup --force-stopped   Skip confirmation\n  \
116                 cleanup --force-failed    Skip confirmation\n  \
117                 cleanup --force-logs      Skip confirmation\n  \
118                 cleanup --force-www       Skip confirmation\n  \
119                 cleanup --force-all       Skip confirmation",
120            ),
121            "recover" => Some(
122                "  recover                  Auto-fix inconsistent servers\n  \
123                 recover all               Fix all servers\n  \
124                 recover <id>              Fix specific server\n\n  \
125                 Aliases: fix, status-fix",
126            ),
127            "remote" => Some(
128                "  remote list              List SSH profiles\n  \
129                 remote add <name> <user@host> <path> [port] [key]\n  \
130                 remote show <name>        Show profile details\n  \
131                 remote remove <name>      Delete profile\n  \
132                 remote test <name>        Test SSH connection\n  \
133                 remote help               Show help\n\n  \
134                 Aliases: remote ls, remote rm, remote delete",
135            ),
136            "sync" => Some(
137                "  sync push <remote> [path] [--delete] [--dry-run]\n  \
138                 sync pull <remote> [path] [--delete] [--dry-run]\n  \
139                 sync test <remote>        Test connection\n  \
140                 sync exec <remote> <cmd>  Run remote command\n  \
141                 sync restart <remote>     Restart remote service\n  \
142                 sync git-pull <remote>    Remote git pull\n\n  \
143                 Flags:\n    \
144                 --delete                  Remove files not in source\n    \
145                 --dry-run, -n             Preview without applying",
146            ),
147            "theme" => Some(
148                "  theme                    Show current & available themes\n  \
149                 theme <name>              Switch theme (live)\n  \
150                 theme preview <name>      Preview theme\n  \
151                 theme debug <name>        Show theme details\n  \
152                 theme -h, --help          Show help",
153            ),
154            "lang" | "language" => Some(
155                "  lang                     Show current language\n  \
156                 lang <code>               Switch language (en, de, fr...)",
157            ),
158            "log-level" => Some(
159                "  log-level                Show current level\n  \
160                 log-level <level>         Set level (trace/debug/info/warn/error)\n  \
161                 log-level -h, --help      Show help",
162            ),
163            "history" => Some(
164                "  history                  Show info\n  \
165                 history -c, --clear       Clear with confirmation\n  \
166                 history -fc, --force-clear  Force clear\n  \
167                 history -h, --help        Show help",
168            ),
169            "version" => Some(
170                "  version                  Show version info\n\n  \
171                 Alias: ver",
172            ),
173            "clear" => Some(
174                "  clear                    Clear screen\n\n  \
175                 Alias: cls",
176            ),
177            "exit" => Some(
178                "  exit                     Exit with confirmation\n\n  \
179                 Alias: q",
180            ),
181            "help" => Some(
182                "  help                     Show all commands\n  \
183                 help <command>            Show command details\n  \
184                 help -s, --simple         Simple list\n  \
185                 help -d, --detailed       Detailed list\n\n  \
186                 Tip: Use '<command> ?' for quick help (e.g. 'create ?')",
187            ),
188            _ => None,
189        }
190    }
191
192    /// Look up the localized description for a command, falling back to the original
193    fn get_localized_description(&self, command_name: &str, original_description: &str) -> String {
194        let normalized_name = command_name.replace("-", "_");
195        let description_key = format!("system.commands.{}.description", normalized_name);
196
197        if crate::i18n::has_translation(&description_key) {
198            get_command_translation(&description_key, &[])
199        } else {
200            original_description.to_string()
201        }
202    }
203
204    /// Build the formatted default help list
205    fn create_formatted_list(&self, handler: &crate::commands::CommandHandler) -> String {
206        let commands = handler.list_commands();
207
208        if commands.is_empty() {
209            return get_command_translation("system.commands.help.no_commands", &[]);
210        }
211
212        let mut result = String::new();
213        result.push_str(&get_command_translation("system.commands.help.header", &[]));
214        result.push_str("\n\n");
215
216        let mut categorized = std::collections::BTreeMap::new();
217
218        for (name, original_description) in commands {
219            let category_key = self.determine_category(name);
220            let localized_description = self.get_localized_description(name, original_description);
221
222            categorized
223                .entry(category_key)
224                .or_insert_with(Vec::new)
225                .push((name, localized_description));
226        }
227
228        for (category_key, commands) in categorized {
229            let category_translation_key =
230                format!("system.commands.help.category.{}", category_key);
231
232            let category_name = if crate::i18n::has_translation(&category_translation_key) {
233                get_command_translation(&category_translation_key, &[])
234            } else {
235                self.get_fallback_category_name(category_key)
236            };
237
238            result.push_str(&format!("{}:\n", category_name));
239
240            for (name, description) in &commands {
241                // Show short usage hint next to description
242                let usage_hint = match *name {
243                    "create" => " (create [name] [port] [count])",
244                    "start" => " (start <id|name|all|1-N> [-w N])",
245                    "stop" => " (stop <id|name|all|1-N>)",
246                    "cleanup" => " (cleanup [stopped|failed|logs|www|all])",
247                    "sync" => " (sync push|pull|test|exec ...)",
248                    "remote" => " (remote list|add|show|remove|test)",
249                    "lang" | "language" => " (lang [code])",
250                    "theme" => " (theme [name|preview|debug])",
251                    "log-level" => " (log-level [level])",
252                    _ => "",
253                };
254
255                result.push_str(&format!(
256                    "  {:12} {}{}\n",
257                    name, description, usage_hint
258                ));
259            }
260            result.push('\n');
261        }
262
263        result.push_str("  Tip: '<command> ?' for details (e.g. 'create ?')\n");
264        result.push_str(&get_command_translation("system.commands.help.footer", &[]));
265        result
266    }
267
268    /// Determine the category for a command by name prefix
269    fn determine_category(&self, command_name: &str) -> &'static str {
270        match command_name {
271            name if name.starts_with("start")
272                || name.starts_with("stop")
273                || name.starts_with("restart") =>
274            {
275                "server_control"
276            }
277            name if name.starts_with("create") || name.starts_with("list") => "server_management",
278            name if name.starts_with("remote") || name.starts_with("sync") => "deployment",
279            name if name.starts_with("cleanup") || name.starts_with("recover") => "maintenance",
280            name if name.starts_with("theme")
281                || name.starts_with("lang")
282                || name.starts_with("log-level") =>
283            {
284                "configuration"
285            }
286            name if name.starts_with("help")
287                || name.starts_with("version")
288                || name.starts_with("history") =>
289            {
290                "information"
291            }
292            name if name.starts_with("exit") || name.starts_with("clear") => "system",
293            _ => "other",
294        }
295    }
296
297    /// Fallback category names when i18n key is missing
298    fn get_fallback_category_name(&self, category_key: &str) -> String {
299        match category_key {
300            "server_control" => "Server Control".to_string(),
301            "server_management" => "Server Management".to_string(),
302            "deployment" => "Deployment & Sync".to_string(),
303            "maintenance" => "Maintenance".to_string(),
304            "configuration" => "Configuration".to_string(),
305            "information" => "Information".to_string(),
306            "system" => "System".to_string(),
307            "other" => "Other".to_string(),
308            _ => category_key.to_string(),
309        }
310    }
311
312    /// Build a comma-separated simple command list
313    fn create_simple_list(&self, handler: &crate::commands::CommandHandler) -> String {
314        let commands = handler.list_commands();
315        let names: Vec<&str> = commands.iter().map(|(name, _)| *name).collect();
316        let names_str = names.join(", ");
317
318        get_command_translation("system.commands.help.simple_list", &[&names_str])
319    }
320
321    /// Build a detailed command list with labels and separators
322    fn create_detailed_list(&self, handler: &crate::commands::CommandHandler) -> String {
323        let commands = handler.list_commands();
324        let mut result = String::new();
325
326        result.push_str(&get_command_translation(
327            "system.commands.help.detailed_header",
328            &[],
329        ));
330        result.push('\n');
331        result.push_str(&get_command_translation(
332            "system.commands.help.detailed_separator",
333            &[],
334        ));
335        result.push_str("\n\n");
336
337        for (name, original_description) in commands {
338            let localized_description = self.get_localized_description(name, original_description);
339
340            result.push_str(&format!("  {}\n", name.to_uppercase()));
341            result.push_str(&format!("  {}\n", localized_description));
342
343            if let Some(usage) = Self::get_command_usage(name) {
344                result.push_str(&format!("\n{}\n", usage));
345            }
346
347            result.push_str("\n  ──────────────────────────────\n\n");
348        }
349
350        result
351    }
352
353    /// Show help for a specific command (with full usage details)
354    fn show_command_help(
355        &self,
356        command_name: &str,
357        handler: &crate::commands::CommandHandler,
358    ) -> String {
359        let commands = handler.list_commands();
360
361        for (name, original_description) in commands {
362            if name.eq_ignore_ascii_case(command_name) {
363                let localized_description =
364                    self.get_localized_description(name, original_description);
365
366                let mut result = format!(
367                    "\n  {} - {}\n",
368                    name.to_uppercase(),
369                    localized_description
370                );
371
372                if let Some(usage) = Self::get_command_usage(name) {
373                    result.push_str(&format!("\n{}\n", usage));
374                }
375
376                return result;
377            }
378        }
379
380        get_command_translation("system.commands.help.command_not_found", &[command_name])
381    }
382}