1use std::sync::Arc;
2use aurora_core::{
3 Command, CommandCategory, Pipeline, Engine, AuroraResult,
4};
5use aurora_locale::Localizer;
6
7use crate::{
8 media, dev, fs, sys, net, archive, docker, unix,
9 crypto, calc, qr, color, timer, note, clip, text,
10};
11
12pub struct ModulesEngine {
13 localizer: Arc<Localizer>,
14}
15
16impl ModulesEngine {
17 pub fn new(localizer: Arc<Localizer>) -> Self {
18 Self { localizer }
19 }
20}
21
22impl Engine for ModulesEngine {
23 fn name(&self) -> &'static str {
24 "modules"
25 }
26
27 fn can_handle(&self, cmd: &Command) -> bool {
28 matches!(
29 cmd.category,
30 CommandCategory::Media
31 | CommandCategory::Dev
32 | CommandCategory::Fs
33 | CommandCategory::Sys
34 | CommandCategory::Net
35 | CommandCategory::Archive
36 | CommandCategory::Docker
37 | CommandCategory::Unix
38 | CommandCategory::Crypto
39 | CommandCategory::Calc
40 | CommandCategory::Qr
41 | CommandCategory::Color
42 | CommandCategory::Timer
43 | CommandCategory::Note
44 | CommandCategory::Clipboard
45 | CommandCategory::Text
46 )
47 }
48
49 fn execute(&self, cmd: &Command, _input: &Pipeline) -> AuroraResult<Pipeline> {
50 match cmd.category {
51 CommandCategory::Media => execute_media(cmd),
52 CommandCategory::Dev => execute_dev(cmd, &self.localizer),
53 CommandCategory::Fs => execute_fs(cmd),
54 CommandCategory::Sys => execute_sys(cmd),
55 CommandCategory::Net => execute_net(cmd),
56 CommandCategory::Archive => execute_archive(cmd),
57 CommandCategory::Docker => execute_docker(cmd),
58 CommandCategory::Unix => execute_unix(cmd),
59 CommandCategory::Crypto => execute_crypto(cmd),
60 CommandCategory::Calc => execute_calc(cmd),
61 CommandCategory::Qr => execute_qr(cmd),
62 CommandCategory::Color => execute_color(cmd),
63 CommandCategory::Timer => execute_timer(cmd),
64 CommandCategory::Note => execute_note(cmd),
65 CommandCategory::Clipboard => execute_clip(cmd),
66 CommandCategory::Text => execute_text(cmd),
67 _ => Err(aurora_core::AuroraError::ModuleError(
68 format!("unsupported category: {:?}", cmd.category)
69 )),
70 }
71 }
72}
73
74fn execute_media(cmd: &Command) -> AuroraResult<Pipeline> {
75 match cmd.action.as_str() {
76 "info" | "probe" => {
77 let input = cmd.args.first().ok_or_else(|| {
78 aurora_core::AuroraError::InvalidInput("missing input file".into())
79 })?;
80 media::media_info(input)
81 }
82 "convert" => {
83 let input = cmd.args.first().ok_or_else(|| {
84 aurora_core::AuroraError::InvalidInput("missing input file".into())
85 })?;
86 let output = cmd.args.get(1).ok_or_else(|| {
87 aurora_core::AuroraError::InvalidInput("missing output file".into())
88 })?;
89 media::media_convert(input, output)
90 }
91 "stream" => {
92 let input = cmd.args.first().ok_or_else(|| {
93 aurora_core::AuroraError::InvalidInput("missing input file".into())
94 })?;
95 media::media_stream(input)
96 }
97 "extract" => {
98 let input = cmd.args.first().ok_or_else(|| {
99 aurora_core::AuroraError::InvalidInput("missing input file".into())
100 })?;
101 let output = cmd.args.get(1).ok_or_else(|| {
102 aurora_core::AuroraError::InvalidInput("missing output file".into())
103 })?;
104 media::media_extract(input, output)
105 }
106 _ => Err(aurora_core::AuroraError::CommandNotFound(
107 format!("media.{}", cmd.action)
108 )),
109 }
110}
111
112fn execute_dev(cmd: &Command, localizer: &Arc<Localizer>) -> AuroraResult<Pipeline> {
113 match cmd.action.as_str() {
114 "log" => {
115 let oneline = cmd.switches.contains(&"oneline".into());
116 let graph = cmd.switches.contains(&"graph".into());
117 let n = cmd.flags.get("n")
118 .and_then(|v| v.parse::<usize>().ok());
119 dev::dev_log(oneline, graph, n, localizer)
120 }
121 "status" => dev::dev_status(),
122 "diff" => {
123 let stat = cmd.switches.contains(&"stat".into());
124 dev::dev_diff(stat, localizer)
125 }
126 "commit" => {
127 let msg = cmd.args.first().map(|s| s.as_str()).unwrap_or("");
128 dev::dev_commit(msg, localizer)
129 }
130 "branch" => {
131 let delete = cmd.switches.contains(&"delete".into());
132 let rename = cmd.switches.contains(&"rename".into());
133 let name = cmd.args.first().map(|s| s.as_str());
134 dev::dev_branch(delete, rename, name, localizer)
135 }
136 "push" => {
137 let remote = cmd.args.first().map(|s| s.as_str());
138 let branch = cmd.args.get(1).map(|s| s.as_str());
139 let upstream = cmd.switches.contains(&"upstream".into());
140 let force = cmd.switches.contains(&"force".into());
141 dev::dev_push(remote, branch, upstream, force, localizer)
142 }
143 "fetch" => {
144 let remote = cmd.args.first().map(|s| s.as_str());
145 let branch = cmd.args.get(1).map(|s| s.as_str());
146 dev::dev_fetch(remote, branch, localizer)
147 }
148 "pull" => {
149 let remote = cmd.args.first().map(|s| s.as_str());
150 let branch = cmd.args.get(1).map(|s| s.as_str());
151 dev::dev_pull(remote, branch, localizer)
152 }
153 "clone" => {
154 let url = cmd.args.first().ok_or_else(|| {
155 aurora_core::AuroraError::InvalidInput("missing repository URL".into())
156 })?;
157 let target = cmd.args.get(1).map(|s| s.as_str());
158 dev::dev_clone(url, target, localizer)
159 }
160 "init" => dev::dev_init(localizer),
161 "add" => {
162 let files = cmd.args.clone();
163 dev::dev_add(&files, localizer)
164 }
165 "checkout" => {
166 let target = cmd.args.first().map(|s| s.as_str());
167 let create = cmd.switches.contains(&"create".into());
168 let orphan = cmd.switches.contains(&"orphan".into());
169 dev::dev_checkout(target, create, orphan, localizer)
170 }
171 "stash" => {
172 let sub = cmd.args.first().map(|s| s.as_str()).unwrap_or("push");
173 match sub {
174 "push" => dev::dev_stash_push(localizer),
175 "list" | "ls" => dev::dev_stash_list(localizer),
176 "pop" => dev::dev_stash_pop(localizer),
177 _ => Err(aurora_core::AuroraError::CommandNotFound(
178 format!("stash.{sub}")
179 )),
180 }
181 }
182 "reset" => {
183 let files = cmd.args.clone();
184 let hard = cmd.switches.contains(&"hard".into());
185 dev::dev_reset(&files, hard, localizer)
186 }
187 "config" => {
188 let key = cmd.args.first().map(|s| s.as_str());
189 let value = cmd.args.get(1).map(|s| s.as_str());
190 dev::dev_config(key, value, localizer)
191 }
192 "remote" => {
193 let sub = cmd.args.first().map(|s| s.as_str()).unwrap_or("");
194 match sub {
195 "add" => {
196 let name = cmd.args.get(1).map(|s| s.as_str());
197 let url = cmd.args.get(2).map(|s| s.as_str());
198 dev::dev_remote_add(name, url, localizer)
199 }
200 "list" | "ls" => dev::dev_remote_list(localizer),
201 "remove" | "rm" => {
202 let name = cmd.args.get(1).ok_or_else(|| {
203 aurora_core::AuroraError::InvalidInput("missing remote name".into())
204 })?;
205 dev::dev_remote_remove(name, localizer)
206 }
207 _ => Err(aurora_core::AuroraError::CommandNotFound(
208 "remote.unknown".into()
209 )),
210 }
211 }
212 _ => Err(aurora_core::AuroraError::CommandNotFound(
213 format!("dev.{}", cmd.action)
214 )),
215 }
216}
217
218fn execute_fs(cmd: &Command) -> AuroraResult<Pipeline> {
219 match cmd.action.as_str() {
220 "ls" => {
221 let path = cmd.args.first().map(|s| s.as_str()).unwrap_or(".");
222 let long = cmd.switches.contains(&"long".into());
223 fs::fs_ls(path, long)
224 }
225 "tree" => {
226 let path = cmd.args.first().map(|s| s.as_str()).unwrap_or(".");
227 let depth = cmd.flags.get("depth")
228 .and_then(|v| v.parse::<usize>().ok());
229 fs::fs_tree(path, depth)
230 }
231 "find" => {
232 let pattern = cmd.args.first().ok_or_else(|| {
233 aurora_core::AuroraError::InvalidInput("missing search pattern".into())
234 })?;
235 let path = cmd.args.get(1).map(|s| s.as_str()).unwrap_or(".");
236 fs::fs_find(pattern, path)
237 }
238 "info" => {
239 let path = cmd.args.first().map(|s| s.as_str()).unwrap_or(".");
240 fs::fs_info(path)
241 }
242 "du" => {
243 let path = cmd.args.first().map(|s| s.as_str()).unwrap_or(".");
244 fs::fs_du(path)
245 }
246 _ => Err(aurora_core::AuroraError::CommandNotFound(
247 format!("fs.{}", cmd.action)
248 )),
249 }
250}
251
252fn execute_sys(cmd: &Command) -> AuroraResult<Pipeline> {
253 match cmd.action.as_str() {
254 "info" => sys::sys_info(),
255 "process" | "ps" => sys::sys_process(),
256 "disk" | "df" => sys::sys_disk(),
257 "memory" | "mem" => sys::sys_memory(),
258 "service" => {
259 let name = cmd.args.first().ok_or_else(|| {
260 aurora_core::AuroraError::InvalidInput("missing service name".into())
261 })?;
262 sys::sys_service(name)
263 }
264 _ => Err(aurora_core::AuroraError::CommandNotFound(
265 format!("sys.{}", cmd.action)
266 )),
267 }
268}
269
270fn execute_net(cmd: &Command) -> AuroraResult<Pipeline> {
271 match cmd.action.as_str() {
272 "ping" => {
273 let host = cmd.args.first().ok_or_else(|| {
274 aurora_core::AuroraError::InvalidInput("missing host".into())
275 })?;
276 net::net_ping(host)
277 }
278 "dns" => {
279 let domain = cmd.args.first().ok_or_else(|| {
280 aurora_core::AuroraError::InvalidInput("missing domain".into())
281 })?;
282 net::net_dns(domain)
283 }
284 "http" | "curl" => {
285 let url = cmd.args.first().ok_or_else(|| {
286 aurora_core::AuroraError::InvalidInput("missing URL".into())
287 })?;
288 net::net_http(url)
289 }
290 "ip" => net::net_ip(),
291 "scan" => {
292 let host = cmd.args.first().ok_or_else(|| {
293 aurora_core::AuroraError::InvalidInput("missing host".into())
294 })?;
295 net::net_scan(host)
296 }
297 _ => Err(aurora_core::AuroraError::CommandNotFound(
298 format!("net.{}", cmd.action)
299 )),
300 }
301}
302
303fn execute_archive(cmd: &Command) -> AuroraResult<Pipeline> {
304 match cmd.action.as_str() {
305 "list" => {
306 let file = cmd.args.first().ok_or_else(|| {
307 aurora_core::AuroraError::InvalidInput("missing archive file".into())
308 })?;
309 archive::archive_list(file)
310 }
311 "extract" => {
312 let file = cmd.args.first().ok_or_else(|| {
313 aurora_core::AuroraError::InvalidInput("missing archive file".into())
314 })?;
315 let output = cmd.args.get(1).map(|s| s.as_str());
316 archive::archive_extract(file, output)
317 }
318 "compress" => {
319 let input = cmd.args.first().ok_or_else(|| {
320 aurora_core::AuroraError::InvalidInput("missing input".into())
321 })?;
322 let output = cmd.args.get(1).ok_or_else(|| {
323 aurora_core::AuroraError::InvalidInput("missing output file".into())
324 })?;
325 archive::archive_compress(input, output)
326 }
327 _ => Err(aurora_core::AuroraError::CommandNotFound(
328 format!("archive.{}", cmd.action)
329 )),
330 }
331}
332
333fn execute_docker(cmd: &Command) -> AuroraResult<Pipeline> {
334 match cmd.action.as_str() {
335 "ps" => docker::docker_ps(),
336 "logs" => {
337 let container = cmd.args.first().ok_or_else(|| {
338 aurora_core::AuroraError::InvalidInput("missing container id".into())
339 })?;
340 let tail = cmd.flags.get("tail")
341 .and_then(|v| v.parse::<usize>().ok());
342 docker::docker_logs(container, tail)
343 }
344 "exec" => {
345 let container = cmd.args.first().ok_or_else(|| {
346 aurora_core::AuroraError::InvalidInput("missing container id".into())
347 })?;
348 let command = cmd.args[1..].to_vec();
349 docker::docker_exec(container, &command)
350 }
351 "images" => docker::docker_images(),
352 _ => Err(aurora_core::AuroraError::CommandNotFound(
353 format!("docker.{}", cmd.action)
354 )),
355 }
356}
357
358fn execute_unix(cmd: &Command) -> AuroraResult<Pipeline> {
359 match cmd.action.as_str() {
360 "ps" => unix::unix_ps(),
361 "df" => {
362 let human = cmd.switches.contains(&"human".into());
363 unix::unix_df(human)
364 }
365 "du" => {
366 let path = cmd.args.first().map(|s| s.as_str()).unwrap_or(".");
367 unix::unix_du(path)
368 }
369 "uptime" => unix::unix_uptime(),
370 "env" => unix::unix_env(),
371 "uname" => unix::unix_uname(),
372 _ => Err(aurora_core::AuroraError::CommandNotFound(
373 format!("unix.{}", cmd.action)
374 )),
375 }
376}
377
378fn execute_crypto(cmd: &Command) -> AuroraResult<Pipeline> {
379 match cmd.action.as_str() {
380 "hash" => {
381 let input = cmd.args.first().ok_or_else(|| {
382 aurora_core::AuroraError::InvalidInput("missing input".into())
383 })?;
384 let algo = cmd.flags.get("algo").map(|s| s.as_str());
385 crypto::crypto_hash(input, algo)
386 }
387 "genkey" | "keygen" => {
388 let algo = cmd.flags.get("algo").map(|s| s.as_str());
389 crypto::crypto_genkey(algo)
390 }
391 _ => Err(aurora_core::AuroraError::CommandNotFound(
392 format!("crypto.{}", cmd.action)
393 )),
394 }
395}
396
397fn execute_calc(cmd: &Command) -> AuroraResult<Pipeline> {
398 match cmd.action.as_str() {
399 "eval" | "calc" => {
400 let expr = cmd.args.clone();
401 if expr.is_empty() {
402 return Err(aurora_core::AuroraError::InvalidInput(
403 "missing expression".into()
404 ));
405 }
406 calc::calc_eval(&expr)
407 }
408 _ => Err(aurora_core::AuroraError::CommandNotFound(
409 format!("calc.{}", cmd.action)
410 )),
411 }
412}
413
414fn execute_qr(cmd: &Command) -> AuroraResult<Pipeline> {
415 match cmd.action.as_str() {
416 "encode" => {
417 let text = cmd.args.first().ok_or_else(|| {
418 aurora_core::AuroraError::InvalidInput("missing text".into())
419 })?;
420 let output = cmd.args.get(1).map(|s| s.as_str());
421 qr::qr_encode(text, output)
422 }
423 "decode" => {
424 let input = cmd.args.first().ok_or_else(|| {
425 aurora_core::AuroraError::InvalidInput("missing input file".into())
426 })?;
427 qr::qr_decode(input)
428 }
429 _ => Err(aurora_core::AuroraError::CommandNotFound(
430 format!("qr.{}", cmd.action)
431 )),
432 }
433}
434
435fn execute_color(cmd: &Command) -> AuroraResult<Pipeline> {
436 match cmd.action.as_str() {
437 "palette" => color::color_palette(),
438 "hex" => {
439 let hex = cmd.args.first().ok_or_else(|| {
440 aurora_core::AuroraError::InvalidInput("missing hex color".into())
441 })?;
442 color::color_hex(hex)
443 }
444 "rgb" => {
445 let r = cmd.args.first()
446 .and_then(|v| v.parse::<u8>().ok())
447 .ok_or_else(|| aurora_core::AuroraError::InvalidInput("missing or invalid r".into()))?;
448 let g = cmd.args.get(1)
449 .and_then(|v| v.parse::<u8>().ok())
450 .ok_or_else(|| aurora_core::AuroraError::InvalidInput("missing or invalid g".into()))?;
451 let b = cmd.args.get(2)
452 .and_then(|v| v.parse::<u8>().ok())
453 .ok_or_else(|| aurora_core::AuroraError::InvalidInput("missing or invalid b".into()))?;
454 color::color_rgb(r, g, b)
455 }
456 _ => Err(aurora_core::AuroraError::CommandNotFound(
457 format!("color.{}", cmd.action)
458 )),
459 }
460}
461
462fn execute_timer(cmd: &Command) -> AuroraResult<Pipeline> {
463 match cmd.action.as_str() {
464 "start" => {
465 let seconds = cmd.args.first()
466 .and_then(|v| v.parse::<u64>().ok())
467 .ok_or_else(|| aurora_core::AuroraError::InvalidInput("missing or invalid seconds".into()))?;
468 let name = cmd.args.get(1).map(|s| s.clone());
469 timer::timer_start(seconds, name.as_deref())
470 }
471 "stop" => timer::timer_stop(),
472 "status" => timer::timer_status(),
473 _ => Err(aurora_core::AuroraError::CommandNotFound(
474 format!("timer.{}", cmd.action)
475 )),
476 }
477}
478
479fn execute_note(cmd: &Command) -> AuroraResult<Pipeline> {
480 match cmd.action.as_str() {
481 "add" | "new" => {
482 let text = cmd.args.clone();
483 if text.is_empty() {
484 return Err(aurora_core::AuroraError::InvalidInput(
485 "missing note text".into()
486 ));
487 }
488 note::note_add(&text)
489 }
490 _ => Err(aurora_core::AuroraError::CommandNotFound(
491 format!("note.{}", cmd.action)
492 )),
493 }
494}
495
496fn execute_clip(cmd: &Command) -> AuroraResult<Pipeline> {
497 match cmd.action.as_str() {
498 "get" => clip::clip_get(),
499 "set" => {
500 let text = cmd.args.clone();
501 clip::clip_set(&text)
502 }
503 "clear" => clip::clip_clear(),
504 _ => Err(aurora_core::AuroraError::CommandNotFound(
505 format!("clip.{}", cmd.action)
506 )),
507 }
508}
509
510fn execute_text(cmd: &Command) -> AuroraResult<Pipeline> {
511 match cmd.action.as_str() {
512 "grep" => {
513 let pattern = cmd.args.first().ok_or_else(|| {
514 aurora_core::AuroraError::InvalidInput("missing search pattern".into())
515 })?;
516 let path = cmd.args.get(1).map(|s| s.as_str()).unwrap_or(".");
517 text::text_grep(pattern, path)
518 }
519 "replace" => {
520 let pattern = cmd.args.first().ok_or_else(|| {
521 aurora_core::AuroraError::InvalidInput("missing search pattern".into())
522 })?;
523 let replacement = cmd.args.get(1).ok_or_else(|| {
524 aurora_core::AuroraError::InvalidInput("missing replacement".into())
525 })?;
526 let path = cmd.args.get(2).ok_or_else(|| {
527 aurora_core::AuroraError::InvalidInput("missing file path".into())
528 })?;
529 text::text_replace(pattern, replacement, path)
530 }
531 "transform" => {
532 let path = cmd.args.first().ok_or_else(|| {
533 aurora_core::AuroraError::InvalidInput("missing file path".into())
534 })?;
535 let upper = cmd.switches.contains(&"upper".into());
536 let lower = cmd.switches.contains(&"lower".into());
537 text::text_transform(path, upper, lower)
538 }
539 _ => Err(aurora_core::AuroraError::CommandNotFound(
540 format!("text.{}", cmd.action)
541 )),
542 }
543}