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}