diff --git a/crates/nu-command/src/commands/mod.rs b/crates/nu-command/src/commands/mod.rs index e9dae6419..94117f12a 100644 --- a/crates/nu-command/src/commands/mod.rs +++ b/crates/nu-command/src/commands/mod.rs @@ -12,6 +12,7 @@ mod generators; mod math; mod network; mod path; +mod pathvar; mod platform; mod random; mod shells; @@ -43,6 +44,7 @@ pub use generators::*; pub use math::*; pub use network::*; pub use path::*; +pub use pathvar::*; pub use platform::*; pub use random::*; pub use shells::*; diff --git a/crates/nu-command/src/commands/pathvar/add.rs b/crates/nu-command/src/commands/pathvar/add.rs new file mode 100644 index 000000000..a3d1d09bc --- /dev/null +++ b/crates/nu-command/src/commands/pathvar/add.rs @@ -0,0 +1,59 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, SyntaxShape}; +use nu_source::Tagged; +use nu_test_support::{NATIVE_PATH_ENV_SEPARATOR, NATIVE_PATH_ENV_VAR}; +use std::path::PathBuf; + +pub struct SubCommand; + +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "pathvar add" + } + + fn signature(&self) -> Signature { + Signature::build("pathvar add").required("path", SyntaxShape::FilePath, "path to add") + } + + fn usage(&self) -> &str { + "Add a filepath to the start of the pathvar" + } + + fn run(&self, args: CommandArgs) -> Result { + add(args) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Add /usr/local/bin to the pathvar", + example: "pathvar add /usr/local/bin", + result: None, + }] + } +} + +pub fn add(args: CommandArgs) -> Result { + let ctx = &args.context; + + let path_to_add: Tagged = args.req(0)?; + let path = path_to_add.item.into_os_string().into_string(); + + if let Ok(mut path) = path { + path.push(NATIVE_PATH_ENV_SEPARATOR); + if let Some(old_pathvar) = ctx.scope.get_env(NATIVE_PATH_ENV_VAR) { + path.push_str(&old_pathvar); + ctx.scope.add_env_var(NATIVE_PATH_ENV_VAR, path); + Ok(OutputStream::empty()) + } else { + Err(ShellError::unexpected("PATH not set")) + } + } else { + Err(ShellError::labeled_error( + "Invalid path.", + "cannot convert to string", + path_to_add.tag, + )) + } +} diff --git a/crates/nu-command/src/commands/pathvar/append.rs b/crates/nu-command/src/commands/pathvar/append.rs new file mode 100644 index 000000000..11f199510 --- /dev/null +++ b/crates/nu-command/src/commands/pathvar/append.rs @@ -0,0 +1,58 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, SyntaxShape}; +use nu_source::Tagged; +use nu_test_support::{NATIVE_PATH_ENV_SEPARATOR, NATIVE_PATH_ENV_VAR}; +use std::path::PathBuf; + +pub struct SubCommand; + +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "pathvar append" + } + + fn signature(&self) -> Signature { + Signature::build("pathvar append").required("path", SyntaxShape::FilePath, "path to append") + } + + fn usage(&self) -> &str { + "Add a path to the end of the pathvar" + } + + fn run(&self, args: CommandArgs) -> Result { + add(args) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Append /bin to the pathvar", + example: "pathvar append /bin", + result: None, + }] + } +} + +pub fn add(args: CommandArgs) -> Result { + let ctx = &args.context; + let path_to_append_arg: Tagged = args.req(0)?; + let path_to_append = path_to_append_arg.item.into_os_string().into_string(); + + if let Ok(path) = path_to_append { + if let Some(mut pathvar) = ctx.scope.get_env(NATIVE_PATH_ENV_VAR) { + pathvar.push(NATIVE_PATH_ENV_SEPARATOR); + pathvar.push_str(&path); + ctx.scope.add_env_var(NATIVE_PATH_ENV_VAR, pathvar); + Ok(OutputStream::empty()) + } else { + Err(ShellError::unexpected("PATH not set")) + } + } else { + Err(ShellError::labeled_error( + "Invalid path.", + "cannot convert to string", + path_to_append_arg.tag, + )) + } +} diff --git a/crates/nu-command/src/commands/pathvar/command.rs b/crates/nu-command/src/commands/pathvar/command.rs new file mode 100644 index 000000000..7d3474f91 --- /dev/null +++ b/crates/nu-command/src/commands/pathvar/command.rs @@ -0,0 +1,58 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, Value}; +use nu_test_support::{NATIVE_PATH_ENV_SEPARATOR, NATIVE_PATH_ENV_VAR}; + +pub struct Command; + +impl WholeStreamCommand for Command { + fn name(&self) -> &str { + "pathvar" + } + + fn signature(&self) -> Signature { + Signature::build("pathvar") + } + + fn usage(&self) -> &str { + "Manipulate the PATH variable (or pathvar)." + } + + fn run(&self, args: CommandArgs) -> Result { + get_pathvar(args) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Display the current session's pathvar", + example: "pathvar", + result: None, + }, + Example { + description: "Add /usr/bin to the pathvar", + example: "pathvar add /usr/bin", + result: None, + }, + Example { + description: "Remove the 3rd path in the pathvar", + example: "pathvar remove 2", + result: None, + }, + ] + } +} + +pub fn get_pathvar(args: CommandArgs) -> Result { + if let Some(pathvar) = args.context.scope.get_env(NATIVE_PATH_ENV_VAR) { + let pathvar: Vec = pathvar + .split(NATIVE_PATH_ENV_SEPARATOR) + .map(Value::from) + .collect(); + + Ok(OutputStream::from(pathvar)) + } else { + Err(ShellError::unexpected("PATH not set")) + } +} diff --git a/crates/nu-command/src/commands/pathvar/mod.rs b/crates/nu-command/src/commands/pathvar/mod.rs new file mode 100644 index 000000000..6191bf1a8 --- /dev/null +++ b/crates/nu-command/src/commands/pathvar/mod.rs @@ -0,0 +1,13 @@ +pub mod add; +pub mod append; +pub mod command; +pub mod remove; +pub mod reset; +pub mod save; + +pub use add::SubCommand as PathvarAdd; +pub use append::SubCommand as PathvarAppend; +pub use command::Command as Pathvar; +pub use remove::SubCommand as PathvarRemove; +pub use reset::SubCommand as PathvarReset; +pub use save::SubCommand as PathvarSave; diff --git a/crates/nu-command/src/commands/pathvar/remove.rs b/crates/nu-command/src/commands/pathvar/remove.rs new file mode 100644 index 000000000..c091b4558 --- /dev/null +++ b/crates/nu-command/src/commands/pathvar/remove.rs @@ -0,0 +1,66 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, SyntaxShape}; +use nu_source::Tagged; +use nu_test_support::{NATIVE_PATH_ENV_SEPARATOR, NATIVE_PATH_ENV_VAR}; + +pub struct SubCommand; + +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "pathvar remove" + } + + fn signature(&self) -> Signature { + Signature::build("pathvar remove").required( + "index", + SyntaxShape::Int, + "index of the path to remove (starting at 0)", + ) + } + + fn usage(&self) -> &str { + "Remove a path from the pathvar" + } + + fn run(&self, args: CommandArgs) -> Result { + remove(args) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Remove the second path from the pathvar", + example: "pathvar remove 1", + result: None, + }] + } +} + +pub fn remove(args: CommandArgs) -> Result { + let ctx = &args.context; + let index_to_remove_arg: Tagged = args.req(0)?; + let index_to_remove = index_to_remove_arg.item as usize; + + if let Some(old_pathvar) = ctx.scope.get_env(NATIVE_PATH_ENV_VAR) { + let mut paths: Vec<&str> = old_pathvar.split(NATIVE_PATH_ENV_SEPARATOR).collect(); + + if index_to_remove >= paths.len() { + return Err(ShellError::labeled_error( + "Index out of bounds", + format!("the index must be between 0 and {}", paths.len() - 1), + index_to_remove_arg.tag, + )); + } + + paths.remove(index_to_remove); + ctx.scope.add_env_var( + NATIVE_PATH_ENV_VAR, + paths.join(&NATIVE_PATH_ENV_SEPARATOR.to_string()), + ); + + Ok(OutputStream::empty()) + } else { + Err(ShellError::unexpected("PATH not set")) + } +} diff --git a/crates/nu-command/src/commands/pathvar/reset.rs b/crates/nu-command/src/commands/pathvar/reset.rs new file mode 100644 index 000000000..34781e8e6 --- /dev/null +++ b/crates/nu-command/src/commands/pathvar/reset.rs @@ -0,0 +1,52 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, UntaggedValue}; +use nu_test_support::{NATIVE_PATH_ENV_SEPARATOR, NATIVE_PATH_ENV_VAR}; + +pub struct SubCommand; + +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "pathvar reset" + } + + fn signature(&self) -> Signature { + Signature::build("pathvar reset") + } + + fn usage(&self) -> &str { + "Reset the pathvar to the one specified in the config" + } + + fn run(&self, args: CommandArgs) -> Result { + reset(args) + } +} +pub fn reset(args: CommandArgs) -> Result { + let name = args.call_info.name_tag.clone(); + let ctx = &args.context; + + if let Some(global_cfg) = &mut ctx.configs().lock().global_config { + let default_pathvar = global_cfg.vars.get("path"); + if let Some(pathvar) = default_pathvar { + if let UntaggedValue::Table(paths) = &pathvar.value { + let pathvar_str = paths + .iter() + .map(|x| x.as_string().expect("Error converting path to string")) + .join(&NATIVE_PATH_ENV_SEPARATOR.to_string()); + ctx.scope.add_env_var(NATIVE_PATH_ENV_VAR, pathvar_str); + } + } else { + return Err(ShellError::untagged_runtime_error( + "Default path is not set in config file.", + )); + } + Ok(OutputStream::empty()) + } else { + let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present()) + .into_value(name); + + Ok(OutputStream::one(value)) + } +} diff --git a/crates/nu-command/src/commands/pathvar/save.rs b/crates/nu-command/src/commands/pathvar/save.rs new file mode 100644 index 000000000..7b25823de --- /dev/null +++ b/crates/nu-command/src/commands/pathvar/save.rs @@ -0,0 +1,57 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::{Signature, UntaggedValue, Value}; +use nu_test_support::{NATIVE_PATH_ENV_SEPARATOR, NATIVE_PATH_ENV_VAR}; + +pub struct SubCommand; + +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "pathvar save" + } + + fn signature(&self) -> Signature { + Signature::build("pathvar save") + } + + fn usage(&self) -> &str { + "Save the current pathvar to the config file" + } + + fn run(&self, args: CommandArgs) -> Result { + save(args) + } +} +pub fn save(args: CommandArgs) -> Result { + let name = args.call_info.name_tag.clone(); + let ctx = &args.context; + + if let Some(global_cfg) = &mut ctx.configs().lock().global_config { + if let Some(pathvar) = ctx.scope.get_env(NATIVE_PATH_ENV_VAR) { + let paths: Vec = pathvar + .split(NATIVE_PATH_ENV_SEPARATOR) + .map(Value::from) + .collect(); + + let span_range = 0..paths.len(); + let row = Value::new( + UntaggedValue::Table(paths), + Tag::from(Span::from(&span_range)), + ); + + global_cfg.vars.insert("path".to_string(), row); + global_cfg.write()?; + ctx.reload_config(global_cfg)?; + + Ok(OutputStream::empty()) + } else { + Err(ShellError::unexpected("PATH not set")) + } + } else { + let value = UntaggedValue::Error(crate::commands::config::err_no_global_cfg_present()) + .into_value(name); + + Ok(OutputStream::one(value)) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c2e0a6d69..673c25fa2 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -27,6 +27,12 @@ pub fn create_default_context(interactive: bool) -> Result Outcome { Outcome { out, err }