implemented builtins: readonly, unset, true, false, and : (no-op)
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
use crate::{
|
||||
jobs::JobBldr,
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{borrow_fd, IoStack},
|
||||
state::{self, read_vars, write_vars, VarFlags},
|
||||
};
|
||||
|
||||
use super::setup_builtin;
|
||||
|
||||
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the environment variables
|
||||
let mut env_output = env::vars()
|
||||
.map(|var| format!("{}={}", var.0, var.1)) // Get all of them, zip them into one string
|
||||
.collect::<Vec<_>>();
|
||||
env_output.sort(); // Sort them alphabetically
|
||||
let mut env_output = env_output.join("\n"); // Join them with newlines
|
||||
env_output.push('\n'); // Push a final newline
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, env_output.as_bytes())?; // Write it
|
||||
} else {
|
||||
for (arg, _) in argv {
|
||||
if let Some((var, val)) = arg.split_once('=') {
|
||||
write_vars(|v| v.set_var(var, val, VarFlags::EXPORT)); // Export an assignment like
|
||||
// 'foo=bar'
|
||||
} else {
|
||||
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
||||
// any
|
||||
}
|
||||
}
|
||||
}
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the local variables
|
||||
let vars_output = read_vars(|v| {
|
||||
let mut vars = v
|
||||
.flatten_vars()
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<String>>();
|
||||
vars.sort();
|
||||
let mut vars_joined = vars.join("\n");
|
||||
vars_joined.push('\n');
|
||||
vars_joined
|
||||
});
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, vars_output.as_bytes())?; // Write it
|
||||
} else {
|
||||
for (arg, _) in argv {
|
||||
if let Some((var, val)) = arg.split_once('=') {
|
||||
write_vars(|v| v.set_var(var, val, VarFlags::LOCAL));
|
||||
} else {
|
||||
write_vars(|v| v.set_var(&arg, "", VarFlags::LOCAL)); // Create an uninitialized local variable
|
||||
}
|
||||
}
|
||||
}
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
@@ -8,13 +8,13 @@ use crate::{
|
||||
execute::prepare_argv,
|
||||
lex::{Span, Tk},
|
||||
},
|
||||
procio::{IoFrame, IoStack, RedirGuard},
|
||||
procio::{IoFrame, IoStack, RedirGuard}, state,
|
||||
};
|
||||
|
||||
pub mod alias;
|
||||
pub mod cd;
|
||||
pub mod echo;
|
||||
pub mod export;
|
||||
pub mod varcmds;
|
||||
pub mod flowctl;
|
||||
pub mod jobctl;
|
||||
pub mod pwd;
|
||||
@@ -29,10 +29,10 @@ pub mod dirstack;
|
||||
pub mod exec;
|
||||
pub mod eval;
|
||||
|
||||
pub const BUILTINS: [&str; 28] = [
|
||||
pub const BUILTINS: [&str; 33] = [
|
||||
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown", "alias", "unalias",
|
||||
"return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command", "trap",
|
||||
"pushd", "popd", "dirs", "exec", "eval"
|
||||
"pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly", "unset"
|
||||
];
|
||||
|
||||
/// Sets up a builtin command
|
||||
@@ -93,3 +93,18 @@ pub fn setup_builtin(
|
||||
// io_frame.restore()
|
||||
Ok((argv, guard))
|
||||
}
|
||||
|
||||
pub fn true_builtin() -> ShResult<()> {
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn false_builtin() -> ShResult<()> {
|
||||
state::set_status(1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn noop_builtin() -> ShResult<()> {
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
163
src/builtin/varcmds.rs
Normal file
163
src/builtin/varcmds.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use crate::{
|
||||
jobs::JobBldr,
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
state::{self, VarFlags, read_vars, write_vars},
|
||||
};
|
||||
|
||||
use super::setup_builtin;
|
||||
|
||||
pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the local variables
|
||||
let vars_output = read_vars(|v| {
|
||||
let mut vars = v
|
||||
.flatten_vars()
|
||||
.into_iter()
|
||||
.filter(|(_, v)| v.flags().contains(VarFlags::READONLY))
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<String>>();
|
||||
vars.sort();
|
||||
let mut vars_joined = vars.join("\n");
|
||||
vars_joined.push('\n');
|
||||
vars_joined
|
||||
});
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, vars_output.as_bytes())?; // Write it
|
||||
} else {
|
||||
for (arg, _) in argv {
|
||||
if let Some((var, val)) = arg.split_once('=') {
|
||||
write_vars(|v| v.set_var(var, val, VarFlags::READONLY))?;
|
||||
} else {
|
||||
write_vars(|v| v.set_var(&arg, "", VarFlags::READONLY))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let blame = node.get_span().clone();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if argv.is_empty() {
|
||||
return Err(ShErr::full(
|
||||
ShErrKind::SyntaxErr,
|
||||
"unset: Expected at least one argument",
|
||||
blame
|
||||
));
|
||||
}
|
||||
|
||||
for (arg,span) in argv {
|
||||
if !read_vars(|v| v.var_exists(&arg)) {
|
||||
return Err(ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
format!("unset: No such variable '{arg}'"),
|
||||
span
|
||||
));
|
||||
}
|
||||
write_vars(|v| v.unset_var(&arg))?;
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the environment variables
|
||||
let mut env_output = env::vars()
|
||||
.map(|var| format!("{}={}", var.0, var.1)) // Get all of them, zip them into one string
|
||||
.collect::<Vec<_>>();
|
||||
env_output.sort(); // Sort them alphabetically
|
||||
let mut env_output = env_output.join("\n"); // Join them with newlines
|
||||
env_output.push('\n'); // Push a final newline
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, env_output.as_bytes())?; // Write it
|
||||
} else {
|
||||
for (arg, _) in argv {
|
||||
if let Some((var, val)) = arg.split_once('=') {
|
||||
write_vars(|v| v.set_var(var, val, VarFlags::EXPORT))?;
|
||||
} else {
|
||||
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
||||
// any
|
||||
}
|
||||
}
|
||||
}
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the local variables
|
||||
let vars_output = read_vars(|v| {
|
||||
let mut vars = v
|
||||
.flatten_vars()
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<String>>();
|
||||
vars.sort();
|
||||
let mut vars_joined = vars.join("\n");
|
||||
vars_joined.push('\n');
|
||||
vars_joined
|
||||
});
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, vars_output.as_bytes())?; // Write it
|
||||
} else {
|
||||
for (arg, _) in argv {
|
||||
if let Some((var, val)) = arg.split_once('=') {
|
||||
write_vars(|v| v.set_var(var, val, VarFlags::LOCAL))?;
|
||||
} else {
|
||||
write_vars(|v| v.set_var(&arg, "", VarFlags::LOCAL))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use std::{collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt};
|
||||
|
||||
use crate::{
|
||||
builtin::{
|
||||
alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, export::{export, local}, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, zoltraak::zoltraak
|
||||
alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, true_builtin, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||
},
|
||||
expand::{expand_aliases, glob_to_regex},
|
||||
jobs::{ChildProc, JobStack, dispatch_job},
|
||||
@@ -90,7 +90,7 @@ impl Drop for VarCtxGuard {
|
||||
fn drop(&mut self) {
|
||||
write_vars(|v| {
|
||||
for var in &self.vars {
|
||||
v.unset_var(var);
|
||||
v.unset_var(var).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -569,7 +569,7 @@ impl Dispatcher {
|
||||
.zip(chunk.iter().chain(std::iter::repeat(&empty)));
|
||||
|
||||
for (var, val) in chunk_iter {
|
||||
write_vars(|v| v.set_var(&var.to_string(), &val.to_string(), VarFlags::NONE));
|
||||
write_vars(|v| v.set_var(&var.to_string(), &val.to_string(), VarFlags::NONE))?;
|
||||
for_guard.vars.insert(var.to_string());
|
||||
}
|
||||
|
||||
@@ -769,6 +769,16 @@ impl Dispatcher {
|
||||
"dirs" => dirs(cmd, io_stack_mut, curr_job_mut),
|
||||
"exec" => exec::exec_builtin(cmd, io_stack_mut, curr_job_mut),
|
||||
"eval" => eval::eval(cmd, io_stack_mut, curr_job_mut),
|
||||
"readonly" => readonly(cmd, io_stack_mut, curr_job_mut),
|
||||
"unset" => unset(cmd, io_stack_mut, curr_job_mut),
|
||||
"true" | ":" => {
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
},
|
||||
"false" => {
|
||||
state::set_status(1);
|
||||
Ok(())
|
||||
},
|
||||
_ => unimplemented!(
|
||||
"Have not yet added support for builtin '{}'",
|
||||
cmd_raw
|
||||
@@ -885,7 +895,7 @@ impl Dispatcher {
|
||||
let var = var.span.as_str();
|
||||
let val = val.expand()?.get_words().join(" ");
|
||||
match kind {
|
||||
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::EXPORT)),
|
||||
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::EXPORT))?,
|
||||
AssignKind::PlusEq => todo!(),
|
||||
AssignKind::MinusEq => todo!(),
|
||||
AssignKind::MultEq => todo!(),
|
||||
@@ -902,7 +912,7 @@ impl Dispatcher {
|
||||
let var = var.span.as_str();
|
||||
let val = val.expand()?.get_words().join(" ");
|
||||
match kind {
|
||||
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::NONE)),
|
||||
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::NONE))?,
|
||||
AssignKind::PlusEq => todo!(),
|
||||
AssignKind::MinusEq => todo!(),
|
||||
AssignKind::MultEq => todo!(),
|
||||
|
||||
49
src/state.rs
49
src/state.rs
@@ -151,13 +151,16 @@ impl ScopeStack {
|
||||
pub fn cur_scope_mut(&mut self) -> &mut VarTab {
|
||||
self.scopes.last_mut().unwrap()
|
||||
}
|
||||
pub fn unset_var(&mut self, var_name: &str) {
|
||||
pub fn unset_var(&mut self, var_name: &str) -> ShResult<()> {
|
||||
for scope in self.scopes.iter_mut().rev() {
|
||||
if scope.var_exists(var_name) {
|
||||
scope.unset_var(var_name);
|
||||
return;
|
||||
return scope.unset_var(var_name);
|
||||
}
|
||||
}
|
||||
Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("Variable '{}' not found", var_name)
|
||||
))
|
||||
}
|
||||
pub fn export_var(&mut self, var_name: &str) {
|
||||
for scope in self.scopes.iter_mut().rev() {
|
||||
@@ -187,22 +190,26 @@ impl ScopeStack {
|
||||
}
|
||||
flat_vars
|
||||
}
|
||||
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
|
||||
let is_local = self.is_local_var(var_name);
|
||||
if flags.contains(VarFlags::LOCAL) || is_local {
|
||||
self.set_var_local(var_name, val, flags);
|
||||
self.set_var_local(var_name, val, flags)
|
||||
} else {
|
||||
self.set_var_global(var_name, val, flags);
|
||||
self.set_var_global(var_name, val, flags)
|
||||
}
|
||||
}
|
||||
fn set_var_global(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||
fn set_var_global(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
|
||||
if let Some(scope) = self.scopes.first_mut() {
|
||||
scope.set_var(var_name, val, flags);
|
||||
scope.set_var(var_name, val, flags)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
fn set_var_local(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||
fn set_var_local(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
|
||||
if let Some(scope) = self.scopes.last_mut() {
|
||||
scope.set_var(var_name, val, flags);
|
||||
scope.set_var(var_name, val, flags)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
pub fn get_var(&self, var_name: &str) -> String {
|
||||
@@ -477,6 +484,9 @@ impl Var {
|
||||
pub fn mark_for_export(&mut self) {
|
||||
self.flags.set(VarFlags::EXPORT, true);
|
||||
}
|
||||
pub fn flags(&self) -> VarFlags {
|
||||
self.flags
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Var {
|
||||
@@ -654,13 +664,27 @@ impl VarTab {
|
||||
pub fn get_var_flags(&self, var_name: &str) -> Option<VarFlags> {
|
||||
self.vars.get(var_name).map(|var| var.flags)
|
||||
}
|
||||
pub fn unset_var(&mut self, var_name: &str) {
|
||||
pub fn unset_var(&mut self, var_name: &str) -> ShResult<()> {
|
||||
if let Some(var) = self.vars.get(var_name) && var.flags.contains(VarFlags::READONLY) {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("cannot unset readonly variable '{}'", var_name)
|
||||
));
|
||||
}
|
||||
self.vars.remove(var_name);
|
||||
unsafe { env::remove_var(var_name) };
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> {
|
||||
if let Some(var) = self.vars.get_mut(var_name) {
|
||||
if var.flags.contains(VarFlags::READONLY) && !flags.contains(VarFlags::READONLY) {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("Variable '{}' is readonly", var_name)
|
||||
));
|
||||
}
|
||||
var.kind = VarKind::Str(val.to_string());
|
||||
var.flags |= flags;
|
||||
if var.flags.contains(VarFlags::EXPORT) || flags.contains(VarFlags::EXPORT) {
|
||||
if flags.contains(VarFlags::EXPORT) && !var.flags.contains(VarFlags::EXPORT) {
|
||||
var.mark_for_export();
|
||||
@@ -675,6 +699,7 @@ impl VarTab {
|
||||
}
|
||||
self.vars.insert(var_name.to_string(), var);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||
|
||||
Reference in New Issue
Block a user