moros/src/usr/shell.rs

250 lines
8.5 KiB
Rust
Raw Normal View History

use crate::{api, sys, usr};
use crate::api::fs;
use crate::api::prompt::Prompt;
use crate::api::console::Style;
use alloc::format;
use alloc::vec::Vec;
use alloc::string::String;
2019-12-28 17:08:11 +00:00
// TODO: Scan /bin
const AUTOCOMPLETE_COMMANDS: [&str; 37] = [
"base64", "calc", "clear", "colors", "copy", "date", "delete", "dhcp", "disk", "edit", "env",
"exit", "geotime", "goto", "halt", "help", "hex", "host", "http", "httpd", "install", "ip",
"keyboard", "lisp", "list", "memory", "move", "net", "print", "read", "route", "shell",
"sleep", "tcp", "user", "vga", "write"
];
2020-01-01 08:07:09 +00:00
#[repr(u8)]
#[derive(PartialEq)]
2020-01-01 08:07:09 +00:00
pub enum ExitCode {
CommandSuccessful = 0,
CommandUnknown = 1,
CommandError = 2,
ShellExit = 255,
}
fn shell_completer(line: &str) -> Vec<String> {
let mut entries = Vec::new();
2019-12-31 12:10:03 +00:00
let args = split_args(line);
let i = args.len() - 1;
if args.len() == 1 { // Autocomplete command
for &cmd in &AUTOCOMPLETE_COMMANDS {
if let Some(entry) = cmd.strip_prefix(args[i]) {
entries.push(entry.into());
2019-12-29 09:34:08 +00:00
}
2019-12-31 12:10:03 +00:00
}
} else { // Autocomplete path
let pathname = fs::realpath(args[i]);
let dirname = fs::dirname(&pathname);
let filename = fs::filename(&pathname);
let sep = if dirname.ends_with('/') { "" } else { "/" };
if let Some(dir) = sys::fs::Dir::open(dirname) {
for entry in dir.entries() {
let name = entry.name();
if name.starts_with(filename) {
let end = if entry.is_dir() { "/" } else { "" };
let path = format!("{}{}{}{}", dirname, sep, name, end);
entries.push(path[pathname.len()..].into());
}
}
}
}
entries
}
pub fn prompt_string(success: bool) -> String {
let csi_color = Style::color("Magenta");
let csi_error = Style::color("Red");
let csi_reset = Style::reset();
format!("{}>{} ", if success { csi_color } else { csi_error }, csi_reset)
}
pub fn split_args(cmd: &str) -> Vec<&str> {
let mut args: Vec<&str> = Vec::new();
let mut i = 0;
let mut n = cmd.len();
let mut is_quote = false;
for (j, c) in cmd.char_indices() {
if c == '#' && !is_quote {
n = j; // Discard comments
break;
} else if c == ' ' && !is_quote {
if i != j {
args.push(&cmd[i..j]);
}
i = j + 1;
} else if c == '"' {
is_quote = !is_quote;
if !is_quote {
args.push(&cmd[i..j]);
}
i = j + 1;
}
}
if i < n {
if is_quote {
n -= 1;
2020-01-25 09:48:06 +00:00
}
args.push(&cmd[i..n]);
2020-01-25 09:48:06 +00:00
}
if n == 0 || cmd.ends_with(' ') {
args.push("");
2020-01-25 09:48:06 +00:00
}
args
}
2020-01-06 21:26:48 +00:00
fn change_dir(args: &[&str]) -> ExitCode {
match args.len() {
1 => {
println!("{}", sys::process::dir());
ExitCode::CommandSuccessful
},
2 => {
let mut pathname = fs::realpath(args[1]);
if pathname.len() > 1 {
pathname = pathname.trim_end_matches('/').into();
2020-01-06 21:26:48 +00:00
}
if sys::fs::Dir::open(&pathname).is_some() {
sys::process::set_dir(&pathname);
ExitCode::CommandSuccessful
} else {
println!("File not found '{}'", pathname);
ExitCode::CommandError
2020-01-06 21:26:48 +00:00
}
},
_ => {
ExitCode::CommandError
2020-01-01 08:07:09 +00:00
}
}
}
2020-01-01 08:07:09 +00:00
pub fn exec(cmd: &str) -> ExitCode {
let args = split_args(cmd);
match args[0] {
"" => ExitCode::CommandError,
"a" | "alias" => ExitCode::CommandUnknown,
"b" => ExitCode::CommandUnknown,
"c" | "copy" => usr::copy::main(&args),
"d" | "del" | "delete" => usr::delete::main(&args),
"e" | "edit" => usr::editor::main(&args),
"f" | "find" => usr::find::main(&args),
"g" | "go" | "goto" => change_dir(&args),
"h" | "help" => usr::help::main(&args),
"i" => ExitCode::CommandUnknown,
"j" | "jump" => ExitCode::CommandUnknown,
"k" | "kill" => ExitCode::CommandUnknown,
"l" | "list" => usr::list::main(&args),
"m" | "move" => usr::r#move::main(&args),
"n" => ExitCode::CommandUnknown,
"o" => ExitCode::CommandUnknown,
"p" | "print" => usr::print::main(&args),
"q" | "quit" | "exit" => ExitCode::ShellExit,
"r" | "read" => usr::read::main(&args),
"s" => ExitCode::CommandUnknown,
"t" => ExitCode::CommandUnknown,
"u" => ExitCode::CommandUnknown,
"v" => ExitCode::CommandUnknown,
"w" | "write" => usr::write::main(&args),
"x" => ExitCode::CommandUnknown,
"y" => ExitCode::CommandUnknown,
"z" => ExitCode::CommandUnknown,
"vga" => usr::vga::main(&args),
"sh" | "shell" => usr::shell::main(&args),
"sleep" => usr::sleep::main(&args),
"clear" => usr::clear::main(&args),
"calc" => usr::calc::main(&args),
"base64" => usr::base64::main(&args),
"date" => usr::date::main(&args),
"env" => usr::env::main(&args),
"halt" => usr::halt::main(&args),
"hex" => usr::hex::main(&args),
"net" => usr::net::main(&args),
"route" => usr::route::main(&args),
"dhcp" => usr::dhcp::main(&args),
"http" => usr::http::main(&args),
"httpd" => usr::httpd::main(&args),
"tcp" => usr::tcp::main(&args),
"host" => usr::host::main(&args),
"install" => usr::install::main(&args),
"ip" => usr::ip::main(&args),
"geotime" => usr::geotime::main(&args),
"colors" => usr::colors::main(&args),
"dsk" | "disk" => usr::disk::main(&args),
"user" => usr::user::main(&args),
"mem" | "memory" => usr::mem::main(&args),
"kb" | "keyboard" => usr::keyboard::main(&args),
"lisp" => usr::lisp::main(&args),
"chess" => usr::chess::main(&args),
"beep" => usr::beep::main(&args),
"elf" => usr::elf::main(&args),
cmd => {
if api::process::spawn(cmd).is_ok() {
ExitCode::CommandSuccessful
} else {
ExitCode::CommandUnknown
}
}
2019-12-28 17:08:11 +00:00
}
}
pub fn run() -> usr::shell::ExitCode {
println!();
let mut prompt = Prompt::new();
let history_file = "~/.shell-history";
prompt.history.load(history_file);
prompt.completion.set(&shell_completer);
let mut success = true;
while let Some(cmd) = prompt.input(&prompt_string(success)) {
match exec(&cmd) {
ExitCode::CommandSuccessful => {
success = true;
},
ExitCode::ShellExit => {
break;
},
_ => {
success = false;
},
}
prompt.history.add(&cmd);
prompt.history.save(history_file);
2021-07-26 21:36:52 +00:00
sys::console::drain();
println!();
}
print!("\x1b[2J\x1b[1;1H"); // Clear screen and move cursor to top
ExitCode::CommandSuccessful
2019-12-28 17:08:11 +00:00
}
2020-01-01 08:07:24 +00:00
pub fn main(args: &[&str]) -> ExitCode {
match args.len() {
1 => {
run()
2020-01-01 08:07:24 +00:00
},
2 => {
let pathname = args[1];
if let Ok(contents) = api::fs::read_to_string(pathname) {
for line in contents.split('\n') {
if !line.is_empty() {
exec(line);
2020-01-01 08:55:33 +00:00
}
2020-01-01 08:07:24 +00:00
}
ExitCode::CommandSuccessful
} else {
println!("File not found '{}'", pathname);
2020-01-01 08:07:24 +00:00
ExitCode::CommandError
}
},
_ => {
ExitCode::CommandError
},
}
}