hackshell/
lib.rs

1use std::{collections::HashMap, path::Path, sync::Arc};
2use tokio::{
3    io::{self},
4    sync::{Mutex, RwLock},
5};
6
7mod commands;
8pub mod error;
9mod readline;
10mod taskpool;
11
12use crate::readline::{Event, Readline};
13use commands::{
14    env::Env, exit::Exit, get::Get, help::Help, set::Set, sleep::Sleep, task::Task, unset::Unset,
15};
16use error::MapErrToString;
17use taskpool::{TaskMetadata, TaskPool};
18
19#[async_trait::async_trait]
20pub trait Command<C>: Send + Sync + 'static {
21    fn commands(&self) -> &'static [&'static str];
22
23    fn help(&self) -> &'static str;
24
25    async fn run(&self, s: &Hackshell<C>, cmd: &[String], ctx: &C) -> Result<(), String>;
26}
27
28struct InnerHackshell<C> {
29    ctx: C,
30    commands: RwLock<HashMap<String, Arc<dyn Command<C>>>>,
31    env: RwLock<HashMap<String, String>>,
32    pool: TaskPool,
33    prompt: String,
34    rl: Mutex<Readline>,
35}
36
37pub struct Hackshell<C> {
38    inner: Arc<InnerHackshell<C>>,
39}
40
41impl<C> Clone for Hackshell<C> {
42    fn clone(&self) -> Self {
43        Self {
44            inner: self.inner.clone(),
45        }
46    }
47}
48
49impl<C: Send + Sync + 'static> Hackshell<C> {
50    pub async fn new(ctx: C, prompt: &str, history_file: Option<&Path>) -> io::Result<Self> {
51        let s = Self {
52            inner: Arc::new(InnerHackshell {
53                ctx,
54                commands: Default::default(),
55                env: Default::default(),
56                pool: Default::default(),
57                prompt: prompt.to_string(),
58                rl: Mutex::new(Readline::new(history_file).await?),
59            }),
60        };
61
62        s.add_command(Env {})
63            .await
64            .add_command(Get {})
65            .await
66            .add_command(Set {})
67            .await
68            .add_command(Unset {})
69            .await
70            .add_command(Help {})
71            .await
72            .add_command(Sleep {})
73            .await
74            .add_command(Exit {})
75            .await
76            .add_command(Task {})
77            .await;
78
79        Ok(s)
80    }
81
82    pub async fn add_command(&self, command: impl Command<C> + 'static) -> Self {
83        let c = Arc::new(command);
84
85        for cmd in c.commands().iter() {
86            self.inner
87                .commands
88                .write()
89                .await
90                .insert(cmd.to_string(), c.clone());
91        }
92
93        self.clone()
94    }
95
96    pub fn get_ctx(&self) -> &C {
97        &self.inner.ctx
98    }
99
100    pub async fn spawn(&self, name: &str, fut: impl Future<Output = ()> + Send + 'static) {
101        self.inner.pool.spawn(name, fut).await;
102    }
103
104    pub async fn terminate(&self, name: &str) -> Result<(), String> {
105        self.inner.pool.remove(name).await
106    }
107
108    pub async fn wait(&self, name: &str) {
109        self.inner.pool.wait(name).await;
110    }
111
112    pub async fn get_tasks(&self) -> Vec<TaskMetadata> {
113        self.inner.pool.get_all().await
114    }
115
116    pub async fn get_commands(&self) -> Vec<Arc<dyn Command<C>>> {
117        self.inner
118            .commands
119            .read()
120            .await
121            .iter()
122            .map(|c| c.1.clone())
123            .collect()
124    }
125
126    pub async fn env(&self) -> HashMap<String, String> {
127        self.inner.env.read().await.clone()
128    }
129
130    pub async fn get_var(&self, n: &str) -> Option<String> {
131        self.inner.env.read().await.get(&n.to_lowercase()).cloned()
132    }
133
134    pub async fn set_var(&self, n: &str, v: &str) {
135        self.inner
136            .env
137            .write()
138            .await
139            .insert(n.to_lowercase(), v.to_string());
140    }
141
142    pub async fn unset_var(&self, n: &str) {
143        self.inner.env.write().await.remove(n);
144    }
145
146    pub async fn feed_slice(&self, cmd: &[String]) -> Result<(), String> {
147        if cmd.is_empty() {
148            return Ok(());
149        }
150
151        match self.inner.commands.read().await.get(&cmd[0]) {
152            Some(c) => {
153                if let Err(e) = c.run(self, cmd, &self.inner.ctx).await {
154                    if e == "exit" {
155                        return Err(e);
156                    }
157
158                    eprintln!("{}", e);
159                }
160            }
161            None => {
162                eprintln!("Command not found");
163            }
164        }
165
166        Ok(())
167    }
168
169    pub async fn feed_line(&self, line: &str) -> Result<(), String> {
170        let cmd = shlex::Shlex::new(line).collect::<Vec<String>>();
171        self.feed_slice(&cmd).await
172    }
173
174    pub async fn run(&self) -> Result<(), String> {
175        let event = self
176            .inner
177            .rl
178            .lock()
179            .await
180            .readline(&self.inner.prompt)
181            .await
182            .to_estring()?;
183
184        match event {
185            Event::Line(line) => {
186                return self.feed_line(&line).await;
187            }
188            Event::Ctrlc => {
189                return Err("CTRLC".to_string());
190            }
191            Event::Eof => {
192                return Err("EOF".to_string());
193            }
194            Event::Tab => {}
195        }
196
197        Ok(())
198    }
199}