Enable conditional source and use patterns by allowing null as a no-op module (#14773)

Related:
- #14329
- #13872
- #8214

# Description & User-Facing Changes

This PR allows enables the following uses, which are all no-op.
```nushell
source null
source-env null
use null
overlay use null
```

The motivation for this change is conditional sourcing of files. For
example, with this change `login.nu` may be deprecated and replaced with
the following code in `config.nu`
```nushell
const login_module = if $nu.is-login { "login.nu" } else { null }
source $login_module
```

# Tests + Formatting
I'm hoping for CI to pass 😄

# After Submitting
Add a part about the conditional sourcing pattern to the website.
This commit is contained in:
Bahex
2025-01-09 15:37:27 +03:00
committed by GitHub
parent 5cf6dea997
commit 79f19f2fc7
7 changed files with 160 additions and 25 deletions

View File

@ -19,8 +19,8 @@ impl Command for SourceEnv {
.input_output_types(vec![(Type::Any, Type::Any)])
.required(
"filename",
SyntaxShape::String, // type is string to avoid automatically canonicalizing the path
"The filepath to the script file to source the environment from.",
SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]), // type is string to avoid automatically canonicalizing the path
"The filepath to the script file to source the environment from (`null` for no-op).",
)
.category(Category::Core)
}
@ -45,6 +45,10 @@ impl Command for SourceEnv {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if call.get_parser_info(caller_stack, "noop").is_some() {
return Ok(PipelineData::empty());
}
let source_filename: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
// Note: this hidden positional is the block_id that corresponded to the 0th position
@ -99,10 +103,17 @@ impl Command for SourceEnv {
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Sources the environment from foo.nu in the current context",
example: r#"source-env foo.nu"#,
result: None,
}]
vec![
Example {
description: "Sources the environment from foo.nu in the current context",
example: r#"source-env foo.nu"#,
result: None,
},
Example {
description: "Sourcing `null` is a no-op.",
example: r#"source-env null"#,
result: None,
},
]
}
}

View File

@ -16,8 +16,8 @@ impl Command for Source {
.input_output_types(vec![(Type::Any, Type::Any)])
.required(
"filename",
SyntaxShape::Filepath,
"The filepath to the script file to source.",
SyntaxShape::OneOf(vec![SyntaxShape::Filepath, SyntaxShape::Nothing]),
"The filepath to the script file to source (`null` for no-op).",
)
.category(Category::Core)
}
@ -42,6 +42,9 @@ impl Command for Source {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if call.get_parser_info(stack, "noop").is_some() {
return Ok(PipelineData::empty());
}
// Note: two hidden positionals are used here that are injected by the parser:
// 1. The block_id that corresponded to the 0th position
// 2. The block_id_name that corresponded to the file name at the 0th position
@ -107,6 +110,16 @@ impl Command for Source {
example: r#"source ./foo.nu; say-hi"#,
result: None,
},
Example {
description: "Sourcing `null` is a no-op.",
example: r#"source null"#,
result: None,
},
Example {
description: "Source can be used with const variables.",
example: r#"const file = if $nu.is-interactive { "interactive.nu" } else { null }; source $file"#,
result: None,
}
]
}
}