sea_shell/
lib.rs

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>>;