2021-07-24 23:07:12 +00:00
|
|
|
use crate::api::prompt::Prompt;
|
2021-07-17 18:58:04 +00:00
|
|
|
use crate::{sys, usr};
|
2021-07-16 08:19:18 +00:00
|
|
|
use crate::api::console::Style;
|
2020-02-07 17:36:27 +00:00
|
|
|
use alloc::format;
|
2020-01-20 09:44:59 +00:00
|
|
|
use alloc::vec::Vec;
|
|
|
|
use alloc::string::String;
|
2019-12-28 17:08:11 +00:00
|
|
|
|
2020-07-10 06:18:34 +00:00
|
|
|
// TODO: Scan /bin
|
2021-07-24 23:07:12 +00:00
|
|
|
const AUTOCOMPLETE_COMMANDS: [&str; 35] = [
|
|
|
|
"base64", "clear", "colors", "copy", "date", "delete", "dhcp", "disk", "edit", "env", "exit",
|
|
|
|
"geotime", "goto", "halt", "help", "hex", "host", "http", "httpd", "install", "ip", "lisp",
|
|
|
|
"list", "memory", "move", "net", "print", "read", "route", "shell", "sleep", "tcp", "user",
|
|
|
|
"vga", "write"
|
2020-07-10 06:18:34 +00:00
|
|
|
];
|
|
|
|
|
2020-01-01 08:07:09 +00:00
|
|
|
#[repr(u8)]
|
2020-07-05 11:31:49 +00:00
|
|
|
#[derive(PartialEq)]
|
2020-01-01 08:07:09 +00:00
|
|
|
pub enum ExitCode {
|
|
|
|
CommandSuccessful = 0,
|
|
|
|
CommandUnknown = 1,
|
|
|
|
CommandError = 2,
|
|
|
|
ShellExit = 255,
|
|
|
|
}
|
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
fn shell_completer(line: &str) -> Vec<String> {
|
|
|
|
let mut entries = Vec::new();
|
2019-12-31 12:10:03 +00:00
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
let args = split_args(&line);
|
|
|
|
let i = args.len() - 1;
|
|
|
|
if args.len() == 1 { // Autocomplete command
|
|
|
|
for &cmd in &AUTOCOMPLETE_COMMANDS {
|
|
|
|
if cmd.starts_with(args[i]) {
|
|
|
|
entries.push(cmd[args[i].len()..].into());
|
2019-12-29 09:34:08 +00:00
|
|
|
}
|
2019-12-31 12:10:03 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
} else { // Autocomplete path
|
|
|
|
let pathname = sys::fs::realpath(args[i]);
|
|
|
|
let dirname = sys::fs::dirname(&pathname);
|
|
|
|
let filename = sys::fs::filename(&pathname);
|
|
|
|
let sep = if dirname.ends_with("/") { "" } else { "/" };
|
|
|
|
if let Some(dir) = sys::fs::Dir::open(dirname) {
|
|
|
|
for entry in dir.read() {
|
|
|
|
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());
|
2020-02-07 17:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
entries
|
|
|
|
}
|
2020-02-07 17:36:27 +00:00
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
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)
|
|
|
|
}
|
2020-02-07 17:36:27 +00:00
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
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]);
|
2020-02-10 20:29:07 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
i = j + 1;
|
|
|
|
} else if c == '"' {
|
|
|
|
is_quote = !is_quote;
|
|
|
|
if !is_quote {
|
|
|
|
args.push(&cmd[i..j]);
|
|
|
|
}
|
|
|
|
i = j + 1;
|
2020-02-10 20:29:07 +00:00
|
|
|
}
|
2020-02-07 17:36:27 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
if i < n {
|
|
|
|
if is_quote {
|
|
|
|
n -= 1;
|
2020-01-25 09:48:06 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
args.push(&cmd[i..n]);
|
2020-01-25 09:48:06 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
if n == 0 || cmd.chars().last().unwrap() == ' ' {
|
|
|
|
args.push("");
|
2020-01-25 09:48:06 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
args
|
|
|
|
}
|
2020-01-06 21:26:48 +00:00
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
fn change_dir(args: &[&str]) -> ExitCode {
|
|
|
|
match args.len() {
|
|
|
|
1 => {
|
|
|
|
print!("{}\n", sys::process::dir());
|
|
|
|
ExitCode::CommandSuccessful
|
|
|
|
},
|
|
|
|
2 => {
|
|
|
|
let mut pathname = sys::fs::realpath(args[1]);
|
|
|
|
if pathname.len() > 1 {
|
|
|
|
pathname = pathname.trim_end_matches('/').into();
|
2020-01-06 21:26:48 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
if sys::fs::Dir::open(&pathname).is_some() {
|
|
|
|
sys::process::set_dir(&pathname);
|
|
|
|
ExitCode::CommandSuccessful
|
|
|
|
} else {
|
|
|
|
print!("File not found '{}'\n", pathname);
|
|
|
|
ExitCode::CommandError
|
2020-01-06 21:26:48 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
ExitCode::CommandError
|
2020-01-01 08:07:09 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
}
|
2020-01-01 08:07:09 +00:00
|
|
|
|
2021-07-24 23:07:12 +00:00
|
|
|
pub fn exec(cmd: &str) -> ExitCode {
|
|
|
|
let args = split_args(cmd);
|
|
|
|
|
|
|
|
match args[0] {
|
|
|
|
"" => ExitCode::CommandSuccessful,
|
|
|
|
"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" => ExitCode::CommandUnknown,
|
|
|
|
"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),
|
|
|
|
"shell" => usr::shell::main(&args),
|
|
|
|
"sleep" => usr::sleep::main(&args),
|
|
|
|
"clear" => usr::clear::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),
|
|
|
|
"disk" => usr::disk::main(&args),
|
|
|
|
"user" => usr::user::main(&args),
|
|
|
|
"mem" | "memory" => usr::mem::main(&args),
|
|
|
|
"lisp" => usr::lisp::main(&args),
|
|
|
|
_ => ExitCode::CommandUnknown,
|
2019-12-28 17:08:11 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
}
|
2020-02-10 20:29:07 +00:00
|
|
|
|
2021-07-24 23:07:12 +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;
|
|
|
|
prompt.history.add(&cmd);
|
|
|
|
prompt.history.save(history_file);
|
2020-02-10 20:29:07 +00:00
|
|
|
},
|
2021-07-24 23:07:12 +00:00
|
|
|
ExitCode::ShellExit => {
|
|
|
|
break;
|
2020-02-10 20:29:07 +00:00
|
|
|
},
|
|
|
|
_ => {
|
2021-07-24 23:07:12 +00:00
|
|
|
success = false;
|
|
|
|
},
|
2020-02-10 20:29:07 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
println!();
|
2020-02-10 20:29:07 +00:00
|
|
|
}
|
2021-07-24 23:07:12 +00:00
|
|
|
sys::vga::clear_screen();
|
|
|
|
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 => {
|
2021-07-24 23:07:12 +00:00
|
|
|
return run();
|
2020-01-01 08:07:24 +00:00
|
|
|
},
|
|
|
|
2 => {
|
|
|
|
let pathname = args[1];
|
2021-07-17 18:48:01 +00:00
|
|
|
if let Some(mut file) = sys::fs::File::open(pathname) {
|
2020-01-17 18:52:48 +00:00
|
|
|
for line in file.read_to_string().split("\n") {
|
2020-01-01 08:55:33 +00:00
|
|
|
if line.len() > 0 {
|
2021-07-24 23:07:12 +00:00
|
|
|
exec(line);
|
2020-01-01 08:55:33 +00:00
|
|
|
}
|
2020-01-01 08:07:24 +00:00
|
|
|
}
|
|
|
|
ExitCode::CommandSuccessful
|
|
|
|
} else {
|
|
|
|
print!("File not found '{}'\n", pathname);
|
|
|
|
ExitCode::CommandError
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
ExitCode::CommandError
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|