mirror of https://github.com/vinc/moros.git
Add shell variables (#348)
* Add env vars to shell * Add shell env * Rename shell::run to shell::repl * Add script pathname to shell env * Add shell variables * Add test * Add doc * Fix doc
This commit is contained in:
parent
9ad5f874e4
commit
0831045c64
22
doc/shell.md
22
doc/shell.md
|
@ -121,3 +121,25 @@ Which is more efficient than doing:
|
|||
|
||||
> print hello -> write /tmp/hello
|
||||
|
||||
## Variables
|
||||
|
||||
- Name of the shell or the script: `$0`
|
||||
- Script arguments: `$1`, `$2`, `$3`, `$4`, ...
|
||||
- Process environment variable: `$HOME`, ...
|
||||
- Shell environment variable: `$foo`, ...
|
||||
|
||||
Setting a variable in the shell environment is done with the following command:
|
||||
|
||||
> foo = "world"
|
||||
|
||||
And accessing that variable is done with the `$` operator:
|
||||
|
||||
> print $foo
|
||||
world
|
||||
|
||||
> print "hello $foo"
|
||||
hello world
|
||||
|
||||
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
|
||||
env var should be lowercase.
|
||||
|
|
|
@ -62,10 +62,10 @@ pub fn delete(path: &str) -> isize
|
|||
pub fn stop(code: usize)
|
||||
```
|
||||
|
||||
The system will reboot with `0xcafe` and halt with `0xdead`.
|
||||
|
||||
## SLEEP (0xB)
|
||||
|
||||
```rust
|
||||
pub fn sleep(seconds: f64)
|
||||
```
|
||||
|
||||
The system will reboot with `0xcafe` and halt with `0xdead`.
|
||||
|
|
|
@ -64,9 +64,11 @@ impl Regex {
|
|||
pub fn new(re: &str) -> Self {
|
||||
Self(re.to_string())
|
||||
}
|
||||
|
||||
pub fn is_match(&self, text: &str) -> bool {
|
||||
self.find(text).is_some()
|
||||
}
|
||||
|
||||
pub fn find(&self, text: &str) -> Option<(usize, usize)> {
|
||||
let text: Vec<char> = text.chars().collect(); // UTF-32
|
||||
let re: Vec<char> = self.0.chars().collect(); // UTF-32
|
||||
|
@ -264,4 +266,5 @@ fn test_regex() {
|
|||
assert_eq!(Regex::new("b.*?c").find("aaabbbcccddd"), Some((3, 7)));
|
||||
assert_eq!(Regex::new("a\\w*d").find("abcdabcd"), Some((0, 8)));
|
||||
assert_eq!(Regex::new("a\\w*?d").find("abcdabcd"), Some((0, 4)));
|
||||
assert_eq!(Regex::new("\\$\\w+").find("test $test test"), Some((5, 10)));
|
||||
}
|
||||
|
|
|
@ -14,9 +14,10 @@ fn main(boot_info: &'static BootInfo) -> ! {
|
|||
print!("\x1b[?25h"); // Enable cursor
|
||||
loop {
|
||||
if let Some(cmd) = option_env!("MOROS_CMD") {
|
||||
let mut env = usr::shell::default_env();
|
||||
let prompt = usr::shell::prompt_string(true);
|
||||
println!("{}{}", prompt, cmd);
|
||||
usr::shell::exec(cmd);
|
||||
usr::shell::exec(cmd, &mut env);
|
||||
sys::acpi::shutdown();
|
||||
} else {
|
||||
user_boot();
|
||||
|
|
|
@ -294,7 +294,8 @@ fn default_env() -> Rc<RefCell<Env>> {
|
|||
data.insert("system".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
|
||||
ensure_length_eq!(args, 1);
|
||||
let cmd = string(&args[0])?;
|
||||
let res = usr::shell::exec(&cmd);
|
||||
let mut env = usr::shell::default_env();
|
||||
let res = usr::shell::exec(&cmd, &mut env);
|
||||
Ok(Exp::Num(res as u8 as f64))
|
||||
}));
|
||||
data.insert("print".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
|
||||
|
|
|
@ -3,9 +3,11 @@ use crate::api::fs;
|
|||
use crate::api::regex::Regex;
|
||||
use crate::api::prompt::Prompt;
|
||||
use crate::api::console::Style;
|
||||
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::string::String;
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
// TODO: Scan /bin
|
||||
const AUTOCOMPLETE_COMMANDS: [&str; 40] = [
|
||||
|
@ -61,6 +63,17 @@ pub fn prompt_string(success: bool) -> String {
|
|||
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
|
||||
}
|
||||
|
||||
pub fn split_args(cmd: &str) -> Vec<&str> {
|
||||
let mut args: Vec<&str> = Vec::new();
|
||||
let mut i = 0;
|
||||
|
@ -154,8 +167,27 @@ fn change_dir(args: &[&str]) -> ExitCode {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn exec(cmd: &str) -> ExitCode {
|
||||
let mut args = split_args(cmd);
|
||||
pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
|
||||
let mut cmd = cmd.to_string();
|
||||
|
||||
// Replace `$key` with its value in the environment or an empty string
|
||||
let re = Regex::new("\\$\\w+");
|
||||
while let Some((a, b)) = re.find(&cmd) {
|
||||
let key: String = cmd.chars().skip(a + 1).take(b - a - 1).collect();
|
||||
let val = env.get(&key).map_or("", String::as_str);
|
||||
cmd = cmd.replace(&format!("${}", key), &val);
|
||||
}
|
||||
|
||||
// Set env var like `foo=42` or `bar = "Hello, World!"
|
||||
if Regex::new("^\\w+ *= *\\S*$").is_match(&cmd) {
|
||||
let mut iter = cmd.splitn(2, '=');
|
||||
let key = iter.next().unwrap_or("").trim().to_string();
|
||||
let val = iter.next().unwrap_or("").trim().to_string();
|
||||
env.insert(key, val);
|
||||
return ExitCode::CommandSuccessful
|
||||
}
|
||||
|
||||
let mut args = split_args(&cmd);
|
||||
|
||||
// Redirections like `print hello => /tmp/hello`
|
||||
// Pipes like `print hello -> write /tmp/hello` or `p hello > w /tmp/hello`
|
||||
|
@ -291,7 +323,7 @@ pub fn exec(cmd: &str) -> ExitCode {
|
|||
res
|
||||
}
|
||||
|
||||
pub fn run() -> usr::shell::ExitCode {
|
||||
fn repl(env: &mut BTreeMap<String, String>) -> usr::shell::ExitCode {
|
||||
println!();
|
||||
|
||||
let mut prompt = Prompt::new();
|
||||
|
@ -301,7 +333,7 @@ pub fn run() -> usr::shell::ExitCode {
|
|||
|
||||
let mut success = true;
|
||||
while let Some(cmd) = prompt.input(&prompt_string(success)) {
|
||||
match exec(&cmd) {
|
||||
match exec(&cmd, env) {
|
||||
ExitCode::CommandSuccessful => {
|
||||
success = true;
|
||||
},
|
||||
|
@ -322,27 +354,32 @@ pub fn run() -> usr::shell::ExitCode {
|
|||
}
|
||||
|
||||
pub fn main(args: &[&str]) -> ExitCode {
|
||||
match args.len() {
|
||||
1 => {
|
||||
run()
|
||||
},
|
||||
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);
|
||||
}
|
||||
let mut env = default_env();
|
||||
|
||||
if args.len() < 2 {
|
||||
env.insert(0.to_string(), args[0].to_string());
|
||||
|
||||
repl(&mut env)
|
||||
} else {
|
||||
env.insert(0.to_string(), args[1].to_string());
|
||||
|
||||
// Add script arguments to the environment as `$1`, `$2`, `$3`, ...
|
||||
for (i, arg) in args[2..].iter().enumerate() {
|
||||
env.insert((i + 1).to_string(), arg.to_string());
|
||||
}
|
||||
|
||||
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, &mut env);
|
||||
}
|
||||
ExitCode::CommandSuccessful
|
||||
} else {
|
||||
println!("File not found '{}'", pathname);
|
||||
ExitCode::CommandError
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
ExitCode::CommandSuccessful
|
||||
} else {
|
||||
println!("File not found '{}'", pathname);
|
||||
ExitCode::CommandError
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,22 +391,27 @@ fn test_shell() {
|
|||
sys::fs::format_mem();
|
||||
usr::install::copy_files(false);
|
||||
|
||||
let mut env = default_env();
|
||||
|
||||
// Redirect standard output
|
||||
exec("print test1 => /test");
|
||||
exec("print test1 => /test", &mut env);
|
||||
assert_eq!(api::fs::read_to_string("/test"), Ok("test1\n".to_string()));
|
||||
|
||||
// Overwrite content of existing file
|
||||
exec("print test2 => /test");
|
||||
exec("print test2 => /test", &mut env);
|
||||
assert_eq!(api::fs::read_to_string("/test"), Ok("test2\n".to_string()));
|
||||
|
||||
// Redirect standard output explicitely
|
||||
exec("print test3 1=> /test");
|
||||
exec("print test3 1=> /test", &mut env);
|
||||
assert_eq!(api::fs::read_to_string("/test"), Ok("test3\n".to_string()));
|
||||
|
||||
// Redirect standard error explicitely
|
||||
exec("hex /nope 2=> /test");
|
||||
exec("hex /nope 2=> /test", &mut env);
|
||||
assert!(api::fs::read_to_string("/test").unwrap().contains("File not found '/nope'"));
|
||||
|
||||
exec("b = 42", &mut env);
|
||||
exec("print a $b $c d => /test", &mut env);
|
||||
assert_eq!(api::fs::read_to_string("/test"), Ok("a 42 d\n".to_string()));
|
||||
|
||||
sys::fs::dismount();
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
|||
let csi_color = Style::color("LightBlue");
|
||||
let csi_reset = Style::reset();
|
||||
let cmd = args[1..].join(" ");
|
||||
let mut env = usr::shell::default_env();
|
||||
let start = clock::realtime();
|
||||
usr::shell::exec(&cmd);
|
||||
usr::shell::exec(&cmd, &mut env);
|
||||
let duration = clock::realtime() - start;
|
||||
println!("{}Executed '{}' in {:.6}s{}", csi_color, cmd, duration, csi_reset);
|
||||
usr::shell::ExitCode::CommandSuccessful
|
||||
|
|
Loading…
Reference in New Issue