mirror of
https://github.com/vinc/moros.git
synced 2024-06-15 05:36:38 +00:00
Add shell aliases (#357)
* Add shell aliases
* Fix env output error
* Fix sort
* Read aliases from config file
* Simplify arguments parsing
* Fix test
* Clone params to spawn syscall
* Run clippy
* Revert "Clone params to spawn syscall"
This reverts commit 4c91bea196
.
* Disable binary stripping
* Remove exit alias
* Update doc
This commit is contained in:
parent
cd6cdceb73
commit
82882ec355
2
Makefile
2
Makefile
|
@ -28,7 +28,7 @@ user-rust:
|
||||||
-C relocation-model=static
|
-C relocation-model=static
|
||||||
basename -s .rs src/bin/*.rs | xargs -I {} \
|
basename -s .rs src/bin/*.rs | xargs -I {} \
|
||||||
cp target/x86_64-moros/release/{} dsk/bin/{}
|
cp target/x86_64-moros/release/{} dsk/bin/{}
|
||||||
strip dsk/bin/*
|
#strip dsk/bin/*
|
||||||
|
|
||||||
bin = target/x86_64-moros/release/bootimage-moros.bin
|
bin = target/x86_64-moros/release/bootimage-moros.bin
|
||||||
img = disk.img
|
img = disk.img
|
||||||
|
|
|
@ -46,7 +46,7 @@ MOROS Lisp v0.1.0
|
||||||
> (+ 1 2)
|
> (+ 1 2)
|
||||||
3
|
3
|
||||||
|
|
||||||
> (exit)
|
> (quit)
|
||||||
```
|
```
|
||||||
|
|
||||||
And it can execute a file. For example a file located in `/tmp/fibonacci.lsp`
|
And it can execute a file. For example a file located in `/tmp/fibonacci.lsp`
|
||||||
|
|
23
doc/shell.md
23
doc/shell.md
|
@ -1,15 +1,20 @@
|
||||||
# MOROS Shell
|
# MOROS Shell
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
The shell will read `/ini/shell.sh` during initialization to setup its
|
||||||
|
configuration.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
The main commands have a long name, a one-letter alias, and may have
|
The main commands have a long name, a one-letter alias, and may have
|
||||||
additional common aliases.
|
additional common aliases.
|
||||||
|
|
||||||
<!--
|
|
||||||
**Alias** command:
|
**Alias** command:
|
||||||
|
|
||||||
> alias d delete
|
> alias d delete
|
||||||
|
|
||||||
|
<!--
|
||||||
**Append** to file:
|
**Append** to file:
|
||||||
|
|
||||||
> a a.txt
|
> a a.txt
|
||||||
|
@ -130,20 +135,26 @@ Which is more efficient than doing:
|
||||||
|
|
||||||
Setting a variable in the shell environment is done with the following command:
|
Setting a variable in the shell environment is done with the following command:
|
||||||
|
|
||||||
> foo = "world"
|
> set foo 42
|
||||||
|
|
||||||
And accessing that variable is done with the `$` operator:
|
> set bar "Alice and Bob"
|
||||||
|
|
||||||
|
And accessing a variable is done with the `$` operator:
|
||||||
|
|
||||||
> print $foo
|
> print $foo
|
||||||
world
|
42
|
||||||
|
|
||||||
> print "hello $foo"
|
> print "Hello $bar"
|
||||||
hello world
|
Hello Alice and Bob
|
||||||
|
|
||||||
The process environment is copied to the shell environment when a session is
|
The process environment is copied to the shell environment when a session is
|
||||||
started. By convention a process env var should be in uppercase and a shell
|
started. By convention a process env var should be in uppercase and a shell
|
||||||
env var should be lowercase.
|
env var should be lowercase.
|
||||||
|
|
||||||
|
Unsetting a variable is done like this:
|
||||||
|
|
||||||
|
> unset foo
|
||||||
|
|
||||||
## Globbing
|
## Globbing
|
||||||
|
|
||||||
MOROS Shell support filename expansion or globbing for `*` and `?` wildcard
|
MOROS Shell support filename expansion or globbing for `*` and `?` wildcard
|
||||||
|
|
BIN
dsk/bin/clear
BIN
dsk/bin/clear
Binary file not shown.
BIN
dsk/bin/halt
BIN
dsk/bin/halt
Binary file not shown.
BIN
dsk/bin/hello
BIN
dsk/bin/hello
Binary file not shown.
BIN
dsk/bin/print
BIN
dsk/bin/print
Binary file not shown.
BIN
dsk/bin/reboot
BIN
dsk/bin/reboot
Binary file not shown.
BIN
dsk/bin/sleep
BIN
dsk/bin/sleep
Binary file not shown.
28
dsk/ini/shell.sh
Normal file
28
dsk/ini/shell.sh
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Command shortcuts
|
||||||
|
alias p print
|
||||||
|
alias c copy
|
||||||
|
alias d delete
|
||||||
|
alias del delete
|
||||||
|
alias e edit
|
||||||
|
alias f find
|
||||||
|
alias g goto
|
||||||
|
alias go goto
|
||||||
|
alias h help
|
||||||
|
alias l list
|
||||||
|
alias m move
|
||||||
|
alias q quit
|
||||||
|
alias r read
|
||||||
|
alias w write
|
||||||
|
alias sh shell
|
||||||
|
alias dsk disk
|
||||||
|
alias mem memory
|
||||||
|
alias kbd keyboard
|
||||||
|
|
||||||
|
# Unix compatibility
|
||||||
|
# alias cd goto
|
||||||
|
# alias cp copy
|
||||||
|
# alias echo print
|
||||||
|
# alias exit quit
|
||||||
|
# alias ls list
|
||||||
|
# alias mv move
|
||||||
|
# alias rm delete
|
|
@ -14,10 +14,9 @@ fn main(boot_info: &'static BootInfo) -> ! {
|
||||||
print!("\x1b[?25h"); // Enable cursor
|
print!("\x1b[?25h"); // Enable cursor
|
||||||
loop {
|
loop {
|
||||||
if let Some(cmd) = option_env!("MOROS_CMD") {
|
if let Some(cmd) = option_env!("MOROS_CMD") {
|
||||||
let mut env = usr::shell::default_env();
|
|
||||||
let prompt = usr::shell::prompt_string(true);
|
let prompt = usr::shell::prompt_string(true);
|
||||||
println!("{}{}", prompt, cmd);
|
println!("{}{}", prompt, cmd);
|
||||||
usr::shell::exec(cmd, &mut env);
|
usr::shell::exec(cmd);
|
||||||
sys::acpi::shutdown();
|
sys::acpi::shutdown();
|
||||||
} else {
|
} else {
|
||||||
user_boot();
|
user_boot();
|
||||||
|
|
|
@ -118,7 +118,7 @@ fn repl() -> usr::shell::ExitCode {
|
||||||
prompt.history.load(history_file);
|
prompt.history.load(history_file);
|
||||||
|
|
||||||
while let Some(line) = prompt.input(&prompt_string) {
|
while let Some(line) = prompt.input(&prompt_string) {
|
||||||
if line == "exit" || line == "quit" {
|
if line == "quit" {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl Chess {
|
||||||
while let Some(cmd) = prompt.input(&prompt_string) {
|
while let Some(cmd) = prompt.input(&prompt_string) {
|
||||||
let args: Vec<&str> = cmd.trim().split(' ').collect();
|
let args: Vec<&str> = cmd.trim().split(' ').collect();
|
||||||
match args[0] {
|
match args[0] {
|
||||||
"q" | "quit" | "exit" => break,
|
"q" | "quit" => break,
|
||||||
"h" | "help" => self.cmd_help(args),
|
"h" | "help" => self.cmd_help(args),
|
||||||
"i" | "init" => self.cmd_init(args),
|
"i" | "init" => self.cmd_init(args),
|
||||||
"t" | "time" => self.cmd_time(args),
|
"t" | "time" => self.cmd_time(args),
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
use crate::{sys, usr};
|
use crate::{sys, usr};
|
||||||
|
|
||||||
pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||||
if args.len() == 1 {
|
match args.len() {
|
||||||
for (key, val) in sys::process::envs() {
|
1 => {
|
||||||
println!("{}={}", key, val);
|
for (key, val) in sys::process::envs() {
|
||||||
|
println!("{:10} \"{}\"", key, val);
|
||||||
|
}
|
||||||
|
usr::shell::ExitCode::CommandSuccessful
|
||||||
}
|
}
|
||||||
} else {
|
2 => {
|
||||||
for arg in args[1..].iter() {
|
let key = args[1];
|
||||||
if let Some(i) = arg.find('=') {
|
if let Some(val) = sys::process::env(key) {
|
||||||
let (key, mut val) = arg.split_at(i);
|
println!("{}", val);
|
||||||
val = &val[1..];
|
usr::shell::ExitCode::CommandSuccessful
|
||||||
sys::process::set_env(key, val);
|
|
||||||
println!("{}={}", key, val);
|
|
||||||
} else {
|
} else {
|
||||||
error!("Error: could not parse '{}'", arg);
|
error!("Could not get '{}'", key);
|
||||||
return usr::shell::ExitCode::CommandError;
|
usr::shell::ExitCode::CommandError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3 => {
|
||||||
|
sys::process::set_env(args[1], args[2]);
|
||||||
|
usr::shell::ExitCode::CommandSuccessful
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Invalid number of arguments");
|
||||||
|
usr::shell::ExitCode::CommandError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
usr::shell::ExitCode::CommandSuccessful
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,11 @@ pub fn copy_files(verbose: bool) {
|
||||||
create_dev("/dev/random", DeviceType::Random, verbose);
|
create_dev("/dev/random", DeviceType::Random, verbose);
|
||||||
create_dev("/dev/console", DeviceType::Console, verbose);
|
create_dev("/dev/console", DeviceType::Console, verbose);
|
||||||
|
|
||||||
copy_file("/ini/boot.sh", include_bytes!("../../dsk/ini/boot.sh"), verbose);
|
|
||||||
copy_file("/ini/banner.txt", include_bytes!("../../dsk/ini/banner.txt"), verbose);
|
copy_file("/ini/banner.txt", include_bytes!("../../dsk/ini/banner.txt"), verbose);
|
||||||
copy_file("/ini/version.txt", include_bytes!("../../dsk/ini/version.txt"), verbose);
|
copy_file("/ini/boot.sh", include_bytes!("../../dsk/ini/boot.sh"), verbose);
|
||||||
copy_file("/ini/palette.csv", include_bytes!("../../dsk/ini/palette.csv"), verbose);
|
copy_file("/ini/palette.csv", include_bytes!("../../dsk/ini/palette.csv"), verbose);
|
||||||
|
copy_file("/ini/shell.sh", include_bytes!("../../dsk/ini/shell.sh"), verbose);
|
||||||
|
copy_file("/ini/version.txt", include_bytes!("../../dsk/ini/version.txt"), verbose);
|
||||||
|
|
||||||
create_dir("/ini/lisp", verbose);
|
create_dir("/ini/lisp", verbose);
|
||||||
copy_file("/ini/lisp/core.lsp", include_bytes!("../../dsk/ini/lisp/core.lsp"), verbose);
|
copy_file("/ini/lisp/core.lsp", include_bytes!("../../dsk/ini/lisp/core.lsp"), verbose);
|
||||||
|
|
|
@ -294,8 +294,7 @@ fn default_env() -> Rc<RefCell<Env>> {
|
||||||
data.insert("system".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
|
data.insert("system".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
|
||||||
ensure_length_eq!(args, 1);
|
ensure_length_eq!(args, 1);
|
||||||
let cmd = string(&args[0])?;
|
let cmd = string(&args[0])?;
|
||||||
let mut env = usr::shell::default_env();
|
let res = usr::shell::exec(&cmd);
|
||||||
let res = usr::shell::exec(&cmd, &mut env);
|
|
||||||
Ok(Exp::Num(res as u8 as f64))
|
Ok(Exp::Num(res as u8 as f64))
|
||||||
}));
|
}));
|
||||||
data.insert("print".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
|
data.insert("print".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
|
||||||
|
@ -673,7 +672,7 @@ fn repl(env: &mut Rc<RefCell<Env>>) -> usr::shell::ExitCode {
|
||||||
prompt.completion.set(&lisp_completer);
|
prompt.completion.set(&lisp_completer);
|
||||||
|
|
||||||
while let Some(line) = prompt.input(&prompt_string) {
|
while let Some(line) = prompt.input(&prompt_string) {
|
||||||
if line == "(exit)" || line == "(quit)" {
|
if line == "(quit)" {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
|
|
284
src/usr/shell.rs
284
src/usr/shell.rs
|
@ -14,13 +14,13 @@ use alloc::string::{String, ToString};
|
||||||
// TODO: Scan /bin
|
// TODO: Scan /bin
|
||||||
const AUTOCOMPLETE_COMMANDS: [&str; 35] = [
|
const AUTOCOMPLETE_COMMANDS: [&str; 35] = [
|
||||||
"2048", "base64", "calc", "colors", "copy", "date", "delete", "dhcp", "disk", "edit",
|
"2048", "base64", "calc", "colors", "copy", "date", "delete", "dhcp", "disk", "edit",
|
||||||
"env", "exit", "geotime", "goto", "help", "hex", "host", "http", "httpd", "install",
|
"env", "geotime", "goto", "help", "hex", "host", "http", "httpd", "install",
|
||||||
"keyboard", "lisp", "list", "memory", "move", "net", "pci", "read",
|
"keyboard", "lisp", "list", "memory", "move", "net", "pci", "quit", "read",
|
||||||
"shell", "socket", "tcp", "time", "user", "vga", "write"
|
"shell", "socket", "tcp", "time", "user", "vga", "write"
|
||||||
];
|
];
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum ExitCode {
|
pub enum ExitCode {
|
||||||
CommandSuccessful = 0,
|
CommandSuccessful = 0,
|
||||||
CommandUnknown = 1,
|
CommandUnknown = 1,
|
||||||
|
@ -28,6 +28,23 @@ pub enum ExitCode {
|
||||||
ShellExit = 255,
|
ShellExit = 255,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
env: BTreeMap<String, String>,
|
||||||
|
aliases: BTreeMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
fn new() -> Config {
|
||||||
|
let aliases = BTreeMap::new();
|
||||||
|
let mut env = BTreeMap::new();
|
||||||
|
for (key, val) in sys::process::envs() {
|
||||||
|
env.insert(key, val); // Copy the process environment to the shell environment
|
||||||
|
}
|
||||||
|
env.insert("DIR".to_string(), sys::process::dir());
|
||||||
|
Config { env, aliases }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn autocomplete_commands() -> Vec<String> {
|
fn autocomplete_commands() -> Vec<String> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
for cmd in AUTOCOMPLETE_COMMANDS {
|
for cmd in AUTOCOMPLETE_COMMANDS {
|
||||||
|
@ -38,7 +55,6 @@ fn autocomplete_commands() -> Vec<String> {
|
||||||
res.push(file.name());
|
res.push(file.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.sort();
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +85,7 @@ fn shell_completer(line: &str) -> Vec<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
entries.sort();
|
||||||
entries
|
entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,19 +96,6 @@ pub fn prompt_string(success: bool) -> String {
|
||||||
format!("{}>{} ", if success { csi_color } else { csi_error }, csi_reset)
|
format!("{}>{} ", if success { csi_color } else { csi_error }, csi_reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_env() -> BTreeMap<String, String> {
|
|
||||||
let mut env = BTreeMap::new();
|
|
||||||
|
|
||||||
// Copy the process environment to the shell environment
|
|
||||||
for (key, val) in sys::process::envs() {
|
|
||||||
env.insert(key, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
env.insert("DIR".to_string(), sys::process::dir());
|
|
||||||
|
|
||||||
env
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_globbing(arg: &str) -> bool {
|
fn is_globbing(arg: &str) -> bool {
|
||||||
let arg: Vec<char> = arg.chars().collect();
|
let arg: Vec<char> = arg.chars().collect();
|
||||||
let n = arg.len();
|
let n = arg.len();
|
||||||
|
@ -124,10 +128,10 @@ fn glob_to_regex(pattern: &str) -> String {
|
||||||
fn glob(arg: &str) -> Vec<String> {
|
fn glob(arg: &str) -> Vec<String> {
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
if is_globbing(arg) {
|
if is_globbing(arg) {
|
||||||
let (dir, pattern) = if arg.contains("/") {
|
let (dir, pattern) = if arg.contains('/') {
|
||||||
(fs::dirname(&arg).to_string(), fs::filename(&arg).to_string())
|
(fs::dirname(arg).to_string(), fs::filename(arg).to_string())
|
||||||
} else {
|
} else {
|
||||||
(sys::process::dir().clone(), arg.to_string())
|
(sys::process::dir(), arg.to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
let re = Regex::new(&glob_to_regex(&pattern));
|
let re = Regex::new(&glob_to_regex(&pattern));
|
||||||
|
@ -192,7 +196,7 @@ pub fn split_args(cmd: &str) -> Vec<String> {
|
||||||
args
|
args
|
||||||
}
|
}
|
||||||
|
|
||||||
fn proc(args: &[&str]) -> ExitCode {
|
fn cmd_proc(args: &[&str]) -> ExitCode {
|
||||||
match args.len() {
|
match args.len() {
|
||||||
1 => {
|
1 => {
|
||||||
ExitCode::CommandSuccessful
|
ExitCode::CommandSuccessful
|
||||||
|
@ -222,7 +226,7 @@ fn proc(args: &[&str]) -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_dir(args: &[&str], env: &mut BTreeMap<String, String>) -> ExitCode {
|
fn cmd_change_dir(args: &[&str], config: &mut Config) -> ExitCode {
|
||||||
match args.len() {
|
match args.len() {
|
||||||
1 => {
|
1 => {
|
||||||
println!("{}", sys::process::dir());
|
println!("{}", sys::process::dir());
|
||||||
|
@ -235,7 +239,7 @@ fn change_dir(args: &[&str], env: &mut BTreeMap<String, String>) -> ExitCode {
|
||||||
}
|
}
|
||||||
if api::fs::is_dir(&pathname) {
|
if api::fs::is_dir(&pathname) {
|
||||||
sys::process::set_dir(&pathname);
|
sys::process::set_dir(&pathname);
|
||||||
env.insert("DIR".to_string(), sys::process::dir());
|
config.env.insert("DIR".to_string(), sys::process::dir());
|
||||||
ExitCode::CommandSuccessful
|
ExitCode::CommandSuccessful
|
||||||
} else {
|
} else {
|
||||||
error!("File not found '{}'", pathname);
|
error!("File not found '{}'", pathname);
|
||||||
|
@ -248,27 +252,86 @@ fn change_dir(args: &[&str], env: &mut BTreeMap<String, String>) -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
|
fn cmd_alias(args: &[&str], config: &mut Config) -> ExitCode {
|
||||||
|
if args.len() != 3 {
|
||||||
|
let csi_option = Style::color("LightCyan");
|
||||||
|
let csi_title = Style::color("Yellow");
|
||||||
|
let csi_reset = Style::reset();
|
||||||
|
println!("{}Usage:{} alias {}<key> <val>{1}", csi_title, csi_reset, csi_option);
|
||||||
|
return usr::shell::ExitCode::CommandError;
|
||||||
|
}
|
||||||
|
config.aliases.insert(args[1].to_string(), args[2].to_string());
|
||||||
|
ExitCode::CommandSuccessful
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_unalias(args: &[&str], config: &mut Config) -> ExitCode {
|
||||||
|
if args.len() != 2 {
|
||||||
|
let csi_option = Style::color("LightCyan");
|
||||||
|
let csi_title = Style::color("Yellow");
|
||||||
|
let csi_reset = Style::reset();
|
||||||
|
println!("{}Usage:{} unalias {}<key>{1}", csi_title, csi_reset, csi_option);
|
||||||
|
return usr::shell::ExitCode::CommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.aliases.remove(&args[1].to_string()).is_none() {
|
||||||
|
error!("Error: could not unalias '{}'", args[1]);
|
||||||
|
return usr::shell::ExitCode::CommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExitCode::CommandSuccessful
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_set(args: &[&str], config: &mut Config) -> ExitCode {
|
||||||
|
if args.len() != 3 {
|
||||||
|
let csi_option = Style::color("LightCyan");
|
||||||
|
let csi_title = Style::color("Yellow");
|
||||||
|
let csi_reset = Style::reset();
|
||||||
|
println!("{}Usage:{} set {}<key> <val>{1}", csi_title, csi_reset, csi_option);
|
||||||
|
return usr::shell::ExitCode::CommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.env.insert(args[1].to_string(), args[2].to_string());
|
||||||
|
ExitCode::CommandSuccessful
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_unset(args: &[&str], config: &mut Config) -> ExitCode {
|
||||||
|
if args.len() != 2 {
|
||||||
|
let csi_option = Style::color("LightCyan");
|
||||||
|
let csi_title = Style::color("Yellow");
|
||||||
|
let csi_reset = Style::reset();
|
||||||
|
println!("{}Usage:{} unset {}<key>{1}", csi_title, csi_reset, csi_option);
|
||||||
|
return usr::shell::ExitCode::CommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.env.remove(&args[1].to_string()).is_none() {
|
||||||
|
error!("Error: could not unset '{}'", args[1]);
|
||||||
|
return usr::shell::ExitCode::CommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExitCode::CommandSuccessful
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec_with_config(cmd: &str, config: &mut Config) -> ExitCode {
|
||||||
let mut cmd = cmd.to_string();
|
let mut cmd = cmd.to_string();
|
||||||
|
|
||||||
// Replace `$key` with its value in the environment or an empty string
|
// Replace `$key` with its value in the environment or an empty string
|
||||||
let re = Regex::new("\\$\\w+");
|
let re = Regex::new("\\$\\w+");
|
||||||
while let Some((a, b)) = re.find(&cmd) {
|
while let Some((a, b)) = re.find(&cmd) {
|
||||||
let key: String = cmd.chars().skip(a + 1).take(b - a - 1).collect();
|
let key: String = cmd.chars().skip(a + 1).take(b - a - 1).collect();
|
||||||
let val = env.get(&key).map_or("", String::as_str);
|
let val = config.env.get(&key).map_or("", String::as_str);
|
||||||
cmd = cmd.replace(&format!("${}", key), &val);
|
cmd = cmd.replace(&format!("${}", key), val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set env var like `foo=42` or `bar = "Hello, World!"
|
let mut args = split_args(&cmd);
|
||||||
if Regex::new("^\\w+ *= *\\S*$").is_match(&cmd) {
|
|
||||||
let mut iter = cmd.splitn(2, '=');
|
// Replace command alias
|
||||||
let key = iter.next().unwrap_or("").trim().to_string();
|
if let Some(alias) = config.aliases.get(&args[0]) {
|
||||||
let val = iter.next().unwrap_or("").trim().to_string();
|
args.remove(0);
|
||||||
env.insert(key, val);
|
for arg in alias.split(' ').rev() {
|
||||||
return ExitCode::CommandSuccessful
|
args.insert(0, arg.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = split_args(&cmd);
|
|
||||||
let mut args: Vec<&str> = args.iter().map(String::as_str).collect();
|
let mut args: Vec<&str> = args.iter().map(String::as_str).collect();
|
||||||
|
|
||||||
// Redirections like `print hello => /tmp/hello`
|
// Redirections like `print hello => /tmp/hello`
|
||||||
|
@ -326,62 +389,52 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = match args[0] {
|
let res = match args[0] {
|
||||||
"" => ExitCode::CommandSuccessful,
|
"" => ExitCode::CommandSuccessful,
|
||||||
"a" => ExitCode::CommandUnknown,
|
"2048" => usr::pow::main(&args),
|
||||||
"b" => ExitCode::CommandUnknown,
|
"alias" => cmd_alias(&args, config),
|
||||||
"c" | "copy" => usr::copy::main(&args),
|
"base64" => usr::base64::main(&args),
|
||||||
"d" | "del" | "delete" => usr::delete::main(&args),
|
"beep" => usr::beep::main(&args),
|
||||||
"e" | "edit" => usr::editor::main(&args),
|
"calc" => usr::calc::main(&args),
|
||||||
"f" | "find" => usr::find::main(&args),
|
"chess" => usr::chess::main(&args),
|
||||||
"g" | "go" | "goto" => change_dir(&args, env),
|
"colors" => usr::colors::main(&args),
|
||||||
"h" | "help" => usr::help::main(&args),
|
"copy" => usr::copy::main(&args),
|
||||||
"i" => ExitCode::CommandUnknown,
|
"date" => usr::date::main(&args),
|
||||||
"j" => ExitCode::CommandUnknown,
|
"delete" => usr::delete::main(&args),
|
||||||
"k" => ExitCode::CommandUnknown,
|
"dhcp" => usr::dhcp::main(&args),
|
||||||
"l" | "list" => usr::list::main(&args),
|
"disk" => usr::disk::main(&args),
|
||||||
"m" | "move" => usr::r#move::main(&args),
|
"edit" => usr::editor::main(&args),
|
||||||
"n" => ExitCode::CommandUnknown,
|
"elf" => usr::elf::main(&args),
|
||||||
"o" => ExitCode::CommandUnknown,
|
"env" => usr::env::main(&args),
|
||||||
"q" | "quit" | "exit" => ExitCode::ShellExit,
|
"find" => usr::find::main(&args),
|
||||||
"r" | "read" => usr::read::main(&args),
|
"geotime" => usr::geotime::main(&args),
|
||||||
"s" => ExitCode::CommandUnknown,
|
"goto" => cmd_change_dir(&args, config),
|
||||||
"t" => ExitCode::CommandUnknown,
|
"help" => usr::help::main(&args),
|
||||||
"u" => ExitCode::CommandUnknown,
|
"hex" => usr::hex::main(&args),
|
||||||
"v" => ExitCode::CommandUnknown,
|
"host" => usr::host::main(&args),
|
||||||
"w" | "write" => usr::write::main(&args),
|
"http" => usr::http::main(&args),
|
||||||
"x" => ExitCode::CommandUnknown,
|
"httpd" => usr::httpd::main(&args),
|
||||||
"y" => ExitCode::CommandUnknown,
|
"install" => usr::install::main(&args),
|
||||||
"z" => ExitCode::CommandUnknown,
|
"keyboard" => usr::keyboard::main(&args),
|
||||||
"vga" => usr::vga::main(&args),
|
"lisp" => usr::lisp::main(&args),
|
||||||
"sh" | "shell" => usr::shell::main(&args),
|
"list" => usr::list::main(&args),
|
||||||
"calc" => usr::calc::main(&args),
|
"memory" => usr::memory::main(&args),
|
||||||
"base64" => usr::base64::main(&args),
|
"move" => usr::r#move::main(&args),
|
||||||
"date" => usr::date::main(&args),
|
"net" => usr::net::main(&args),
|
||||||
"env" => usr::env::main(&args),
|
"pci" => usr::pci::main(&args),
|
||||||
"hex" => usr::hex::main(&args),
|
"proc" => cmd_proc(&args),
|
||||||
"net" => usr::net::main(&args),
|
"quit" => ExitCode::ShellExit,
|
||||||
"dhcp" => usr::dhcp::main(&args),
|
"read" => usr::read::main(&args),
|
||||||
"http" => usr::http::main(&args),
|
"set" => cmd_set(&args, config),
|
||||||
"httpd" => usr::httpd::main(&args),
|
"shell" => usr::shell::main(&args),
|
||||||
"socket" => usr::socket::main(&args),
|
"socket" => usr::socket::main(&args),
|
||||||
"tcp" => usr::tcp::main(&args),
|
"tcp" => usr::tcp::main(&args),
|
||||||
"host" => usr::host::main(&args),
|
"time" => usr::time::main(&args),
|
||||||
"install" => usr::install::main(&args),
|
"unalias" => cmd_unalias(&args, config),
|
||||||
"geotime" => usr::geotime::main(&args),
|
"unset" => cmd_unset(&args, config),
|
||||||
"colors" => usr::colors::main(&args),
|
"user" => usr::user::main(&args),
|
||||||
"dsk" | "disk" => usr::disk::main(&args),
|
"vga" => usr::vga::main(&args),
|
||||||
"user" => usr::user::main(&args),
|
"write" => usr::write::main(&args),
|
||||||
"mem" | "memory" => usr::memory::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),
|
|
||||||
"pci" => usr::pci::main(&args),
|
|
||||||
"2048" => usr::pow::main(&args),
|
|
||||||
"time" => usr::time::main(&args),
|
|
||||||
"proc" => proc(&args),
|
|
||||||
_ => {
|
|
||||||
let mut path = fs::realpath(args[0]);
|
let mut path = fs::realpath(args[0]);
|
||||||
if path.len() > 1 {
|
if path.len() > 1 {
|
||||||
path = path.trim_end_matches('/').into();
|
path = path.trim_end_matches('/').into();
|
||||||
|
@ -389,7 +442,7 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
|
||||||
match syscall::info(&path).map(|info| info.kind()) {
|
match syscall::info(&path).map(|info| info.kind()) {
|
||||||
Some(FileType::Dir) => {
|
Some(FileType::Dir) => {
|
||||||
sys::process::set_dir(&path);
|
sys::process::set_dir(&path);
|
||||||
env.insert("DIR".to_string(), sys::process::dir());
|
config.env.insert("DIR".to_string(), sys::process::dir());
|
||||||
ExitCode::CommandSuccessful
|
ExitCode::CommandSuccessful
|
||||||
}
|
}
|
||||||
Some(FileType::File) => {
|
Some(FileType::File) => {
|
||||||
|
@ -402,12 +455,7 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// TODO: add aliases command instead of hardcoding them
|
if api::process::spawn(&format!("/bin/{}", args[0]), &args).is_ok() {
|
||||||
let name = match args[0] {
|
|
||||||
"p" => "print",
|
|
||||||
arg => arg,
|
|
||||||
};
|
|
||||||
if api::process::spawn(&format!("/bin/{}", name), &args).is_ok() {
|
|
||||||
ExitCode::CommandSuccessful
|
ExitCode::CommandSuccessful
|
||||||
} else {
|
} else {
|
||||||
error!("Could not execute '{}'", cmd);
|
error!("Could not execute '{}'", cmd);
|
||||||
|
@ -428,7 +476,7 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn repl(env: &mut BTreeMap<String, String>) -> usr::shell::ExitCode {
|
fn repl(config: &mut Config) -> usr::shell::ExitCode {
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
let mut prompt = Prompt::new();
|
let mut prompt = Prompt::new();
|
||||||
|
@ -438,7 +486,7 @@ fn repl(env: &mut BTreeMap<String, String>) -> usr::shell::ExitCode {
|
||||||
|
|
||||||
let mut success = true;
|
let mut success = true;
|
||||||
while let Some(cmd) = prompt.input(&prompt_string(success)) {
|
while let Some(cmd) = prompt.input(&prompt_string(success)) {
|
||||||
match exec(&cmd, env) {
|
match exec_with_config(&cmd, config) {
|
||||||
ExitCode::CommandSuccessful => {
|
ExitCode::CommandSuccessful => {
|
||||||
success = true;
|
success = true;
|
||||||
},
|
},
|
||||||
|
@ -458,26 +506,37 @@ fn repl(env: &mut BTreeMap<String, String>) -> usr::shell::ExitCode {
|
||||||
ExitCode::CommandSuccessful
|
ExitCode::CommandSuccessful
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn exec(cmd: &str) -> ExitCode {
|
||||||
|
let mut config = Config::new();
|
||||||
|
exec_with_config(cmd, &mut config)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main(args: &[&str]) -> ExitCode {
|
pub fn main(args: &[&str]) -> ExitCode {
|
||||||
let mut env = default_env();
|
let mut config = Config::new();
|
||||||
|
|
||||||
|
if let Ok(rc) = fs::read_to_string("/ini/shell.sh") {
|
||||||
|
for cmd in rc.split('\n') {
|
||||||
|
exec_with_config(cmd, &mut config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
env.insert(0.to_string(), args[0].to_string());
|
config.env.insert(0.to_string(), args[0].to_string());
|
||||||
|
|
||||||
repl(&mut env)
|
repl(&mut config)
|
||||||
} else {
|
} else {
|
||||||
env.insert(0.to_string(), args[1].to_string());
|
config.env.insert(0.to_string(), args[1].to_string());
|
||||||
|
|
||||||
// Add script arguments to the environment as `$1`, `$2`, `$3`, ...
|
// Add script arguments to the environment as `$1`, `$2`, `$3`, ...
|
||||||
for (i, arg) in args[2..].iter().enumerate() {
|
for (i, arg) in args[2..].iter().enumerate() {
|
||||||
env.insert((i + 1).to_string(), arg.to_string());
|
config.env.insert((i + 1).to_string(), arg.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let pathname = args[1];
|
let pathname = args[1];
|
||||||
if let Ok(contents) = api::fs::read_to_string(pathname) {
|
if let Ok(contents) = api::fs::read_to_string(pathname) {
|
||||||
for line in contents.split('\n') {
|
for line in contents.split('\n') {
|
||||||
if !line.is_empty() {
|
if !line.is_empty() {
|
||||||
exec(line, &mut env);
|
exec_with_config(line, &mut config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExitCode::CommandSuccessful
|
ExitCode::CommandSuccessful
|
||||||
|
@ -496,26 +555,25 @@ fn test_shell() {
|
||||||
sys::fs::format_mem();
|
sys::fs::format_mem();
|
||||||
usr::install::copy_files(false);
|
usr::install::copy_files(false);
|
||||||
|
|
||||||
let mut env = default_env();
|
|
||||||
|
|
||||||
// Redirect standard output
|
// Redirect standard output
|
||||||
exec("print test1 => /test", &mut env);
|
exec("print test1 => /test");
|
||||||
assert_eq!(api::fs::read_to_string("/test"), Ok("test1\n".to_string()));
|
assert_eq!(api::fs::read_to_string("/test"), Ok("test1\n".to_string()));
|
||||||
|
|
||||||
// Overwrite content of existing file
|
// Overwrite content of existing file
|
||||||
exec("print test2 => /test", &mut env);
|
exec("print test2 => /test");
|
||||||
assert_eq!(api::fs::read_to_string("/test"), Ok("test2\n".to_string()));
|
assert_eq!(api::fs::read_to_string("/test"), Ok("test2\n".to_string()));
|
||||||
|
|
||||||
// Redirect standard output explicitely
|
// Redirect standard output explicitely
|
||||||
exec("print test3 1=> /test", &mut env);
|
exec("print test3 1=> /test");
|
||||||
assert_eq!(api::fs::read_to_string("/test"), Ok("test3\n".to_string()));
|
assert_eq!(api::fs::read_to_string("/test"), Ok("test3\n".to_string()));
|
||||||
|
|
||||||
// Redirect standard error explicitely
|
// Redirect standard error explicitely
|
||||||
exec("hex /nope 2=> /test", &mut env);
|
exec("hex /nope 2=> /test");
|
||||||
assert!(api::fs::read_to_string("/test").unwrap().contains("File not found '/nope'"));
|
assert!(api::fs::read_to_string("/test").unwrap().contains("File not found '/nope'"));
|
||||||
|
|
||||||
exec("b = 42", &mut env);
|
let mut config = Config::new();
|
||||||
exec("print a $b $c d => /test", &mut env);
|
exec_with_config("set b 42", &mut config);
|
||||||
|
exec_with_config("print a $b $c d => /test", &mut config);
|
||||||
assert_eq!(api::fs::read_to_string("/test"), Ok("a 42 d\n".to_string()));
|
assert_eq!(api::fs::read_to_string("/test"), Ok("a 42 d\n".to_string()));
|
||||||
|
|
||||||
sys::fs::dismount();
|
sys::fs::dismount();
|
||||||
|
|
|
@ -6,9 +6,8 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||||
let csi_color = Style::color("LightBlue");
|
let csi_color = Style::color("LightBlue");
|
||||||
let csi_reset = Style::reset();
|
let csi_reset = Style::reset();
|
||||||
let cmd = args[1..].join(" ");
|
let cmd = args[1..].join(" ");
|
||||||
let mut env = usr::shell::default_env();
|
|
||||||
let start = clock::realtime();
|
let start = clock::realtime();
|
||||||
usr::shell::exec(&cmd, &mut env);
|
usr::shell::exec(&cmd);
|
||||||
let duration = clock::realtime() - start;
|
let duration = clock::realtime() - start;
|
||||||
println!("{}Executed '{}' in {:.6}s{}", csi_color, cmd, duration, csi_reset);
|
println!("{}Executed '{}' in {:.6}s{}", csi_color, cmd, duration, csi_reset);
|
||||||
usr::shell::ExitCode::CommandSuccessful
|
usr::shell::ExitCode::CommandSuccessful
|
||||||
|
|
Loading…
Reference in New Issue
Block a user