Files
nushell/crates/nu-command/src/conversions/into/glob.rs
Wind 387328fe73 Glob: don't allow implicit casting between glob and string (#11992)
# Description
As title, currently on latest main, nushell confused user if it allows
implicit casting between glob and string:
```nushell
let x = "*.txt"
def glob-test [g: glob] { open $g } 
glob-test $x
```
It always expand the glob although `$x` is defined as a string.
This pr implements a solution from @kubouch :
> We could make it really strict and disallow all autocasting between
globs and strings because that's what's causing the "magic" confusion.
Then, modify all builtins that accept globs to accept oneof(glob,
string) and the rules would be that globs always expand and strings
never expand

# User-Facing Changes
After this pr, user needs to use `into glob` to invoke `glob-test`, if
user pass a string variable:
```nushell
let x = "*.txt"
def glob-test [g: glob] { open $g } 
glob-test ($x | into glob)
```
Or else nushell will return an error.
```
 3 │ glob-test $x
   ·           ─┬
   ·            ╰── can't convert string to glob
```

# Tests + Formatting
Done

# After Submitting
Nan
2024-02-28 23:05:35 +08:00

134 lines
3.7 KiB
Rust

use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
};
struct Arguments {
cell_paths: Option<Vec<CellPath>>,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"into glob"
}
fn signature(&self) -> Signature {
Signature::build("into glob")
.input_output_types(vec![
(Type::String, Type::Glob),
(
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::Glob)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
])
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
.rest(
"rest",
SyntaxShape::CellPath,
"For a data structure input, convert data at the given cell paths.",
)
.category(Category::Conversions)
}
fn usage(&self) -> &str {
"Convert value to glob."
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "text"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
glob_helper(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert string to glob",
example: "'1234' | into glob",
result: Some(Value::test_glob("1234")),
},
Example {
description: "convert filepath to glob",
example: "ls Cargo.toml | get name | into glob",
result: None,
},
]
}
}
fn glob_helper(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let cell_paths = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments { cell_paths };
match input {
PipelineData::ExternalStream { stdout: None, .. } => {
Ok(Value::glob(String::new(), false, head).into_pipeline_data())
}
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_string()?;
Ok(Value::glob(output.item, false, head).into_pipeline_data())
}
_ => operate(action, args, input, head, engine_state.ctrlc.clone()),
}
}
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
match input {
Value::String { val, .. } => Value::glob(val.to_string(), false, span),
x => Value::error(
ShellError::CantConvert {
to_type: String::from("glob"),
from_type: x.get_type().to_string(),
span,
help: None,
},
span,
),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}