Skip to main content

aurora_modules/
engine.rs

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}