1#![cfg_attr(not(feature = "std"), no_std)]
2
3pub(crate) mod re_exports {
4 #[cfg(not(feature = "std"))]
5 pub extern crate alloc;
6 #[cfg(feature = "std")]
7 pub use std as alloc;
8
9 pub use crate::arg_parser::Arg;
10 pub use crate::logger::create_logger_from_logger;
11 pub use alloc::{
12 boxed::Box,
13 collections::BTreeMap,
14 format,
15 string::{String, ToString},
16 sync::Arc,
17 vec::Vec,
18 };
19 pub use core::{
20 fmt::{self, Display, Formatter, Result as FmtResult},
21 future::Future,
22 pin::Pin,
23 };
24 pub use itertools::Itertools;
25}
26
27use core::future::Future as Future_;
28use re_exports::*;
29
30mod state;
31
32pub use state::State;
33
34pub mod arg_parser;
35pub mod commands;
36pub mod logger;
37
38pub(crate) mod macro_helpers;
39
40pub const VERSION: &str = env!("CARGO_PKG_VERSION");
41pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
42
43#[derive(Clone)]
44pub struct SeaShell<'a> {
45 pub state: State,
46 #[allow(clippy::type_complexity)]
47 pub exit_handler: Arc<Box<dyn Fn(i32, Self) -> Option<Self> + 'a>>,
48 pub logger: Arc<Box<dyn logger::Logger + 'a>>,
49}
50
51impl<'a> SeaShell<'a> {
52 pub fn new(
53 exit_handler: impl Fn(i32, Self) -> Option<Self> + 'a,
54 logger_: impl logger::Logger + 'a,
55 unicode_supported: bool,
56 ) -> Self {
57 create_logger_from_logger!(logger_, true);
58
59 log!(info, "Welcome to Sea Shell version: {}", VERSION);
60 log!(info, DESCRIPTION);
61 log!(info, "Type 'help' for a list of commands");
62 log!();
63
64 Self {
65 exit_handler: Arc::new(Box::new(exit_handler)),
66 state: State::new(commands::BUILT_IN_COMMANDS, unicode_supported),
67 logger: Arc::new(Box::new(logger_)),
68 }
69 }
70
71 pub async fn handle_command(&mut self, input: impl AsRef<str>) {
72 let input = input.as_ref().trim();
73
74 if input.is_empty() {
75 return;
76 }
77
78 let mut input_ = input.split_whitespace().filter_map(|input| {
79 let trimmed = input.trim();
80
81 if trimmed.is_empty() {
82 None
83 } else {
84 Some(trimmed.into())
85 }
86 });
87
88 let command = input_.next().unwrap();
89
90 let args = input_.collect::<Vec<_>>();
91
92 self.state.history.push(input.into());
93
94 create_logger_from_logger!(self.logger, true);
95 let code = match self.get_command(&command) {
96 Some(command_) => {
97 log!(debug, "executing: {}...", command);
98
99 let out = (command_.handler)(self.clone(), args).await;
100
101 if let Some(self_) = out.0 {
102 *self = self_;
103 }
104
105 out.1
106 }
107 None => {
108 log!(error, "command not found: {}", command);
109
110 1
111 }
112 };
113
114 self
115 .state
116 .set_environment_variable("exit", code.to_string());
117 }
118
119 pub fn get_command(&self, command: impl AsRef<str>) -> Option<&Command> {
120 let command = command.as_ref();
121
122 self.state.commands.iter().find(|c| c.name == command)
123 }
124}
125
126#[derive(Clone)]
127pub struct Command {
128 pub name: &'static str,
129 pub description: &'static str,
130 pub args: &'static [Arg<'static>],
131 #[allow(clippy::type_complexity)]
132 pub handler: for<'a> fn(SeaShell<'a>, Vec<String>) -> Future<'a, (Option<SeaShell<'a>>, i32)>,
133}
134
135impl Display for Command {
136 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
137 let mut help_text = String::new();
138
139 help_text.push_str(self.name);
140
141 if !self.args.is_empty() {
142 help_text.push(' ');
143
144 for arg in self.args {
145 help_text.push_str(&format!("{}", arg));
146 }
147 }
148
149 help_text.push_str(": ");
150 help_text.push_str(self.description);
151
152 write!(f, "{}", help_text)
153 }
154}
155
156pub(crate) type Future<'a, T> = Pin<Box<dyn Future_<Output = T> + 'a>>;