From 8771872d861eeb94eec63051cb4938f6306d1dcd Mon Sep 17 00:00:00 2001 From: Bahex Date: Fri, 6 Dec 2024 17:19:08 +0300 Subject: [PATCH] Add `path self` command for getting absolute paths to files at parse time (#14303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alternative solution to: - #12195 The other approach: - #14305 # Description Adds ~`path const`~ `path self`, a parse-time only command for getting the absolute path of the source file containing it, or any file relative to the source file. - Useful for any script or module that makes use of non nuscript files. - Removes the need for `$env.CURRENT_FILE` and `$env.FILE_PWD`. - Can be used in modules, sourced files or scripts. # Examples ```nushell # ~/.config/nushell/scripts/foo.nu const paths = { self: (path self), dir: (path self .), sibling: (path self sibling), parent_dir: (path self ..), cousin: (path self ../cousin), } export def main [] { $paths } ``` ```nushell > use foo.nu > foo ╭────────────┬────────────────────────────────────────────╮ │ self │ /home/user/.config/nushell/scripts/foo.nu │ │ dir │ /home/user/.config/nushell/scripts │ │ sibling │ /home/user/.config/nushell/scripts/sibling │ │ parent_dir │ /home/user/.config/nushell │ │ cousin │ /home/user/.config/nushell/cousin │ ╰────────────┴────────────────────────────────────────────╯ ``` Trying to run in a non-const context ```nushell > path self Error: × this command can only run during parse-time ╭─[entry #1:1:1] 1 │ path self · ─────┬──── · ╰── can't run after parse-time ╰──── help: try assigning this command's output to a const variable ``` Trying to run in the REPL i.e. not in a file ```nushell > const foo = path self Error: × Error: nu::shell::file_not_found │ │ × File not found │ ╭─[entry #3:1:13] │ 1 │ const foo = path self │ · ─────┬──── │ · ╰── Couldn't find current file │ ╰──── │ ╭─[entry #3:1:13] 1 │ const foo = path self · ─────┬──── · ╰── Encountered error during parse-time evaluation ╰──── ``` # Comparison with #14305 ## Pros - Self contained implementation, does not require changes in the parser. - More concise usage, especially with parent directories. --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/path/mod.rs | 2 + crates/nu-command/src/path/self_.rs | 129 +++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 crates/nu-command/src/path/self_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 0a4075c532..a4288c685c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -106,6 +106,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { bind_command! { Path, PathBasename, + PathSelf, PathDirname, PathExists, PathExpand, diff --git a/crates/nu-command/src/path/mod.rs b/crates/nu-command/src/path/mod.rs index 8a7b9a6f2c..4b6405c8d8 100644 --- a/crates/nu-command/src/path/mod.rs +++ b/crates/nu-command/src/path/mod.rs @@ -6,6 +6,7 @@ mod join; mod parse; pub mod path_; mod relative_to; +mod self_; mod split; mod r#type; @@ -18,6 +19,7 @@ pub use parse::SubCommand as PathParse; pub use path_::PathCommand as Path; pub use r#type::SubCommand as PathType; pub use relative_to::SubCommand as PathRelativeTo; +pub use self_::SubCommand as PathSelf; pub use split::SubCommand as PathSplit; use nu_protocol::{ShellError, Span, Value}; diff --git a/crates/nu-command/src/path/self_.rs b/crates/nu-command/src/path/self_.rs new file mode 100644 index 0000000000..242fbea7a8 --- /dev/null +++ b/crates/nu-command/src/path/self_.rs @@ -0,0 +1,129 @@ +use nu_engine::command_prelude::*; +use nu_path::expand_path_with; +use nu_protocol::engine::StateWorkingSet; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path self" + } + + fn signature(&self) -> Signature { + Signature::build("path self") + .input_output_type(Type::Nothing, Type::String) + .allow_variants_without_examples(true) + .optional( + "path", + SyntaxShape::Filepath, + "Path to get instead of the current file.", + ) + .category(Category::Path) + } + + fn description(&self) -> &str { + "Get the absolute path of the script or module containing this command at parse time." + } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Err(ShellError::GenericError { + error: "this command can only run during parse-time".into(), + msg: "can't run after parse-time".into(), + span: Some(call.head), + help: Some("try assigning this command's output to a const variable".into()), + inner: vec![], + }) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + _input: PipelineData, + ) -> Result { + let path: Option = call.opt_const(working_set, 0)?; + let cwd = working_set.permanent_state.cwd(None)?; + let current_file = + working_set + .files + .top() + .ok_or_else(|| ShellError::FileNotFoundCustom { + msg: "Couldn't find current file".into(), + span: call.head, + })?; + + let out = if let Some(path) = path { + let dir = expand_path_with( + current_file + .parent() + .ok_or_else(|| ShellError::FileNotFoundCustom { + msg: "Couldn't find current file's parent.".into(), + span: call.head, + })?, + &cwd, + true, + ); + Value::string( + expand_path_with(path, dir, false).to_string_lossy(), + call.head, + ) + } else { + Value::string( + expand_path_with(current_file, &cwd, true).to_string_lossy(), + call.head, + ) + }; + + Ok(out.into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get the path of the current file", + example: r#"const current_file = path self"#, + result: None, + }, + Example { + description: "Get the path of the directory containing the current file", + example: r#"const current_file = path self ."#, + result: None, + }, + #[cfg(windows)] + Example { + description: "Get the absolute form of a path relative to the current file", + example: r#"const current_file = path self ..\foo"#, + result: None, + }, + #[cfg(not(windows))] + Example { + description: "Get the absolute form of a path relative to the current file", + example: r#"const current_file = path self ../foo"#, + result: None, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +}