Merge remote-tracking branch 'upstream' into sort-custom
This commit is contained in:
commit
dfd618133a
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
|
@ -10,4 +10,4 @@ jobs:
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@v1.22.4
|
uses: crate-ci/typos@v1.22.7
|
||||||
|
|
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -920,6 +920,15 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -1678,9 +1687,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git2"
|
name = "git2"
|
||||||
version = "0.18.3"
|
version = "0.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
|
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2279,9 +2288,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libgit2-sys"
|
name = "libgit2-sys"
|
||||||
version = "0.16.2+1.7.2"
|
version = "0.17.0+1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
|
checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -3020,6 +3029,17 @@ dependencies = [
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-derive-value"
|
||||||
|
version = "0.94.3"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.60",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-engine"
|
name = "nu-engine"
|
||||||
version = "0.94.3"
|
version = "0.94.3"
|
||||||
|
@ -3209,11 +3229,13 @@ dependencies = [
|
||||||
"byte-unit",
|
"byte-unit",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-humanize",
|
"chrono-humanize",
|
||||||
|
"convert_case",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lru",
|
"lru",
|
||||||
"miette",
|
"miette",
|
||||||
"nix",
|
"nix",
|
||||||
|
"nu-derive-value",
|
||||||
"nu-path",
|
"nu-path",
|
||||||
"nu-system",
|
"nu-system",
|
||||||
"nu-test-support",
|
"nu-test-support",
|
||||||
|
|
|
@ -39,6 +39,7 @@ members = [
|
||||||
"crates/nu-lsp",
|
"crates/nu-lsp",
|
||||||
"crates/nu-pretty-hex",
|
"crates/nu-pretty-hex",
|
||||||
"crates/nu-protocol",
|
"crates/nu-protocol",
|
||||||
|
"crates/nu-derive-value",
|
||||||
"crates/nu-plugin",
|
"crates/nu-plugin",
|
||||||
"crates/nu-plugin-core",
|
"crates/nu-plugin-core",
|
||||||
"crates/nu-plugin-engine",
|
"crates/nu-plugin-engine",
|
||||||
|
@ -74,6 +75,7 @@ chardetng = "0.1.17"
|
||||||
chrono = { default-features = false, version = "0.4.34" }
|
chrono = { default-features = false, version = "0.4.34" }
|
||||||
chrono-humanize = "0.2.3"
|
chrono-humanize = "0.2.3"
|
||||||
chrono-tz = "0.8"
|
chrono-tz = "0.8"
|
||||||
|
convert_case = "0.6"
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.8"
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
csv = "1.3"
|
csv = "1.3"
|
||||||
|
@ -123,11 +125,14 @@ pathdiff = "0.2"
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
pretty_assertions = "1.4"
|
pretty_assertions = "1.4"
|
||||||
print-positions = "0.6"
|
print-positions = "0.6"
|
||||||
|
proc-macro-error = { version = "1.0", default-features = false }
|
||||||
|
proc-macro2 = "1.0"
|
||||||
procfs = "0.16.0"
|
procfs = "0.16.0"
|
||||||
pwd = "1.3"
|
pwd = "1.3"
|
||||||
quick-xml = "0.31.0"
|
quick-xml = "0.31.0"
|
||||||
quickcheck = "1.0"
|
quickcheck = "1.0"
|
||||||
quickcheck_macros = "1.0"
|
quickcheck_macros = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
ratatui = "0.26"
|
ratatui = "0.26"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
|
@ -147,6 +152,7 @@ serde_urlencoded = "0.7.1"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
syn = "2.0"
|
||||||
sysinfo = "0.30"
|
sysinfo = "0.30"
|
||||||
tabled = { version = "0.14.0", default-features = false }
|
tabled = { version = "0.14.0", default-features = false }
|
||||||
tempfile = "3.10"
|
tempfile = "3.10"
|
||||||
|
|
|
@ -222,6 +222,7 @@ Please submit an issue or PR to be added to this list.
|
||||||
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
- [clap](https://github.com/clap-rs/clap/tree/master/clap_complete_nushell)
|
||||||
- [Dorothy](http://github.com/bevry/dorothy)
|
- [Dorothy](http://github.com/bevry/dorothy)
|
||||||
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
- [Direnv](https://github.com/direnv/direnv/blob/master/docs/hook.md#nushell)
|
||||||
|
- [x-cmd](https://x-cmd.com/mod/nu)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,7 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) {
|
||||||
&mut engine,
|
&mut engine,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
None,
|
Default::default(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -90,8 +89,7 @@ fn bench_command(
|
||||||
&mut engine,
|
&mut engine,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
PipelineData::empty(),
|
PipelineData::empty(),
|
||||||
None,
|
Default::default(),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,15 +8,45 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EvaluateCommandsOpts {
|
||||||
|
pub table_mode: Option<Value>,
|
||||||
|
pub error_style: Option<Value>,
|
||||||
|
pub no_newline: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Run a command (or commands) given to us by the user
|
/// Run a command (or commands) given to us by the user
|
||||||
pub fn evaluate_commands(
|
pub fn evaluate_commands(
|
||||||
commands: &Spanned<String>,
|
commands: &Spanned<String>,
|
||||||
engine_state: &mut EngineState,
|
engine_state: &mut EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
table_mode: Option<Value>,
|
opts: EvaluateCommandsOpts,
|
||||||
no_newline: bool,
|
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
|
let EvaluateCommandsOpts {
|
||||||
|
table_mode,
|
||||||
|
error_style,
|
||||||
|
no_newline,
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
// Handle the configured error style early
|
||||||
|
if let Some(e_style) = error_style {
|
||||||
|
match e_style.coerce_str()?.parse() {
|
||||||
|
Ok(e_style) => {
|
||||||
|
Arc::make_mut(&mut engine_state.config).error_style = e_style;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
|
error: "Invalid value for `--error-style`".into(),
|
||||||
|
msg: err.into(),
|
||||||
|
span: Some(e_style.span()),
|
||||||
|
help: None,
|
||||||
|
inner: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Translate environment variables from Strings to Values
|
// Translate environment variables from Strings to Values
|
||||||
convert_env_values(engine_state, stack)?;
|
convert_env_values(engine_state, stack)?;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ mod validation;
|
||||||
pub use commands::add_cli_context;
|
pub use commands::add_cli_context;
|
||||||
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
pub use completions::{FileCompletion, NuCompleter, SemanticSuggestion, SuggestionKind};
|
||||||
pub use config_files::eval_config_contents;
|
pub use config_files::eval_config_contents;
|
||||||
pub use eval_cmds::evaluate_commands;
|
pub use eval_cmds::{evaluate_commands, EvaluateCommandsOpts};
|
||||||
pub use eval_file::evaluate_file;
|
pub use eval_file::evaluate_file;
|
||||||
pub use menus::NuHelpCompleter;
|
pub use menus::NuHelpCompleter;
|
||||||
pub use nu_cmd_base::util::get_init_cwd;
|
pub use nu_cmd_base::util::get_init_cwd;
|
||||||
|
|
|
@ -194,7 +194,7 @@ pub fn eval_hook(
|
||||||
let Some(follow) = val.get("code") else {
|
let Some(follow) = val.get("code") else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: "code".into(),
|
col_name: "code".into(),
|
||||||
span,
|
span: Some(span),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,13 +20,14 @@ pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf
|
||||||
|
|
||||||
type MakeRangeError = fn(&str, Span) -> ShellError;
|
type MakeRangeError = fn(&str, Span) -> ShellError;
|
||||||
|
|
||||||
|
/// Returns a inclusive pair of boundary in given `range`.
|
||||||
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
pub fn process_range(range: &Range) -> Result<(isize, isize), MakeRangeError> {
|
||||||
match range {
|
match range {
|
||||||
Range::IntRange(range) => {
|
Range::IntRange(range) => {
|
||||||
let start = range.start().try_into().unwrap_or(0);
|
let start = range.start().try_into().unwrap_or(0);
|
||||||
let end = match range.end() {
|
let end = match range.end() {
|
||||||
Bound::Included(v) => (v + 1) as isize,
|
Bound::Included(v) => v as isize,
|
||||||
Bound::Excluded(v) => v as isize,
|
Bound::Excluded(v) => (v - 1) as isize,
|
||||||
Bound::Unbounded => isize::MAX,
|
Bound::Unbounded => isize::MAX,
|
||||||
};
|
};
|
||||||
Ok((start, end))
|
Ok((start, end))
|
||||||
|
|
|
@ -10,7 +10,7 @@ impl Command for Try {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Try to run a block, if it fails optionally run a catch block."
|
"Try to run a block, if it fails optionally run a catch closure."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
@ -18,7 +18,7 @@ impl Command for Try {
|
||||||
.input_output_types(vec![(Type::Any, Type::Any)])
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
.required("try_block", SyntaxShape::Block, "Block to run.")
|
.required("try_block", SyntaxShape::Block, "Block to run.")
|
||||||
.optional(
|
.optional(
|
||||||
"catch_block",
|
"catch_closure",
|
||||||
SyntaxShape::Keyword(
|
SyntaxShape::Keyword(
|
||||||
b"catch".to_vec(),
|
b"catch".to_vec(),
|
||||||
Box::new(SyntaxShape::OneOf(vec![
|
Box::new(SyntaxShape::OneOf(vec![
|
||||||
|
@ -26,7 +26,7 @@ impl Command for Try {
|
||||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||||
])),
|
])),
|
||||||
),
|
),
|
||||||
"Block to run if try block fails.",
|
"Closure to run if try block fails.",
|
||||||
)
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
@ -85,9 +85,14 @@ impl Command for Try {
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Try to run a missing command",
|
description: "Try to run a missing command",
|
||||||
example: "try { asdfasdf } catch { 'missing' } ",
|
example: "try { asdfasdf } catch { 'missing' }",
|
||||||
result: Some(Value::test_string("missing")),
|
result: Some(Value::test_string("missing")),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Try to run a missing command and report the message",
|
||||||
|
example: "try { asdfasdf } catch { |err| $err.msg }",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,48 +128,24 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
let range = &args.indexes;
|
let range = &args.indexes;
|
||||||
match input {
|
match input {
|
||||||
Value::Binary { val, .. } => {
|
Value::Binary { val, .. } => {
|
||||||
use std::cmp::{self, Ordering};
|
|
||||||
let len = val.len() as isize;
|
let len = val.len() as isize;
|
||||||
|
|
||||||
let start = if range.0 < 0 { range.0 + len } else { range.0 };
|
let start = if range.0 < 0 { range.0 + len } else { range.0 };
|
||||||
|
let end = if range.1 < 0 { range.1 + len } else { range.1 };
|
||||||
|
|
||||||
let end = if range.1 < 0 {
|
if start > end {
|
||||||
cmp::max(range.1 + len, 0)
|
|
||||||
} else {
|
|
||||||
range.1
|
|
||||||
};
|
|
||||||
|
|
||||||
if start < len && end >= 0 {
|
|
||||||
match start.cmp(&end) {
|
|
||||||
Ordering::Equal => Value::binary(vec![], head),
|
|
||||||
Ordering::Greater => Value::error(
|
|
||||||
ShellError::TypeMismatch {
|
|
||||||
err_message: "End must be greater than or equal to Start".to_string(),
|
|
||||||
span: head,
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
Ordering::Less => Value::binary(
|
|
||||||
if end == isize::MAX {
|
|
||||||
val.iter()
|
|
||||||
.skip(start as usize)
|
|
||||||
.copied()
|
|
||||||
.collect::<Vec<u8>>()
|
|
||||||
} else {
|
|
||||||
val.iter()
|
|
||||||
.skip(start as usize)
|
|
||||||
.take((end - start) as usize)
|
|
||||||
.copied()
|
|
||||||
.collect()
|
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Value::binary(vec![], head)
|
Value::binary(vec![], head)
|
||||||
|
} else {
|
||||||
|
let val_iter = val.iter().skip(start as usize);
|
||||||
|
Value::binary(
|
||||||
|
if end == isize::MAX {
|
||||||
|
val_iter.copied().collect::<Vec<u8>>()
|
||||||
|
} else {
|
||||||
|
val_iter.take((end - start + 1) as usize).copied().collect()
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
|
|
||||||
other => Value::error(
|
other => Value::error(
|
||||||
|
|
|
@ -194,7 +194,7 @@ fn run_histogram(
|
||||||
if inputs.is_empty() {
|
if inputs.is_empty() {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: head_span,
|
span: Some(head_span),
|
||||||
src_span: list_span,
|
src_span: list_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ fn record_to_path_member(
|
||||||
let Some(value) = record.get("value") else {
|
let Some(value) = record.get("value") else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: "value".into(),
|
col_name: "value".into(),
|
||||||
span: val_span,
|
span: Some(val_span),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -130,7 +130,7 @@ pub fn split(
|
||||||
Some(group_key) => Ok(group_key.coerce_string()?),
|
Some(group_key) => Ok(group_key.coerce_string()?),
|
||||||
None => Err(ShellError::CantFindColumn {
|
None => Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.item.to_string(),
|
col_name: column_name.item.to_string(),
|
||||||
span: column_name.span,
|
span: Some(column_name.span),
|
||||||
src_span: row.span(),
|
src_span: row.span(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ fn validate(vec: &[Value], columns: &[String], span: Span) -> Result<(), ShellEr
|
||||||
if let Some(nonexistent) = nonexistent_column(columns, record.columns()) {
|
if let Some(nonexistent) = nonexistent_column(columns, record.columns()) {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: nonexistent,
|
col_name: nonexistent,
|
||||||
span,
|
span: Some(span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ use nu_cmd_base::{
|
||||||
};
|
};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{engine::StateWorkingSet, Range};
|
use nu_protocol::{engine::StateWorkingSet, Range};
|
||||||
use std::cmp::Ordering;
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -151,6 +150,11 @@ impl Command for SubCommand {
|
||||||
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5",
|
example: " '🇯🇵ほげ ふが ぴよ' | str substring --grapheme-clusters 4..5",
|
||||||
result: Some(Value::test_string("ふが")),
|
result: Some(Value::test_string("ふが")),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "sub string by negative index",
|
||||||
|
example: " 'good nushell' | str substring 5..-2",
|
||||||
|
result: Some(Value::test_string("nushel")),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,56 +171,46 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
options.0
|
options.0
|
||||||
};
|
};
|
||||||
let end: isize = if options.1 < 0 {
|
let end: isize = if options.1 < 0 {
|
||||||
std::cmp::max(len + options.1, 0)
|
options.1 + len
|
||||||
} else {
|
} else {
|
||||||
options.1
|
options.1
|
||||||
};
|
};
|
||||||
|
|
||||||
if start < len && end >= 0 {
|
if start > end {
|
||||||
match start.cmp(&end) {
|
Value::string("", head)
|
||||||
Ordering::Equal => Value::string("", head),
|
} else {
|
||||||
Ordering::Greater => Value::error(
|
Value::string(
|
||||||
ShellError::TypeMismatch {
|
{
|
||||||
err_message: "End must be greater than or equal to Start".to_string(),
|
if end == isize::MAX {
|
||||||
span: head,
|
if args.graphemes {
|
||||||
},
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
Ordering::Less => Value::string(
|
|
||||||
{
|
|
||||||
if end == isize::MAX {
|
|
||||||
if args.graphemes {
|
|
||||||
s.graphemes(true)
|
|
||||||
.skip(start as usize)
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join("")
|
|
||||||
} else {
|
|
||||||
String::from_utf8_lossy(
|
|
||||||
&s.bytes().skip(start as usize).collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
} else if args.graphemes {
|
|
||||||
s.graphemes(true)
|
s.graphemes(true)
|
||||||
.skip(start as usize)
|
.skip(start as usize)
|
||||||
.take((end - start) as usize)
|
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.join("")
|
.join("")
|
||||||
} else {
|
} else {
|
||||||
String::from_utf8_lossy(
|
String::from_utf8_lossy(
|
||||||
&s.bytes()
|
&s.bytes().skip(start as usize).collect::<Vec<_>>(),
|
||||||
.skip(start as usize)
|
|
||||||
.take((end - start) as usize)
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
)
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
},
|
} else if args.graphemes {
|
||||||
head,
|
s.graphemes(true)
|
||||||
),
|
.skip(start as usize)
|
||||||
}
|
.take((end - start + 1) as usize)
|
||||||
} else {
|
.collect::<Vec<&str>>()
|
||||||
Value::string("", head)
|
.join("")
|
||||||
|
} else {
|
||||||
|
String::from_utf8_lossy(
|
||||||
|
&s.bytes()
|
||||||
|
.skip(start as usize)
|
||||||
|
.take((end - start + 1) as usize)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
|
@ -243,6 +237,7 @@ mod tests {
|
||||||
|
|
||||||
test_examples(SubCommand {})
|
test_examples(SubCommand {})
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
struct Expectation<'a> {
|
struct Expectation<'a> {
|
||||||
options: (isize, isize),
|
options: (isize, isize),
|
||||||
expected: &'a str,
|
expected: &'a str,
|
||||||
|
@ -266,18 +261,19 @@ mod tests {
|
||||||
let word = Value::test_string("andres");
|
let word = Value::test_string("andres");
|
||||||
|
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
expectation("a", (0, 1)),
|
expectation("a", (0, 0)),
|
||||||
expectation("an", (0, 2)),
|
expectation("an", (0, 1)),
|
||||||
expectation("and", (0, 3)),
|
expectation("and", (0, 2)),
|
||||||
expectation("andr", (0, 4)),
|
expectation("andr", (0, 3)),
|
||||||
expectation("andre", (0, 5)),
|
expectation("andre", (0, 4)),
|
||||||
|
expectation("andres", (0, 5)),
|
||||||
expectation("andres", (0, 6)),
|
expectation("andres", (0, 6)),
|
||||||
expectation("", (0, -6)),
|
expectation("a", (0, -6)),
|
||||||
expectation("a", (0, -5)),
|
expectation("an", (0, -5)),
|
||||||
expectation("an", (0, -4)),
|
expectation("and", (0, -4)),
|
||||||
expectation("and", (0, -3)),
|
expectation("andr", (0, -3)),
|
||||||
expectation("andr", (0, -2)),
|
expectation("andre", (0, -2)),
|
||||||
expectation("andre", (0, -1)),
|
expectation("andres", (0, -1)),
|
||||||
// str substring [ -4 , _ ]
|
// str substring [ -4 , _ ]
|
||||||
// str substring -4 ,
|
// str substring -4 ,
|
||||||
expectation("dres", (-4, isize::MAX)),
|
expectation("dres", (-4, isize::MAX)),
|
||||||
|
@ -292,6 +288,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
for expectation in &cases {
|
for expectation in &cases {
|
||||||
|
println!("{:?}", expectation);
|
||||||
let expected = expectation.expected;
|
let expected = expectation.expected;
|
||||||
let actual = action(
|
let actual = action(
|
||||||
&word,
|
&word,
|
||||||
|
|
|
@ -13,7 +13,7 @@ impl Command for SysUsers {
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build("sys users")
|
Signature::build("sys users")
|
||||||
.category(Category::System)
|
.category(Category::System)
|
||||||
.input_output_types(vec![(Type::Nothing, Type::record())])
|
.input_output_types(vec![(Type::Nothing, Type::table())])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
|
|
|
@ -170,7 +170,10 @@ impl Command for Table {
|
||||||
}),
|
}),
|
||||||
Value::test_record(record! {
|
Value::test_record(record! {
|
||||||
"a" => Value::test_int(3),
|
"a" => Value::test_int(3),
|
||||||
"b" => Value::test_int(4),
|
"b" => Value::test_list(vec![
|
||||||
|
Value::test_int(4),
|
||||||
|
Value::test_int(4),
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
@ -184,7 +187,10 @@ impl Command for Table {
|
||||||
}),
|
}),
|
||||||
Value::test_record(record! {
|
Value::test_record(record! {
|
||||||
"a" => Value::test_int(3),
|
"a" => Value::test_int(3),
|
||||||
"b" => Value::test_int(4),
|
"b" => Value::test_list(vec![
|
||||||
|
Value::test_int(4),
|
||||||
|
Value::test_int(4),
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,12 +31,12 @@ fn detect_columns_with_legacy_and_flag_c() {
|
||||||
(
|
(
|
||||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||||
"[[c1,c3,c4,c5]; ['a b',c,d,e]]",
|
"[[c1,c3,c4,c5]; ['a b',c,d,e]]",
|
||||||
"0..0",
|
"0..1",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||||
"[[c1,c2,c3,c4]; [a,b,c,'d e']]",
|
"[[c1,c2,c3,c4]; [a,b,c,'d e']]",
|
||||||
"(-2)..(-2)",
|
"(-2)..(-1)",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
"$\"c1 c2 c3 c4 c5(char nl)a b c d e\"",
|
||||||
|
@ -72,10 +72,10 @@ drwxr-xr-x 2 root root 4.0K Mar 20 08:28 =(char nl)
|
||||||
drwxr-xr-x 4 root root 4.0K Mar 20 08:18 ~(char nl)
|
drwxr-xr-x 4 root root 4.0K Mar 20 08:18 ~(char nl)
|
||||||
-rw-r--r-- 1 root root 3.0K Mar 20 07:23 ~asdf(char nl)\"";
|
-rw-r--r-- 1 root root 3.0K Mar 20 07:23 ~asdf(char nl)\"";
|
||||||
let expected = "[
|
let expected = "[
|
||||||
['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column8'];
|
['column0', 'column1', 'column2', 'column3', 'column4', 'column5', 'column7', 'column8'];
|
||||||
['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20 08:28', '='],
|
['drwxr-xr-x', '2', 'root', 'root', '4.0K', 'Mar 20', '08:28', '='],
|
||||||
['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20 08:18', '~'],
|
['drwxr-xr-x', '4', 'root', 'root', '4.0K', 'Mar 20', '08:18', '~'],
|
||||||
['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20 07:23', '~asdf']
|
['-rw-r--r--', '1', 'root', 'root', '3.0K', 'Mar 20', '07:23', '~asdf']
|
||||||
]";
|
]";
|
||||||
let range = "5..6";
|
let range = "5..6";
|
||||||
let cmd = format!(
|
let cmd = format!(
|
||||||
|
|
|
@ -4,8 +4,9 @@ use nu_test_support::nu;
|
||||||
fn error_label_works() {
|
fn error_label_works() {
|
||||||
let actual = nu!("error make {msg:foo label:{text:unseen}}");
|
let actual = nu!("error make {msg:foo label:{text:unseen}}");
|
||||||
|
|
||||||
assert!(actual.err.contains("unseen"));
|
assert!(actual
|
||||||
assert!(actual.err.contains("╰──"));
|
.err
|
||||||
|
.contains("label at line 1, columns 1 to 10: unseen"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -255,7 +255,7 @@ fn substrings_the_input() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn substring_errors_if_start_index_is_greater_than_end_index() {
|
fn substring_empty_if_start_index_is_greater_than_end_index() {
|
||||||
Playground::setup("str_test_9", |dirs, sandbox| {
|
Playground::setup("str_test_9", |dirs, sandbox| {
|
||||||
sandbox.with_files(&[FileWithContent(
|
sandbox.with_files(&[FileWithContent(
|
||||||
"sample.toml",
|
"sample.toml",
|
||||||
|
@ -270,12 +270,10 @@ fn substring_errors_if_start_index_is_greater_than_end_index() {
|
||||||
r#"
|
r#"
|
||||||
open sample.toml
|
open sample.toml
|
||||||
| str substring 6..4 fortune.teller.phone
|
| str substring 6..4 fortune.teller.phone
|
||||||
|
| get fortune.teller.phone
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
assert_eq!(actual.out, "")
|
||||||
assert!(actual
|
|
||||||
.err
|
|
||||||
.contains("End must be greater than or equal to Start"))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,6 +373,21 @@ fn substrings_the_input_and_treats_end_index_as_length_if_blank_end_index_given(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn substring_by_negative_index() {
|
||||||
|
Playground::setup("str_test_13", |dirs, _| {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), "'apples' | str substring 0..-1",
|
||||||
|
);
|
||||||
|
assert_eq!(actual.out, "apples");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), "'apples' | str substring 0..<-1",
|
||||||
|
);
|
||||||
|
assert_eq!(actual.out, "apple");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn str_reverse() {
|
fn str_reverse() {
|
||||||
let actual = nu!(r#"
|
let actual = nu!(r#"
|
||||||
|
|
21
crates/nu-derive-value/Cargo.toml
Normal file
21
crates/nu-derive-value/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
authors = ["The Nushell Project Developers"]
|
||||||
|
description = "Macros implementation of #[derive(FromValue, IntoValue)]"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu-derive-value"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value"
|
||||||
|
version = "0.94.3"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
# we can only use exposed macros in doctests really,
|
||||||
|
# so we cannot test anything useful in a doctest
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = { workspace = true }
|
||||||
|
syn = { workspace = true }
|
||||||
|
quote = { workspace = true }
|
||||||
|
proc-macro-error = { workspace = true }
|
||||||
|
convert_case = { workspace = true }
|
21
crates/nu-derive-value/LICENSE
Normal file
21
crates/nu-derive-value/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 - 2023 The Nushell Project Developers
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
116
crates/nu-derive-value/src/attributes.rs
Normal file
116
crates/nu-derive-value/src/attributes.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use convert_case::Case;
|
||||||
|
use syn::{spanned::Spanned, Attribute, Fields, LitStr};
|
||||||
|
|
||||||
|
use crate::{error::DeriveError, HELPER_ATTRIBUTE};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ContainerAttributes {
|
||||||
|
pub rename_all: Case,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ContainerAttributes {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
rename_all: Case::Snake,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerAttributes {
|
||||||
|
pub fn parse_attrs<'a, M>(
|
||||||
|
iter: impl Iterator<Item = &'a Attribute>,
|
||||||
|
) -> Result<Self, DeriveError<M>> {
|
||||||
|
let mut container_attrs = ContainerAttributes::default();
|
||||||
|
for attr in filter(iter) {
|
||||||
|
// This is a container to allow returning derive errors inside the parse_nested_meta fn.
|
||||||
|
let mut err = Ok(());
|
||||||
|
|
||||||
|
attr.parse_nested_meta(|meta| {
|
||||||
|
let ident = meta.path.require_ident()?;
|
||||||
|
match ident.to_string().as_str() {
|
||||||
|
"rename_all" => {
|
||||||
|
// The matched case are all useful variants from `convert_case` with aliases
|
||||||
|
// that `serde` uses.
|
||||||
|
let case: LitStr = meta.value()?.parse()?;
|
||||||
|
let case = match case.value().as_str() {
|
||||||
|
"UPPER CASE" | "UPPER WITH SPACES CASE" => Case::Upper,
|
||||||
|
"lower case" | "lower with spaces case" => Case::Lower,
|
||||||
|
"Title Case" => Case::Title,
|
||||||
|
"camelCase" => Case::Camel,
|
||||||
|
"PascalCase" | "UpperCamelCase" => Case::Pascal,
|
||||||
|
"snake_case" => Case::Snake,
|
||||||
|
"UPPER_SNAKE_CASE" | "SCREAMING_SNAKE_CASE" => Case::UpperSnake,
|
||||||
|
"kebab-case" => Case::Kebab,
|
||||||
|
"COBOL-CASE" | "UPPER-KEBAB-CASE" | "SCREAMING-KEBAB-CASE" => {
|
||||||
|
Case::Cobol
|
||||||
|
}
|
||||||
|
"Train-Case" => Case::Train,
|
||||||
|
"flatcase" | "lowercase" => Case::Flat,
|
||||||
|
"UPPERFLATCASE" | "UPPERCASE" => Case::UpperFlat,
|
||||||
|
// Although very funny, we don't support `Case::{Toggle, Alternating}`,
|
||||||
|
// as we see no real benefit.
|
||||||
|
c => {
|
||||||
|
err = Err(DeriveError::InvalidAttributeValue {
|
||||||
|
value_span: case.span(),
|
||||||
|
value: Box::new(c.to_string()),
|
||||||
|
});
|
||||||
|
return Ok(()); // We stored the err in `err`.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
container_attrs.rename_all = case;
|
||||||
|
}
|
||||||
|
ident => {
|
||||||
|
err = Err(DeriveError::UnexpectedAttribute {
|
||||||
|
meta_span: ident.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.map_err(DeriveError::Syn)?;
|
||||||
|
|
||||||
|
err?; // Shortcircuit here if `err` is holding some error.
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(container_attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filter<'a>(
|
||||||
|
iter: impl Iterator<Item = &'a Attribute>,
|
||||||
|
) -> impl Iterator<Item = &'a Attribute> {
|
||||||
|
iter.filter(|attr| attr.path().is_ident(HELPER_ATTRIBUTE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The deny functions are built to easily deny the use of the helper attribute if used incorrectly.
|
||||||
|
// As the usage of it gets more complex, these functions might be discarded or replaced.
|
||||||
|
|
||||||
|
/// Deny any attribute that uses the helper attribute.
|
||||||
|
pub fn deny<M>(attrs: &[Attribute]) -> Result<(), DeriveError<M>> {
|
||||||
|
match filter(attrs.iter()).next() {
|
||||||
|
Some(attr) => Err(DeriveError::InvalidAttributePosition {
|
||||||
|
attribute_span: attr.span(),
|
||||||
|
}),
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deny any attributes that uses the helper attribute on any field.
|
||||||
|
pub fn deny_fields<M>(fields: &Fields) -> Result<(), DeriveError<M>> {
|
||||||
|
match fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
for field in fields.named.iter() {
|
||||||
|
deny(&field.attrs)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
for field in fields.unnamed.iter() {
|
||||||
|
deny(&field.attrs)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unit => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
82
crates/nu-derive-value/src/error.rs
Normal file
82
crates/nu-derive-value/src/error.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use std::{any, fmt::Debug, marker::PhantomData};
|
||||||
|
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use proc_macro_error::{Diagnostic, Level};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DeriveError<M> {
|
||||||
|
/// Marker variant, makes the `M` generic parameter valid.
|
||||||
|
_Marker(PhantomData<M>),
|
||||||
|
|
||||||
|
/// Parsing errors thrown by `syn`.
|
||||||
|
Syn(syn::parse::Error),
|
||||||
|
|
||||||
|
/// `syn::DeriveInput` was a union, currently not supported
|
||||||
|
UnsupportedUnions,
|
||||||
|
|
||||||
|
/// Only plain enums are supported right now.
|
||||||
|
UnsupportedEnums { fields_span: Span },
|
||||||
|
|
||||||
|
/// Found a `#[nu_value(x)]` attribute where `x` is unexpected.
|
||||||
|
UnexpectedAttribute { meta_span: Span },
|
||||||
|
|
||||||
|
/// Found a `#[nu_value(x)]` attribute at a invalid position.
|
||||||
|
InvalidAttributePosition { attribute_span: Span },
|
||||||
|
|
||||||
|
/// Found a valid `#[nu_value(x)]` attribute but the passed values is invalid.
|
||||||
|
InvalidAttributeValue {
|
||||||
|
value_span: Span,
|
||||||
|
value: Box<dyn Debug>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> From<DeriveError<M>> for Diagnostic {
|
||||||
|
fn from(value: DeriveError<M>) -> Self {
|
||||||
|
let derive_name = any::type_name::<M>().split("::").last().expect("not empty");
|
||||||
|
match value {
|
||||||
|
DeriveError::_Marker(_) => panic!("used marker variant"),
|
||||||
|
|
||||||
|
DeriveError::Syn(e) => Diagnostic::spanned(e.span(), Level::Error, e.to_string()),
|
||||||
|
|
||||||
|
DeriveError::UnsupportedUnions => Diagnostic::new(
|
||||||
|
Level::Error,
|
||||||
|
format!("`{derive_name}` cannot be derived from unions"),
|
||||||
|
)
|
||||||
|
.help("consider refactoring to a struct".to_string())
|
||||||
|
.note("if you really need a union, consider opening an issue on Github".to_string()),
|
||||||
|
|
||||||
|
DeriveError::UnsupportedEnums { fields_span } => Diagnostic::spanned(
|
||||||
|
fields_span,
|
||||||
|
Level::Error,
|
||||||
|
format!("`{derive_name}` can only be derived from plain enums"),
|
||||||
|
)
|
||||||
|
.help(
|
||||||
|
"consider refactoring your data type to a struct with a plain enum as a field"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.note("more complex enums could be implemented in the future".to_string()),
|
||||||
|
|
||||||
|
DeriveError::InvalidAttributePosition { attribute_span } => Diagnostic::spanned(
|
||||||
|
attribute_span,
|
||||||
|
Level::Error,
|
||||||
|
"invalid attribute position".to_string(),
|
||||||
|
)
|
||||||
|
.help(format!(
|
||||||
|
"check documentation for `{derive_name}` for valid placements"
|
||||||
|
)),
|
||||||
|
|
||||||
|
DeriveError::UnexpectedAttribute { meta_span } => {
|
||||||
|
Diagnostic::spanned(meta_span, Level::Error, "unknown attribute".to_string()).help(
|
||||||
|
format!("check documentation for `{derive_name}` for valid attributes"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DeriveError::InvalidAttributeValue { value_span, value } => {
|
||||||
|
Diagnostic::spanned(value_span, Level::Error, format!("invalid value {value:?}"))
|
||||||
|
.help(format!(
|
||||||
|
"check documentation for `{derive_name}` for valid attribute values"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
539
crates/nu-derive-value/src/from.rs
Normal file
539
crates/nu-derive-value/src/from.rs
Normal file
|
@ -0,0 +1,539 @@
|
||||||
|
use convert_case::Casing;
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{
|
||||||
|
spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::attributes::{self, ContainerAttributes};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FromValue;
|
||||||
|
type DeriveError = super::error::DeriveError<FromValue>;
|
||||||
|
|
||||||
|
/// Inner implementation of the `#[derive(FromValue)]` macro for structs and enums.
|
||||||
|
///
|
||||||
|
/// Uses `proc_macro2::TokenStream` for better testing support, unlike `proc_macro::TokenStream`.
|
||||||
|
///
|
||||||
|
/// This function directs the `FromValue` trait derivation to the correct implementation based on
|
||||||
|
/// the input type:
|
||||||
|
/// - For structs: [`derive_struct_from_value`]
|
||||||
|
/// - For enums: [`derive_enum_from_value`]
|
||||||
|
/// - Unions are not supported and will return an error.
|
||||||
|
pub fn derive_from_value(input: TokenStream2) -> Result<TokenStream2, DeriveError> {
|
||||||
|
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
||||||
|
match input.data {
|
||||||
|
Data::Struct(data_struct) => Ok(derive_struct_from_value(
|
||||||
|
input.ident,
|
||||||
|
data_struct,
|
||||||
|
input.generics,
|
||||||
|
input.attrs,
|
||||||
|
)?),
|
||||||
|
Data::Enum(data_enum) => Ok(derive_enum_from_value(
|
||||||
|
input.ident,
|
||||||
|
data_enum,
|
||||||
|
input.generics,
|
||||||
|
input.attrs,
|
||||||
|
)?),
|
||||||
|
Data::Union(_) => Err(DeriveError::UnsupportedUnions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the `#[derive(FromValue)]` macro for structs.
|
||||||
|
///
|
||||||
|
/// This function ensures that the helper attribute is not used anywhere, as it is not supported for
|
||||||
|
/// structs.
|
||||||
|
/// Other than this, this function provides the impl signature for `FromValue`.
|
||||||
|
/// The implementation for `FromValue::from_value` is handled by [`struct_from_value`] and the
|
||||||
|
/// `FromValue::expected_type` is handled by [`struct_expected_type`].
|
||||||
|
fn derive_struct_from_value(
|
||||||
|
ident: Ident,
|
||||||
|
data: DataStruct,
|
||||||
|
generics: Generics,
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
) -> Result<TokenStream2, DeriveError> {
|
||||||
|
attributes::deny(&attrs)?;
|
||||||
|
attributes::deny_fields(&data.fields)?;
|
||||||
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
let from_value_impl = struct_from_value(&data);
|
||||||
|
let expected_type_impl = struct_expected_type(&data.fields);
|
||||||
|
Ok(quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
||||||
|
#from_value_impl
|
||||||
|
#expected_type_impl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements `FromValue::from_value` for structs.
|
||||||
|
///
|
||||||
|
/// This function constructs the `from_value` function for structs.
|
||||||
|
/// The implementation is straightforward as most of the heavy lifting is handled by
|
||||||
|
/// `parse_value_via_fields`, and this function only needs to construct the signature around it.
|
||||||
|
///
|
||||||
|
/// For structs with named fields, this constructs a large return type where each field
|
||||||
|
/// contains the implementation for that specific field.
|
||||||
|
/// In structs with unnamed fields, a [`VecDeque`](std::collections::VecDeque) is used to load each
|
||||||
|
/// field one after another, and the result is used to construct the tuple.
|
||||||
|
/// For unit structs, this only checks if the input value is `Value::Nothing`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// These examples show what the macro would generate.
|
||||||
|
///
|
||||||
|
/// Struct with named fields:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Pet {
|
||||||
|
/// name: String,
|
||||||
|
/// age: u8,
|
||||||
|
/// favorite_toy: Option<String>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::FromValue for Pet {
|
||||||
|
/// fn from_value(
|
||||||
|
/// v: nu_protocol::Value
|
||||||
|
/// ) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||||
|
/// let span = v.span();
|
||||||
|
/// let mut record = v.into_record()?;
|
||||||
|
/// std::result::Result::Ok(Pet {
|
||||||
|
/// name: <String as nu_protocol::FromValue>::from_value(
|
||||||
|
/// record
|
||||||
|
/// .remove("name")
|
||||||
|
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
/// col_name: std::string::ToString::to_string("name"),
|
||||||
|
/// span: std::option::Option::None,
|
||||||
|
/// src_span: span
|
||||||
|
/// })?,
|
||||||
|
/// )?,
|
||||||
|
/// age: <u8 as nu_protocol::FromValue>::from_value(
|
||||||
|
/// record
|
||||||
|
/// .remove("age")
|
||||||
|
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
/// col_name: std::string::ToString::to_string("age"),
|
||||||
|
/// span: std::option::Option::None,
|
||||||
|
/// src_span: span
|
||||||
|
/// })?,
|
||||||
|
/// )?,
|
||||||
|
/// favorite_toy: <Option<String> as nu_protocol::FromValue>::from_value(
|
||||||
|
/// record
|
||||||
|
/// .remove("favorite_toy")
|
||||||
|
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
/// col_name: std::string::ToString::to_string("favorite_toy"),
|
||||||
|
/// span: std::option::Option::None,
|
||||||
|
/// src_span: span
|
||||||
|
/// })?,
|
||||||
|
/// )?,
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Struct with unnamed fields:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Color(u8, u8, u8);
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::FromValue for Color {
|
||||||
|
/// fn from_value(
|
||||||
|
/// v: nu_protocol::Value
|
||||||
|
/// ) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||||
|
/// let span = v.span();
|
||||||
|
/// let list = v.into_list()?;
|
||||||
|
/// let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list);
|
||||||
|
/// std::result::Result::Ok(Self(
|
||||||
|
/// {
|
||||||
|
/// <u8 as nu_protocol::FromValue>::from_value(
|
||||||
|
/// deque
|
||||||
|
/// .pop_front()
|
||||||
|
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
/// col_name: std::string::ToString::to_string(&0),
|
||||||
|
/// span: std::option::Option::None,
|
||||||
|
/// src_span: span
|
||||||
|
/// })?,
|
||||||
|
/// )?
|
||||||
|
/// },
|
||||||
|
/// {
|
||||||
|
/// <u8 as nu_protocol::FromValue>::from_value(
|
||||||
|
/// deque
|
||||||
|
/// .pop_front()
|
||||||
|
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
/// col_name: std::string::ToString::to_string(&1),
|
||||||
|
/// span: std::option::Option::None,
|
||||||
|
/// src_span: span
|
||||||
|
/// })?,
|
||||||
|
/// )?
|
||||||
|
/// },
|
||||||
|
/// {
|
||||||
|
/// <u8 as nu_protocol::FromValue>::from_value(
|
||||||
|
/// deque
|
||||||
|
/// .pop_front()
|
||||||
|
/// .ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
/// col_name: std::string::ToString::to_string(&2),
|
||||||
|
/// span: std::option::Option::None,
|
||||||
|
/// src_span: span
|
||||||
|
/// })?,
|
||||||
|
/// )?
|
||||||
|
/// }
|
||||||
|
/// ))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Unit struct:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Unicorn;
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::FromValue for Unicorn {
|
||||||
|
/// fn from_value(
|
||||||
|
/// v: nu_protocol::Value
|
||||||
|
/// ) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||||
|
/// match v {
|
||||||
|
/// nu_protocol::Value::Nothing {..} => Ok(Self),
|
||||||
|
/// v => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||||
|
/// to_type: std::string::ToString::to_string(&<Self as nu_protocol::FromValue>::expected_type()),
|
||||||
|
/// from_type: std::string::ToString::to_string(&v.get_type()),
|
||||||
|
/// span: v.span(),
|
||||||
|
/// help: std::option::Option::None
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn struct_from_value(data: &DataStruct) -> TokenStream2 {
|
||||||
|
let body = parse_value_via_fields(&data.fields, quote!(Self));
|
||||||
|
quote! {
|
||||||
|
fn from_value(
|
||||||
|
v: nu_protocol::Value
|
||||||
|
) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||||
|
#body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements `FromValue::expected_type` for structs.
|
||||||
|
///
|
||||||
|
/// This function constructs the `expected_type` function for structs.
|
||||||
|
/// The type depends on the `fields`: named fields construct a record type with every key and type
|
||||||
|
/// laid out.
|
||||||
|
/// Unnamed fields construct a custom type with the name in the format like
|
||||||
|
/// `list[type0, type1, type2]`.
|
||||||
|
/// No fields expect the `Type::Nothing`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// These examples show what the macro would generate.
|
||||||
|
///
|
||||||
|
/// Struct with named fields:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Pet {
|
||||||
|
/// name: String,
|
||||||
|
/// age: u8,
|
||||||
|
/// favorite_toy: Option<String>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::FromValue for Pet {
|
||||||
|
/// fn expected_type() -> nu_protocol::Type {
|
||||||
|
/// nu_protocol::Type::Record(
|
||||||
|
/// std::vec![
|
||||||
|
/// (
|
||||||
|
/// std::string::ToString::to_string("name"),
|
||||||
|
/// <String as nu_protocol::FromValue>::expected_type(),
|
||||||
|
/// ),
|
||||||
|
/// (
|
||||||
|
/// std::string::ToString::to_string("age"),
|
||||||
|
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
||||||
|
/// ),
|
||||||
|
/// (
|
||||||
|
/// std::string::ToString::to_string("favorite_toy"),
|
||||||
|
/// <Option<String> as nu_protocol::FromValue>::expected_type(),
|
||||||
|
/// )
|
||||||
|
/// ].into_boxed_slice()
|
||||||
|
/// )
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Struct with unnamed fields:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Color(u8, u8, u8);
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::FromValue for Color {
|
||||||
|
/// fn expected_type() -> nu_protocol::Type {
|
||||||
|
/// nu_protocol::Type::Custom(
|
||||||
|
/// std::format!(
|
||||||
|
/// "[{}, {}, {}]",
|
||||||
|
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
||||||
|
/// <u8 as nu_protocol::FromValue>::expected_type(),
|
||||||
|
/// <u8 as nu_protocol::FromValue>::expected_type()
|
||||||
|
/// )
|
||||||
|
/// .into_boxed_str()
|
||||||
|
/// )
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Unit struct:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Unicorn;
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::FromValue for Color {
|
||||||
|
/// fn expected_type() -> nu_protocol::Type {
|
||||||
|
/// nu_protocol::Type::Nothing
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn struct_expected_type(fields: &Fields) -> TokenStream2 {
|
||||||
|
let ty = match fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
let fields = fields.named.iter().map(|field| {
|
||||||
|
let ident = field.ident.as_ref().expect("named has idents");
|
||||||
|
let ident_s = ident.to_string();
|
||||||
|
let ty = &field.ty;
|
||||||
|
quote! {(
|
||||||
|
std::string::ToString::to_string(#ident_s),
|
||||||
|
<#ty as nu_protocol::FromValue>::expected_type(),
|
||||||
|
)}
|
||||||
|
});
|
||||||
|
quote!(nu_protocol::Type::Record(
|
||||||
|
std::vec![#(#fields),*].into_boxed_slice()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
let mut iter = fields.unnamed.iter();
|
||||||
|
let fields = fields.unnamed.iter().map(|field| {
|
||||||
|
let ty = &field.ty;
|
||||||
|
quote!(<#ty as nu_protocol::FromValue>::expected_type())
|
||||||
|
});
|
||||||
|
let mut template = String::new();
|
||||||
|
template.push('[');
|
||||||
|
if iter.next().is_some() {
|
||||||
|
template.push_str("{}")
|
||||||
|
}
|
||||||
|
iter.for_each(|_| template.push_str(", {}"));
|
||||||
|
template.push(']');
|
||||||
|
quote! {
|
||||||
|
nu_protocol::Type::Custom(
|
||||||
|
std::format!(
|
||||||
|
#template,
|
||||||
|
#(#fields),*
|
||||||
|
)
|
||||||
|
.into_boxed_str()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unit => quote!(nu_protocol::Type::Nothing),
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
fn expected_type() -> nu_protocol::Type {
|
||||||
|
#ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the `#[derive(FromValue)]` macro for enums.
|
||||||
|
///
|
||||||
|
/// This function constructs the implementation of the `FromValue` trait for enums.
|
||||||
|
/// It is designed to be on the same level as [`derive_struct_from_value`], even though this
|
||||||
|
/// function only provides the impl signature for `FromValue`.
|
||||||
|
/// The actual implementation for `FromValue::from_value` is handled by [`enum_from_value`].
|
||||||
|
///
|
||||||
|
/// Since variants are difficult to type with the current type system, this function uses the
|
||||||
|
/// default implementation for `expected_type`.
|
||||||
|
fn derive_enum_from_value(
|
||||||
|
ident: Ident,
|
||||||
|
data: DataEnum,
|
||||||
|
generics: Generics,
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
) -> Result<TokenStream2, DeriveError> {
|
||||||
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
let from_value_impl = enum_from_value(&data, &attrs)?;
|
||||||
|
Ok(quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
||||||
|
#from_value_impl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements `FromValue::from_value` for enums.
|
||||||
|
///
|
||||||
|
/// This function constructs the `from_value` implementation for enums.
|
||||||
|
/// It only accepts enums with unit variants, as it is currently unclear how other types of enums
|
||||||
|
/// should be represented via a `Value`.
|
||||||
|
/// This function checks that every field is a unit variant and constructs a match statement over
|
||||||
|
/// all possible variants.
|
||||||
|
/// The input value is expected to be a `Value::String` containing the name of the variant formatted
|
||||||
|
/// as defined by the `#[nu_value(rename_all = "...")]` attribute.
|
||||||
|
/// If no attribute is given, [`convert_case::Case::Snake`] is expected.
|
||||||
|
///
|
||||||
|
/// If no matching variant is found, `ShellError::CantConvert` is returned.
|
||||||
|
///
|
||||||
|
/// This is how such a derived implementation looks:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// enum Weather {
|
||||||
|
/// Sunny,
|
||||||
|
/// Cloudy,
|
||||||
|
/// Raining
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::IntoValue for Weather {
|
||||||
|
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||||
|
/// let span = v.span();
|
||||||
|
/// let ty = v.get_type();
|
||||||
|
///
|
||||||
|
/// let s = v.into_string()?;
|
||||||
|
/// match s.as_str() {
|
||||||
|
/// "sunny" => std::result::Ok(Self::Sunny),
|
||||||
|
/// "cloudy" => std::result::Ok(Self::Cloudy),
|
||||||
|
/// "raining" => std::result::Ok(Self::Raining),
|
||||||
|
/// _ => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||||
|
/// to_type: std::string::ToString::to_string(
|
||||||
|
/// &<Self as nu_protocol::FromValue>::expected_type()
|
||||||
|
/// ),
|
||||||
|
/// from_type: std::string::ToString::to_string(&ty),
|
||||||
|
/// span: span,help: std::option::Option::None,
|
||||||
|
/// }),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn enum_from_value(data: &DataEnum, attrs: &[Attribute]) -> Result<TokenStream2, DeriveError> {
|
||||||
|
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||||
|
let arms: Vec<TokenStream2> = data
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.map(|variant| {
|
||||||
|
attributes::deny(&variant.attrs)?;
|
||||||
|
let ident = &variant.ident;
|
||||||
|
let ident_s = format!("{ident}")
|
||||||
|
.as_str()
|
||||||
|
.to_case(container_attrs.rename_all);
|
||||||
|
match &variant.fields {
|
||||||
|
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
||||||
|
fields_span: fields.span(),
|
||||||
|
}),
|
||||||
|
Fields::Unnamed(fields) => Err(DeriveError::UnsupportedEnums {
|
||||||
|
fields_span: fields.span(),
|
||||||
|
}),
|
||||||
|
Fields::Unit => Ok(quote!(#ident_s => std::result::Result::Ok(Self::#ident))),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
fn from_value(
|
||||||
|
v: nu_protocol::Value
|
||||||
|
) -> std::result::Result<Self, nu_protocol::ShellError> {
|
||||||
|
let span = v.span();
|
||||||
|
let ty = v.get_type();
|
||||||
|
|
||||||
|
let s = v.into_string()?;
|
||||||
|
match s.as_str() {
|
||||||
|
#(#arms,)*
|
||||||
|
_ => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||||
|
to_type: std::string::ToString::to_string(
|
||||||
|
&<Self as nu_protocol::FromValue>::expected_type()
|
||||||
|
),
|
||||||
|
from_type: std::string::ToString::to_string(&ty),
|
||||||
|
span: span,
|
||||||
|
help: std::option::Option::None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a `Value` into self.
|
||||||
|
///
|
||||||
|
/// This function handles the actual parsing of a `Value` into self.
|
||||||
|
/// It takes two parameters: `fields` and `self_ident`.
|
||||||
|
/// The `fields` parameter determines the expected type of `Value`: named fields expect a
|
||||||
|
/// `Value::Record`, unnamed fields expect a `Value::List`, and a unit expects `Value::Nothing`.
|
||||||
|
///
|
||||||
|
/// For named fields, the `fields` parameter indicates which field in the record corresponds to
|
||||||
|
/// which struct field.
|
||||||
|
/// For both named and unnamed fields, it also helps cast the type into a `FromValue` type.
|
||||||
|
/// This approach maintains
|
||||||
|
/// [hygiene](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene).
|
||||||
|
///
|
||||||
|
/// The `self_ident` parameter is used to describe the identifier of the returned value.
|
||||||
|
/// For structs, `Self` is usually sufficient, but for enums, `Self::Variant` may be needed in the
|
||||||
|
/// future.
|
||||||
|
///
|
||||||
|
/// This function is more complex than the equivalent for `IntoValue` due to error handling
|
||||||
|
/// requirements.
|
||||||
|
/// For missing fields, `ShellError::CantFindColumn` is used, and for unit structs,
|
||||||
|
/// `ShellError::CantConvert` is used.
|
||||||
|
/// The implementation avoids local variables for fields to prevent accidental shadowing, ensuring
|
||||||
|
/// that poorly named fields don't cause issues.
|
||||||
|
/// While this style is not typically recommended in handwritten Rust, it is acceptable for code
|
||||||
|
/// generation.
|
||||||
|
fn parse_value_via_fields(fields: &Fields, self_ident: impl ToTokens) -> TokenStream2 {
|
||||||
|
match fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
let fields = fields.named.iter().map(|field| {
|
||||||
|
// TODO: handle missing fields for Options as None
|
||||||
|
let ident = field.ident.as_ref().expect("named has idents");
|
||||||
|
let ident_s = ident.to_string();
|
||||||
|
let ty = &field.ty;
|
||||||
|
quote! {
|
||||||
|
#ident: <#ty as nu_protocol::FromValue>::from_value(
|
||||||
|
record
|
||||||
|
.remove(#ident_s)
|
||||||
|
.ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
col_name: std::string::ToString::to_string(#ident_s),
|
||||||
|
span: std::option::Option::None,
|
||||||
|
src_span: span
|
||||||
|
})?,
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
let span = v.span();
|
||||||
|
let mut record = v.into_record()?;
|
||||||
|
std::result::Result::Ok(#self_ident {#(#fields),*})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
let fields = fields.unnamed.iter().enumerate().map(|(i, field)| {
|
||||||
|
let ty = &field.ty;
|
||||||
|
quote! {{
|
||||||
|
<#ty as nu_protocol::FromValue>::from_value(
|
||||||
|
deque
|
||||||
|
.pop_front()
|
||||||
|
.ok_or_else(|| nu_protocol::ShellError::CantFindColumn {
|
||||||
|
col_name: std::string::ToString::to_string(&#i),
|
||||||
|
span: std::option::Option::None,
|
||||||
|
src_span: span
|
||||||
|
})?,
|
||||||
|
)?
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
quote! {
|
||||||
|
let span = v.span();
|
||||||
|
let list = v.into_list()?;
|
||||||
|
let mut deque: std::collections::VecDeque<_> = std::convert::From::from(list);
|
||||||
|
std::result::Result::Ok(#self_ident(#(#fields),*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unit => quote! {
|
||||||
|
match v {
|
||||||
|
nu_protocol::Value::Nothing {..} => Ok(#self_ident),
|
||||||
|
v => std::result::Result::Err(nu_protocol::ShellError::CantConvert {
|
||||||
|
to_type: std::string::ToString::to_string(&<Self as nu_protocol::FromValue>::expected_type()),
|
||||||
|
from_type: std::string::ToString::to_string(&v.get_type()),
|
||||||
|
span: v.span(),
|
||||||
|
help: std::option::Option::None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
266
crates/nu-derive-value/src/into.rs
Normal file
266
crates/nu-derive-value/src/into.rs
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
use convert_case::Casing;
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{
|
||||||
|
spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident,
|
||||||
|
Index,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::attributes::{self, ContainerAttributes};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IntoValue;
|
||||||
|
type DeriveError = super::error::DeriveError<IntoValue>;
|
||||||
|
|
||||||
|
/// Inner implementation of the `#[derive(IntoValue)]` macro for structs and enums.
|
||||||
|
///
|
||||||
|
/// Uses `proc_macro2::TokenStream` for better testing support, unlike `proc_macro::TokenStream`.
|
||||||
|
///
|
||||||
|
/// This function directs the `IntoValue` trait derivation to the correct implementation based on
|
||||||
|
/// the input type:
|
||||||
|
/// - For structs: [`struct_into_value`]
|
||||||
|
/// - For enums: [`enum_into_value`]
|
||||||
|
/// - Unions are not supported and will return an error.
|
||||||
|
pub fn derive_into_value(input: TokenStream2) -> Result<TokenStream2, DeriveError> {
|
||||||
|
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
|
||||||
|
match input.data {
|
||||||
|
Data::Struct(data_struct) => Ok(struct_into_value(
|
||||||
|
input.ident,
|
||||||
|
data_struct,
|
||||||
|
input.generics,
|
||||||
|
input.attrs,
|
||||||
|
)?),
|
||||||
|
Data::Enum(data_enum) => Ok(enum_into_value(
|
||||||
|
input.ident,
|
||||||
|
data_enum,
|
||||||
|
input.generics,
|
||||||
|
input.attrs,
|
||||||
|
)?),
|
||||||
|
Data::Union(_) => Err(DeriveError::UnsupportedUnions),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the `#[derive(IntoValue)]` macro for structs.
|
||||||
|
///
|
||||||
|
/// Automatically derives the `IntoValue` trait for any struct where each field implements
|
||||||
|
/// `IntoValue`.
|
||||||
|
/// For structs with named fields, the derived implementation creates a `Value::Record` using the
|
||||||
|
/// struct fields as keys.
|
||||||
|
/// Each field value is converted using the `IntoValue::into_value` method.
|
||||||
|
/// For structs with unnamed fields, this generates a `Value::List` with each field in the list.
|
||||||
|
/// For unit structs, this generates `Value::Nothing`, because there is no data.
|
||||||
|
///
|
||||||
|
/// Note: The helper attribute `#[nu_value(...)]` is currently not allowed on structs.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// These examples show what the macro would generate.
|
||||||
|
///
|
||||||
|
/// Struct with named fields:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Pet {
|
||||||
|
/// name: String,
|
||||||
|
/// age: u8,
|
||||||
|
/// favorite_toy: Option<String>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::IntoValue for Pet {
|
||||||
|
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||||
|
/// nu_protocol::Value::record(nu_protocol::record! {
|
||||||
|
/// "name" => nu_protocol::IntoValue::into_value(self.name, span),
|
||||||
|
/// "age" => nu_protocol::IntoValue::into_value(self.age, span),
|
||||||
|
/// "favorite_toy" => nu_protocol::IntoValue::into_value(self.favorite_toy, span),
|
||||||
|
/// }, span)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Struct with unnamed fields:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Color(u8, u8, u8);
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::IntoValue for Color {
|
||||||
|
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||||
|
/// nu_protocol::Value::list(vec![
|
||||||
|
/// nu_protocol::IntoValue::into_value(self.0, span),
|
||||||
|
/// nu_protocol::IntoValue::into_value(self.1, span),
|
||||||
|
/// nu_protocol::IntoValue::into_value(self.2, span),
|
||||||
|
/// ], span)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Unit struct:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// struct Unicorn;
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::IntoValue for Unicorn {
|
||||||
|
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||||
|
/// nu_protocol::Value::nothing(span)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn struct_into_value(
|
||||||
|
ident: Ident,
|
||||||
|
data: DataStruct,
|
||||||
|
generics: Generics,
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
) -> Result<TokenStream2, DeriveError> {
|
||||||
|
attributes::deny(&attrs)?;
|
||||||
|
attributes::deny_fields(&data.fields)?;
|
||||||
|
let record = match &data.fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
let accessor = fields
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.map(|field| field.ident.as_ref().expect("named has idents"))
|
||||||
|
.map(|ident| quote!(self.#ident));
|
||||||
|
fields_return_value(&data.fields, accessor)
|
||||||
|
}
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
let accessor = fields
|
||||||
|
.unnamed
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(n, _)| Index::from(n))
|
||||||
|
.map(|index| quote!(self.#index));
|
||||||
|
fields_return_value(&data.fields, accessor)
|
||||||
|
}
|
||||||
|
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
|
||||||
|
};
|
||||||
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
Ok(quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause {
|
||||||
|
fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||||
|
#record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the `#[derive(IntoValue)]` macro for enums.
|
||||||
|
///
|
||||||
|
/// This function implements the derive macro `IntoValue` for enums.
|
||||||
|
/// Currently, only unit enum variants are supported as it is not clear how other types of enums
|
||||||
|
/// should be represented in a `Value`.
|
||||||
|
/// For simple enums, we represent the enum as a `Value::String`. For other types of variants, we return an error.
|
||||||
|
/// The variant name will be case-converted as described by the `#[nu_value(rename_all = "...")]` helper attribute.
|
||||||
|
/// If no attribute is used, the default is `case_convert::Case::Snake`.
|
||||||
|
/// The implementation matches over all variants, uses the appropriate variant name, and constructs a `Value::String`.
|
||||||
|
///
|
||||||
|
/// This is how such a derived implementation looks:
|
||||||
|
/// ```rust
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// enum Weather {
|
||||||
|
/// Sunny,
|
||||||
|
/// Cloudy,
|
||||||
|
/// Raining
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl nu_protocol::IntoValue for Weather {
|
||||||
|
/// fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||||
|
/// match self {
|
||||||
|
/// Self::Sunny => nu_protocol::Value::string("sunny", span),
|
||||||
|
/// Self::Cloudy => nu_protocol::Value::string("cloudy", span),
|
||||||
|
/// Self::Raining => nu_protocol::Value::string("raining", span),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn enum_into_value(
|
||||||
|
ident: Ident,
|
||||||
|
data: DataEnum,
|
||||||
|
generics: Generics,
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
) -> Result<TokenStream2, DeriveError> {
|
||||||
|
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||||
|
let arms: Vec<TokenStream2> = data
|
||||||
|
.variants
|
||||||
|
.into_iter()
|
||||||
|
.map(|variant| {
|
||||||
|
attributes::deny(&variant.attrs)?;
|
||||||
|
let ident = variant.ident;
|
||||||
|
let ident_s = format!("{ident}")
|
||||||
|
.as_str()
|
||||||
|
.to_case(container_attrs.rename_all);
|
||||||
|
match &variant.fields {
|
||||||
|
// In the future we can implement more complexe enums here.
|
||||||
|
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
|
||||||
|
fields_span: fields.span(),
|
||||||
|
}),
|
||||||
|
Fields::Unnamed(fields) => Err(DeriveError::UnsupportedEnums {
|
||||||
|
fields_span: fields.span(),
|
||||||
|
}),
|
||||||
|
Fields::Unit => {
|
||||||
|
Ok(quote!(Self::#ident => nu_protocol::Value::string(#ident_s, span)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
Ok(quote! {
|
||||||
|
impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause {
|
||||||
|
fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||||
|
match self {
|
||||||
|
#(#arms,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs the final `Value` that the macro generates.
|
||||||
|
///
|
||||||
|
/// This function handles the construction of the final `Value` that the macro generates.
|
||||||
|
/// It is currently only used for structs but may be used for enums in the future.
|
||||||
|
/// The function takes two parameters: the `fields`, which allow iterating over each field of a data
|
||||||
|
/// type, and the `accessor`.
|
||||||
|
/// The fields determine whether we need to generate a `Value::Record`, `Value::List`, or
|
||||||
|
/// `Value::Nothing`.
|
||||||
|
/// For named fields, they are also directly used to generate the record key.
|
||||||
|
///
|
||||||
|
/// The `accessor` parameter generalizes how the data is accessed.
|
||||||
|
/// For named fields, this is usually the name of the fields preceded by `self` in a struct, and
|
||||||
|
/// maybe something else for enums.
|
||||||
|
/// For unnamed fields, this should be an iterator similar to the one with named fields, but
|
||||||
|
/// accessing tuple fields, so we get `self.n`.
|
||||||
|
/// For unit structs, this parameter is ignored.
|
||||||
|
/// By using the accessor like this, we can have the same code for structs and enums with data
|
||||||
|
/// variants in the future.
|
||||||
|
fn fields_return_value(
|
||||||
|
fields: &Fields,
|
||||||
|
accessor: impl Iterator<Item = impl ToTokens>,
|
||||||
|
) -> TokenStream2 {
|
||||||
|
match fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
let items: Vec<TokenStream2> = fields
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.zip(accessor)
|
||||||
|
.map(|(field, accessor)| {
|
||||||
|
let ident = field.ident.as_ref().expect("named has idents");
|
||||||
|
let field = ident.to_string();
|
||||||
|
quote!(#field => nu_protocol::IntoValue::into_value(#accessor, span))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
quote! {
|
||||||
|
nu_protocol::Value::record(nu_protocol::record! {
|
||||||
|
#(#items),*
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
let items =
|
||||||
|
fields.unnamed.iter().zip(accessor).map(
|
||||||
|
|(_, accessor)| quote!(nu_protocol::IntoValue::into_value(#accessor, span)),
|
||||||
|
);
|
||||||
|
quote!(nu_protocol::Value::list(std::vec![#(#items),*], span))
|
||||||
|
}
|
||||||
|
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
|
||||||
|
}
|
||||||
|
}
|
69
crates/nu-derive-value/src/lib.rs
Normal file
69
crates/nu-derive-value/src/lib.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
//! Macro implementations of `#[derive(FromValue, IntoValue)]`.
|
||||||
|
//!
|
||||||
|
//! As this crate is a [`proc_macro`] crate, it is only allowed to export
|
||||||
|
//! [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html).
|
||||||
|
//! Therefore, it only exports [`IntoValue`] and [`FromValue`].
|
||||||
|
//!
|
||||||
|
//! To get documentation for other functions and types used in this crate, run
|
||||||
|
//! `cargo doc -p nu-derive-value --document-private-items`.
|
||||||
|
//!
|
||||||
|
//! This crate uses a lot of
|
||||||
|
//! [`proc_macro2::TokenStream`](https://docs.rs/proc-macro2/1.0.24/proc_macro2/struct.TokenStream.html)
|
||||||
|
//! as `TokenStream2` to allow testing the behavior of the macros directly, including the output
|
||||||
|
//! token stream or if the macro errors as expected.
|
||||||
|
//! The tests for functionality can be found in `nu_protocol::value::test_derive`.
|
||||||
|
//!
|
||||||
|
//! This documentation is often less reference-heavy than typical Rust documentation.
|
||||||
|
//! This is because this crate is a dependency for `nu_protocol`, and linking to it would create a
|
||||||
|
//! cyclic dependency.
|
||||||
|
//! Also all examples in the documentation aren't tested as this crate cannot be compiled as a
|
||||||
|
//! normal library very easily.
|
||||||
|
//! This might change in the future if cargo allows building a proc-macro crate differently for
|
||||||
|
//! `cfg(doctest)` as they are already doing for `cfg(test)`.
|
||||||
|
//!
|
||||||
|
//! The generated code from the derive macros tries to be as
|
||||||
|
//! [hygienic](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene) as possible.
|
||||||
|
//! This ensures that the macro can be called anywhere without requiring specific imports.
|
||||||
|
//! This results in obtuse code, which isn't recommended for manual, handwritten Rust
|
||||||
|
//! but ensures that no other code may influence this generated code or vice versa.
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use proc_macro_error::{proc_macro_error, Diagnostic};
|
||||||
|
|
||||||
|
mod attributes;
|
||||||
|
mod error;
|
||||||
|
mod from;
|
||||||
|
mod into;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
const HELPER_ATTRIBUTE: &str = "nu_value";
|
||||||
|
|
||||||
|
/// Derive macro generating an impl of the trait `IntoValue`.
|
||||||
|
///
|
||||||
|
/// For further information, see the docs on the trait itself.
|
||||||
|
#[proc_macro_derive(IntoValue, attributes(nu_value))]
|
||||||
|
#[proc_macro_error]
|
||||||
|
pub fn derive_into_value(input: TokenStream) -> TokenStream {
|
||||||
|
let input = TokenStream2::from(input);
|
||||||
|
let output = match into::derive_into_value(input) {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(e) => Diagnostic::from(e).abort(),
|
||||||
|
};
|
||||||
|
TokenStream::from(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive macro generating an impl of the trait `FromValue`.
|
||||||
|
///
|
||||||
|
/// For further information, see the docs on the trait itself.
|
||||||
|
#[proc_macro_derive(FromValue, attributes(nu_value))]
|
||||||
|
#[proc_macro_error]
|
||||||
|
pub fn derive_from_value(input: TokenStream) -> TokenStream {
|
||||||
|
let input = TokenStream2::from(input);
|
||||||
|
let output = match from::derive_from_value(input) {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(e) => Diagnostic::from(e).abort(),
|
||||||
|
};
|
||||||
|
TokenStream::from(output)
|
||||||
|
}
|
157
crates/nu-derive-value/src/tests.rs
Normal file
157
crates/nu-derive-value/src/tests.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
// These tests only check that the derive macros throw the relevant errors.
|
||||||
|
// Functionality of the derived types is tested in nu_protocol::value::test_derive.
|
||||||
|
|
||||||
|
use crate::error::DeriveError;
|
||||||
|
use crate::from::derive_from_value;
|
||||||
|
use crate::into::derive_into_value;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsupported_unions() {
|
||||||
|
let input = quote! {
|
||||||
|
#[nu_value]
|
||||||
|
union SomeUnion {
|
||||||
|
f1: u32,
|
||||||
|
f2: f32,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::UnsupportedUnions)),
|
||||||
|
"expected `DeriveError::UnsupportedUnions`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::UnsupportedUnions)),
|
||||||
|
"expected `DeriveError::UnsupportedUnions`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsupported_enums() {
|
||||||
|
let input = quote! {
|
||||||
|
#[nu_value(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
enum ComplexEnum {
|
||||||
|
Unit,
|
||||||
|
Unnamed(u32, f32),
|
||||||
|
Named {
|
||||||
|
u: u32,
|
||||||
|
f: f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::UnsupportedEnums { .. })),
|
||||||
|
"expected `DeriveError::UnsupportedEnums`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::UnsupportedEnums { .. })),
|
||||||
|
"expected `DeriveError::UnsupportedEnums`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unexpected_attribute() {
|
||||||
|
let input = quote! {
|
||||||
|
#[nu_value(what)]
|
||||||
|
enum SimpleEnum {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||||
|
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::UnexpectedAttribute { .. })),
|
||||||
|
"expected `DeriveError::UnexpectedAttribute`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deny_attribute_on_structs() {
|
||||||
|
let input = quote! {
|
||||||
|
#[nu_value]
|
||||||
|
struct SomeStruct;
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||||
|
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||||
|
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deny_attribute_on_fields() {
|
||||||
|
let input = quote! {
|
||||||
|
struct SomeStruct {
|
||||||
|
#[nu_value]
|
||||||
|
field: ()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||||
|
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::InvalidAttributePosition { .. })),
|
||||||
|
"expected `DeriveError::InvalidAttributePosition`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_attribute_value() {
|
||||||
|
let input = quote! {
|
||||||
|
#[nu_value(rename_all = "CrazY-CasE")]
|
||||||
|
enum SimpleEnum {
|
||||||
|
A,
|
||||||
|
B
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_res = derive_from_value(input.clone());
|
||||||
|
assert!(
|
||||||
|
matches!(from_res, Err(DeriveError::InvalidAttributeValue { .. })),
|
||||||
|
"expected `DeriveError::InvalidAttributeValue`, got {:?}",
|
||||||
|
from_res
|
||||||
|
);
|
||||||
|
|
||||||
|
let into_res = derive_into_value(input);
|
||||||
|
assert!(
|
||||||
|
matches!(into_res, Err(DeriveError::InvalidAttributeValue { .. })),
|
||||||
|
"expected `DeriveError::InvalidAttributeValue`, got {:?}",
|
||||||
|
into_res
|
||||||
|
);
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use nu_protocol::{
|
||||||
ast::{Argument, Call, Expr, Expression, RecordItem},
|
ast::{Argument, Call, Expr, Expression, RecordItem},
|
||||||
debugger::WithoutDebug,
|
debugger::WithoutDebug,
|
||||||
engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID},
|
engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID},
|
||||||
record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId,
|
record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, Spanned,
|
||||||
SyntaxShape, Type, Value,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, fmt::Write};
|
use std::{collections::HashMap, fmt::Write};
|
||||||
|
@ -296,6 +296,28 @@ fn get_documentation(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(result) = &example.result {
|
if let Some(result) = &example.result {
|
||||||
|
let mut table_call = Call::new(Span::unknown());
|
||||||
|
if example.example.ends_with("--collapse") {
|
||||||
|
// collapse the result
|
||||||
|
table_call.add_named((
|
||||||
|
Spanned {
|
||||||
|
item: "collapse".to_string(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// expand the result
|
||||||
|
table_call.add_named((
|
||||||
|
Spanned {
|
||||||
|
item: "expand".to_string(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
let table = engine_state
|
let table = engine_state
|
||||||
.find_decl("table".as_bytes(), &[])
|
.find_decl("table".as_bytes(), &[])
|
||||||
.and_then(|decl_id| {
|
.and_then(|decl_id| {
|
||||||
|
@ -304,7 +326,7 @@ fn get_documentation(
|
||||||
.run(
|
.run(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&Call::new(Span::new(0, 0)),
|
&table_call,
|
||||||
PipelineData::Value(result.clone(), None),
|
PipelineData::Value(result.clone(), None),
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
|
@ -16,11 +16,13 @@ bench = false
|
||||||
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
nu-utils = { path = "../nu-utils", version = "0.94.3" }
|
||||||
nu-path = { path = "../nu-path", version = "0.94.3" }
|
nu-path = { path = "../nu-path", version = "0.94.3" }
|
||||||
nu-system = { path = "../nu-system", version = "0.94.3" }
|
nu-system = { path = "../nu-system", version = "0.94.3" }
|
||||||
|
nu-derive-value = { path = "../nu-derive-value", version = "0.94.3" }
|
||||||
|
|
||||||
brotli = { workspace = true, optional = true }
|
brotli = { workspace = true, optional = true }
|
||||||
byte-unit = { version = "5.1", features = [ "serde" ] }
|
byte-unit = { version = "5.1", features = [ "serde" ] }
|
||||||
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
||||||
chrono-humanize = { workspace = true }
|
chrono-humanize = { workspace = true }
|
||||||
|
convert_case = { workspace = true }
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
lru = { workspace = true }
|
lru = { workspace = true }
|
||||||
|
|
|
@ -557,12 +557,12 @@ pub enum ShellError {
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
///
|
///
|
||||||
/// Check the spelling of your column name. Did you forget to rename a column somewhere?
|
/// Check the spelling of your column name. Did you forget to rename a column somewhere?
|
||||||
#[error("Cannot find column")]
|
#[error("Cannot find column '{col_name}'")]
|
||||||
#[diagnostic(code(nu::shell::column_not_found))]
|
#[diagnostic(code(nu::shell::column_not_found))]
|
||||||
CantFindColumn {
|
CantFindColumn {
|
||||||
col_name: String,
|
col_name: String,
|
||||||
#[label = "cannot find column '{col_name}'"]
|
#[label = "cannot find column '{col_name}'"]
|
||||||
span: Span,
|
span: Option<Span>,
|
||||||
#[label = "value originates here"]
|
#[label = "value originates here"]
|
||||||
src_span: Span,
|
src_span: Span,
|
||||||
},
|
},
|
||||||
|
|
|
@ -39,3 +39,5 @@ pub use span::*;
|
||||||
pub use syntax_shape::*;
|
pub use syntax_shape::*;
|
||||||
pub use ty::*;
|
pub use ty::*;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|
||||||
|
pub use nu_derive_value::*;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
196
crates/nu-protocol/src/value/into_value.rs
Normal file
196
crates/nu-protocol/src/value/into_value.rs
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{Record, ShellError, Span, Value};
|
||||||
|
|
||||||
|
/// A trait for converting a value into a [`Value`].
|
||||||
|
///
|
||||||
|
/// This conversion is infallible, for fallible conversions use [`TryIntoValue`].
|
||||||
|
///
|
||||||
|
/// # Derivable
|
||||||
|
/// This trait can be used with `#[derive]`.
|
||||||
|
/// When derived on structs with named fields, the resulting value representation will use
|
||||||
|
/// [`Value::Record`], where each field of the record corresponds to a field of the struct.
|
||||||
|
/// For structs with unnamed fields, the value representation will be [`Value::List`], with all
|
||||||
|
/// fields inserted into a list.
|
||||||
|
/// Unit structs will be represented as [`Value::Nothing`] since they contain no data.
|
||||||
|
///
|
||||||
|
/// Only enums with no fields may derive this trait.
|
||||||
|
/// The resulting value representation will be the name of the variant as a [`Value::String`].
|
||||||
|
/// By default, variant names will be converted to ["snake_case"](convert_case::Case::Snake).
|
||||||
|
/// You can customize the case conversion using `#[nu_value(rename_all = "kebab-case")]` on the enum.
|
||||||
|
/// All deterministic and useful case conversions provided by [`convert_case::Case`] are supported
|
||||||
|
/// by specifying the case name followed by "case".
|
||||||
|
/// Also all values for
|
||||||
|
/// [`#[serde(rename_all = "...")]`](https://serde.rs/container-attrs.html#rename_all) are valid
|
||||||
|
/// here.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use nu_protocol::{IntoValue, Value, Span};
|
||||||
|
/// #[derive(IntoValue)]
|
||||||
|
/// #[nu_value(rename_all = "COBOL-CASE")]
|
||||||
|
/// enum Bird {
|
||||||
|
/// MountainEagle,
|
||||||
|
/// ForestOwl,
|
||||||
|
/// RiverDuck,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// Bird::RiverDuck.into_value(Span::unknown()),
|
||||||
|
/// Value::test_string("RIVER-DUCK")
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub trait IntoValue: Sized {
|
||||||
|
/// Converts the given value to a [`Value`].
|
||||||
|
fn into_value(self, span: Span) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primitive Types
|
||||||
|
|
||||||
|
impl<T, const N: usize> IntoValue for [T; N]
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Vec::from(self).into_value(span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! primitive_into_value {
|
||||||
|
($type:ty, $method:ident) => {
|
||||||
|
primitive_into_value!($type => $type, $method);
|
||||||
|
};
|
||||||
|
|
||||||
|
($type:ty => $as_type:ty, $method:ident) => {
|
||||||
|
impl IntoValue for $type {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::$method(<$as_type>::from(self), span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
primitive_into_value!(bool, bool);
|
||||||
|
primitive_into_value!(char, string);
|
||||||
|
primitive_into_value!(f32 => f64, float);
|
||||||
|
primitive_into_value!(f64, float);
|
||||||
|
primitive_into_value!(i8 => i64, int);
|
||||||
|
primitive_into_value!(i16 => i64, int);
|
||||||
|
primitive_into_value!(i32 => i64, int);
|
||||||
|
primitive_into_value!(i64, int);
|
||||||
|
primitive_into_value!(u8 => i64, int);
|
||||||
|
primitive_into_value!(u16 => i64, int);
|
||||||
|
primitive_into_value!(u32 => i64, int);
|
||||||
|
// u64 and usize may be truncated as Value only supports i64.
|
||||||
|
|
||||||
|
impl IntoValue for isize {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::int(self as i64, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoValue for () {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::nothing(span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tuple_into_value {
|
||||||
|
($($t:ident:$n:tt),+) => {
|
||||||
|
impl<$($t),+> IntoValue for ($($t,)+) where $($t: IntoValue,)+ {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
let vals = vec![$(self.$n.into_value(span)),+];
|
||||||
|
Value::list(vals, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tuples in std are implemented for up to 12 elements, so we do it here too.
|
||||||
|
tuple_into_value!(T0:0);
|
||||||
|
tuple_into_value!(T0:0, T1:1);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10);
|
||||||
|
tuple_into_value!(T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10, T11:11);
|
||||||
|
|
||||||
|
// Other std Types
|
||||||
|
|
||||||
|
impl IntoValue for String {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::string(self, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoValue for Vec<T>
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
Value::list(self.into_iter().map(|v| v.into_value(span)).collect(), span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoValue for Option<T>
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
match self {
|
||||||
|
Some(v) => v.into_value(span),
|
||||||
|
None => Value::nothing(span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> IntoValue for HashMap<String, V>
|
||||||
|
where
|
||||||
|
V: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
let mut record = Record::new();
|
||||||
|
for (k, v) in self.into_iter() {
|
||||||
|
// Using `push` is fine as a hashmaps have unique keys.
|
||||||
|
// To ensure this uniqueness, we only allow hashmaps with strings as
|
||||||
|
// keys and not keys which implement `Into<String>` or `ToString`.
|
||||||
|
record.push(k, v.into_value(span));
|
||||||
|
}
|
||||||
|
Value::record(record, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nu Types
|
||||||
|
|
||||||
|
impl IntoValue for Value {
|
||||||
|
fn into_value(self, span: Span) -> Value {
|
||||||
|
self.with_span(span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use this type for all the `into_value` methods that types implement but return a Result
|
||||||
|
/// A trait for trying to convert a value into a `Value`.
|
||||||
|
///
|
||||||
|
/// Types like streams may fail while collecting the `Value`,
|
||||||
|
/// for these types it is useful to implement a fallible variant.
|
||||||
|
///
|
||||||
|
/// This conversion is fallible, for infallible conversions use [`IntoValue`].
|
||||||
|
/// All types that implement `IntoValue` will automatically implement this trait.
|
||||||
|
pub trait TryIntoValue: Sized {
|
||||||
|
// TODO: instead of ShellError, maybe we could have a IntoValueError that implements Into<ShellError>
|
||||||
|
/// Tries to convert the given value into a `Value`.
|
||||||
|
fn try_into_value(self, span: Span) -> Result<Value, ShellError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TryIntoValue for T
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn try_into_value(self, span: Span) -> Result<Value, ShellError> {
|
||||||
|
Ok(self.into_value(span))
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,10 @@ mod filesize;
|
||||||
mod from;
|
mod from;
|
||||||
mod from_value;
|
mod from_value;
|
||||||
mod glob;
|
mod glob;
|
||||||
|
mod into_value;
|
||||||
mod range;
|
mod range;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_derive;
|
||||||
|
|
||||||
pub mod record;
|
pub mod record;
|
||||||
pub use custom_value::CustomValue;
|
pub use custom_value::CustomValue;
|
||||||
|
@ -12,6 +15,7 @@ pub use duration::*;
|
||||||
pub use filesize::*;
|
pub use filesize::*;
|
||||||
pub use from_value::FromValue;
|
pub use from_value::FromValue;
|
||||||
pub use glob::*;
|
pub use glob::*;
|
||||||
|
pub use into_value::{IntoValue, TryIntoValue};
|
||||||
pub use range::{FloatRange, IntRange, Range};
|
pub use range::{FloatRange, IntRange, Range};
|
||||||
pub use record::Record;
|
pub use record::Record;
|
||||||
|
|
||||||
|
@ -1089,7 +1093,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.clone(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: Some(*origin_span),
|
||||||
src_span: span,
|
src_span: span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1126,7 +1130,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CantFindColumn {
|
Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.clone(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: Some(*origin_span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1136,7 +1140,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
_ => Err(ShellError::CantFindColumn {
|
_ => Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name.clone(),
|
col_name: column_name.clone(),
|
||||||
span: *origin_span,
|
span: Some(*origin_span),
|
||||||
src_span: val_span,
|
src_span: val_span,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
@ -1237,7 +1241,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1262,7 +1266,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1342,7 +1346,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1351,7 +1355,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1364,7 +1368,7 @@ impl Value {
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1373,7 +1377,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1427,7 +1431,7 @@ impl Value {
|
||||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
if record.to_mut().remove(col_name).is_none() && !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1435,7 +1439,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1447,7 +1451,7 @@ impl Value {
|
||||||
if record.to_mut().remove(col_name).is_none() && !optional {
|
if record.to_mut().remove(col_name).is_none() && !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1455,7 +1459,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
v => Err(ShellError::CantFindColumn {
|
v => Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -1504,7 +1508,7 @@ impl Value {
|
||||||
} else if !optional {
|
} else if !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1512,7 +1516,7 @@ impl Value {
|
||||||
v => {
|
v => {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1526,7 +1530,7 @@ impl Value {
|
||||||
} else if !optional {
|
} else if !optional {
|
||||||
return Err(ShellError::CantFindColumn {
|
return Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v_span,
|
src_span: v_span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1534,7 +1538,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
v => Err(ShellError::CantFindColumn {
|
v => Err(ShellError::CantFindColumn {
|
||||||
col_name: col_name.clone(),
|
col_name: col_name.clone(),
|
||||||
span: *span,
|
span: Some(*span),
|
||||||
src_span: v.span(),
|
src_span: v.span(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
386
crates/nu-protocol/src/value/test_derive.rs
Normal file
386
crates/nu-protocol/src/value/test_derive.rs
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
use crate::{record, FromValue, IntoValue, Record, Span, Value};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// Make nu_protocol available in this namespace, consumers of this crate will
|
||||||
|
// have this without such an export.
|
||||||
|
// The derive macro fully qualifies paths to "nu_protocol".
|
||||||
|
use crate as nu_protocol;
|
||||||
|
|
||||||
|
trait IntoTestValue {
|
||||||
|
fn into_test_value(self) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoTestValue for T
|
||||||
|
where
|
||||||
|
T: IntoValue,
|
||||||
|
{
|
||||||
|
fn into_test_value(self) -> Value {
|
||||||
|
self.into_value(Span::test_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct NamedFieldsStruct<T>
|
||||||
|
where
|
||||||
|
T: IntoValue + FromValue,
|
||||||
|
{
|
||||||
|
array: [u16; 4],
|
||||||
|
bool: bool,
|
||||||
|
char: char,
|
||||||
|
f32: f32,
|
||||||
|
f64: f64,
|
||||||
|
i8: i8,
|
||||||
|
i16: i16,
|
||||||
|
i32: i32,
|
||||||
|
i64: i64,
|
||||||
|
isize: isize,
|
||||||
|
u16: u16,
|
||||||
|
u32: u32,
|
||||||
|
unit: (),
|
||||||
|
tuple: (u32, bool),
|
||||||
|
some: Option<u32>,
|
||||||
|
none: Option<u32>,
|
||||||
|
vec: Vec<T>,
|
||||||
|
string: String,
|
||||||
|
hashmap: HashMap<String, u32>,
|
||||||
|
nested: Nestee,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct Nestee {
|
||||||
|
u32: u32,
|
||||||
|
some: Option<u32>,
|
||||||
|
none: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamedFieldsStruct<u32> {
|
||||||
|
fn make() -> Self {
|
||||||
|
Self {
|
||||||
|
array: [1, 2, 3, 4],
|
||||||
|
bool: true,
|
||||||
|
char: 'a',
|
||||||
|
f32: std::f32::consts::PI,
|
||||||
|
f64: std::f64::consts::E,
|
||||||
|
i8: 127,
|
||||||
|
i16: -32768,
|
||||||
|
i32: 2147483647,
|
||||||
|
i64: -9223372036854775808,
|
||||||
|
isize: 2,
|
||||||
|
u16: 65535,
|
||||||
|
u32: 4294967295,
|
||||||
|
unit: (),
|
||||||
|
tuple: (1, true),
|
||||||
|
some: Some(123),
|
||||||
|
none: None,
|
||||||
|
vec: vec![10, 20, 30],
|
||||||
|
string: "string".to_string(),
|
||||||
|
hashmap: HashMap::from_iter([("a".to_string(), 10), ("b".to_string(), 20)]),
|
||||||
|
nested: Nestee {
|
||||||
|
u32: 3,
|
||||||
|
some: Some(42),
|
||||||
|
none: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_record(record! {
|
||||||
|
"array" => Value::test_list(vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_int(2),
|
||||||
|
Value::test_int(3),
|
||||||
|
Value::test_int(4)
|
||||||
|
]),
|
||||||
|
"bool" => Value::test_bool(true),
|
||||||
|
"char" => Value::test_string('a'),
|
||||||
|
"f32" => Value::test_float(std::f32::consts::PI.into()),
|
||||||
|
"f64" => Value::test_float(std::f64::consts::E),
|
||||||
|
"i8" => Value::test_int(127),
|
||||||
|
"i16" => Value::test_int(-32768),
|
||||||
|
"i32" => Value::test_int(2147483647),
|
||||||
|
"i64" => Value::test_int(-9223372036854775808),
|
||||||
|
"isize" => Value::test_int(2),
|
||||||
|
"u16" => Value::test_int(65535),
|
||||||
|
"u32" => Value::test_int(4294967295),
|
||||||
|
"unit" => Value::test_nothing(),
|
||||||
|
"tuple" => Value::test_list(vec![
|
||||||
|
Value::test_int(1),
|
||||||
|
Value::test_bool(true)
|
||||||
|
]),
|
||||||
|
"some" => Value::test_int(123),
|
||||||
|
"none" => Value::test_nothing(),
|
||||||
|
"vec" => Value::test_list(vec![
|
||||||
|
Value::test_int(10),
|
||||||
|
Value::test_int(20),
|
||||||
|
Value::test_int(30)
|
||||||
|
]),
|
||||||
|
"string" => Value::test_string("string"),
|
||||||
|
"hashmap" => Value::test_record(record! {
|
||||||
|
"a" => Value::test_int(10),
|
||||||
|
"b" => Value::test_int(20)
|
||||||
|
}),
|
||||||
|
"nested" => Value::test_record(record! {
|
||||||
|
"u32" => Value::test_int(3),
|
||||||
|
"some" => Value::test_int(42),
|
||||||
|
"none" => Value::test_nothing(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_into_value() {
|
||||||
|
let expected = NamedFieldsStruct::value();
|
||||||
|
let actual = NamedFieldsStruct::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_from_value() {
|
||||||
|
let expected = NamedFieldsStruct::make();
|
||||||
|
let actual = NamedFieldsStruct::from_value(NamedFieldsStruct::value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_roundtrip() {
|
||||||
|
let expected = NamedFieldsStruct::make();
|
||||||
|
let actual =
|
||||||
|
NamedFieldsStruct::from_value(NamedFieldsStruct::make().into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = NamedFieldsStruct::value();
|
||||||
|
let actual = NamedFieldsStruct::<u32>::from_value(NamedFieldsStruct::value())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_missing_value() {
|
||||||
|
let value = Value::test_record(Record::new());
|
||||||
|
let res: Result<NamedFieldsStruct<u32>, _> = NamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_fields_struct_incorrect_type() {
|
||||||
|
// Should work for every type that is not a record.
|
||||||
|
let value = Value::test_nothing();
|
||||||
|
let res: Result<NamedFieldsStruct<u32>, _> = NamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct UnnamedFieldsStruct<T>(u32, String, T)
|
||||||
|
where
|
||||||
|
T: IntoValue + FromValue;
|
||||||
|
|
||||||
|
impl UnnamedFieldsStruct<f64> {
|
||||||
|
fn make() -> Self {
|
||||||
|
UnnamedFieldsStruct(420, "Hello, tuple!".to_string(), 33.33)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_list(vec![
|
||||||
|
Value::test_int(420),
|
||||||
|
Value::test_string("Hello, tuple!"),
|
||||||
|
Value::test_float(33.33),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_into_value() {
|
||||||
|
let expected = UnnamedFieldsStruct::value();
|
||||||
|
let actual = UnnamedFieldsStruct::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_from_value() {
|
||||||
|
let expected = UnnamedFieldsStruct::make();
|
||||||
|
let value = UnnamedFieldsStruct::value();
|
||||||
|
let actual = UnnamedFieldsStruct::from_value(value).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_roundtrip() {
|
||||||
|
let expected = UnnamedFieldsStruct::make();
|
||||||
|
let actual =
|
||||||
|
UnnamedFieldsStruct::from_value(UnnamedFieldsStruct::make().into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = UnnamedFieldsStruct::value();
|
||||||
|
let actual = UnnamedFieldsStruct::<f64>::from_value(UnnamedFieldsStruct::value())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_missing_value() {
|
||||||
|
let value = Value::test_list(vec![]);
|
||||||
|
let res: Result<UnnamedFieldsStruct<f64>, _> = UnnamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unnamed_fields_struct_incorrect_type() {
|
||||||
|
// Should work for every type that is not a record.
|
||||||
|
let value = Value::test_nothing();
|
||||||
|
let res: Result<UnnamedFieldsStruct<f64>, _> = UnnamedFieldsStruct::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
struct UnitStruct;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unit_struct_into_value() {
|
||||||
|
let expected = Value::test_nothing();
|
||||||
|
let actual = UnitStruct.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unit_struct_from_value() {
|
||||||
|
let expected = UnitStruct;
|
||||||
|
let actual = UnitStruct::from_value(Value::test_nothing()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unit_struct_roundtrip() {
|
||||||
|
let expected = UnitStruct;
|
||||||
|
let actual = UnitStruct::from_value(UnitStruct.into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = Value::test_nothing();
|
||||||
|
let actual = UnitStruct::from_value(Value::test_nothing())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
||||||
|
enum Enum {
|
||||||
|
AlphaOne,
|
||||||
|
BetaTwo,
|
||||||
|
CharlieThree,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Enum {
|
||||||
|
fn make() -> [Self; 3] {
|
||||||
|
[Enum::AlphaOne, Enum::BetaTwo, Enum::CharlieThree]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_list(vec![
|
||||||
|
Value::test_string("alpha_one"),
|
||||||
|
Value::test_string("beta_two"),
|
||||||
|
Value::test_string("charlie_three"),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_into_value() {
|
||||||
|
let expected = Enum::value();
|
||||||
|
let actual = Enum::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_from_value() {
|
||||||
|
let expected = Enum::make();
|
||||||
|
let actual = <[Enum; 3]>::from_value(Enum::value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_roundtrip() {
|
||||||
|
let expected = Enum::make();
|
||||||
|
let actual = <[Enum; 3]>::from_value(Enum::make().into_test_value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
|
||||||
|
let expected = Enum::value();
|
||||||
|
let actual = <[Enum; 3]>::from_value(Enum::value())
|
||||||
|
.unwrap()
|
||||||
|
.into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_unknown_variant() {
|
||||||
|
let value = Value::test_string("delta_four");
|
||||||
|
let res = Enum::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_incorrect_type() {
|
||||||
|
// Should work for every type that is not a record.
|
||||||
|
let value = Value::test_nothing();
|
||||||
|
let res = Enum::from_value(value);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the `Enum` from before but with all possible `rename_all` variants.
|
||||||
|
macro_rules! enum_rename_all {
|
||||||
|
($($ident:ident: $case:literal => [$a1:literal, $b2:literal, $c3:literal]),*) => {
|
||||||
|
$(
|
||||||
|
#[derive(Debug, PartialEq, IntoValue, FromValue)]
|
||||||
|
#[nu_value(rename_all = $case)]
|
||||||
|
enum $ident {
|
||||||
|
AlphaOne,
|
||||||
|
BetaTwo,
|
||||||
|
CharlieThree
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $ident {
|
||||||
|
fn make() -> [Self; 3] {
|
||||||
|
[Self::AlphaOne, Self::BetaTwo, Self::CharlieThree]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value() -> Value {
|
||||||
|
Value::test_list(vec![
|
||||||
|
Value::test_string($a1),
|
||||||
|
Value::test_string($b2),
|
||||||
|
Value::test_string($c3),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_rename_all_into_value() {$({
|
||||||
|
let expected = $ident::value();
|
||||||
|
let actual = $ident::make().into_test_value();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
})*}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_rename_all_from_value() {$({
|
||||||
|
let expected = $ident::make();
|
||||||
|
let actual = <[$ident; 3]>::from_value($ident::value()).unwrap();
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
})*}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_rename_all! {
|
||||||
|
Upper: "UPPER CASE" => ["ALPHA ONE", "BETA TWO", "CHARLIE THREE"],
|
||||||
|
Lower: "lower case" => ["alpha one", "beta two", "charlie three"],
|
||||||
|
Title: "Title Case" => ["Alpha One", "Beta Two", "Charlie Three"],
|
||||||
|
Camel: "camelCase" => ["alphaOne", "betaTwo", "charlieThree"],
|
||||||
|
Pascal: "PascalCase" => ["AlphaOne", "BetaTwo", "CharlieThree"],
|
||||||
|
Snake: "snake_case" => ["alpha_one", "beta_two", "charlie_three"],
|
||||||
|
UpperSnake: "UPPER_SNAKE_CASE" => ["ALPHA_ONE", "BETA_TWO", "CHARLIE_THREE"],
|
||||||
|
Kebab: "kebab-case" => ["alpha-one", "beta-two", "charlie-three"],
|
||||||
|
Cobol: "COBOL-CASE" => ["ALPHA-ONE", "BETA-TWO", "CHARLIE-THREE"],
|
||||||
|
Train: "Train-Case" => ["Alpha-One", "Beta-Two", "Charlie-Three"],
|
||||||
|
Flat: "flatcase" => ["alphaone", "betatwo", "charliethree"],
|
||||||
|
UpperFlat: "UPPERFLATCASE" => ["ALPHAONE", "BETATWO", "CHARLIETHREE"]
|
||||||
|
}
|
|
@ -283,6 +283,8 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef<str>, with_std: bool) -> O
|
||||||
if !with_std {
|
if !with_std {
|
||||||
command.arg("--no-std-lib");
|
command.arg("--no-std-lib");
|
||||||
}
|
}
|
||||||
|
// Use plain errors to help make error text matching more consistent
|
||||||
|
command.args(["--error-style", "plain"]);
|
||||||
command
|
command
|
||||||
.arg(format!("-c {}", escape_quote_string(&commands)))
|
.arg(format!("-c {}", escape_quote_string(&commands)))
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
@ -369,6 +371,8 @@ where
|
||||||
.envs(envs)
|
.envs(envs)
|
||||||
.arg("--commands")
|
.arg("--commands")
|
||||||
.arg(command)
|
.arg(command)
|
||||||
|
// Use plain errors to help make error text matching more consistent
|
||||||
|
.args(["--error-style", "plain"])
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
.arg(temp_config_file)
|
.arg(temp_config_file)
|
||||||
.arg("--env-config")
|
.arg("--env-config")
|
||||||
|
|
|
@ -87,7 +87,7 @@ impl CustomValue for CoolCustomValue {
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::CantFindColumn {
|
Err(ShellError::CantFindColumn {
|
||||||
col_name: column_name,
|
col_name: column_name,
|
||||||
span: path_span,
|
span: Some(path_span),
|
||||||
src_span: self_span,
|
src_span: self_span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,4 @@ bench = false
|
||||||
nu-plugin = { path = "../nu-plugin", version = "0.94.3" }
|
nu-plugin = { path = "../nu-plugin", version = "0.94.3" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
nu-protocol = { path = "../nu-protocol", version = "0.94.3" }
|
||||||
|
|
||||||
git2 = "0.18"
|
git2 = "0.19"
|
||||||
|
|
|
@ -29,8 +29,10 @@ pub(crate) fn gather_commandline_args() -> (Vec<String>, String, Vec<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let flag_value = match arg.as_ref() {
|
let flag_value = match arg.as_ref() {
|
||||||
"--commands" | "-c" | "--table-mode" | "-m" | "-e" | "--execute" | "--config"
|
"--commands" | "-c" | "--table-mode" | "-m" | "--error-style" | "-e" | "--execute"
|
||||||
| "--env-config" | "-I" | "ide-ast" => args.next().map(|a| escape_quote_string(&a)),
|
| "--config" | "--env-config" | "-I" | "ide-ast" => {
|
||||||
|
args.next().map(|a| escape_quote_string(&a))
|
||||||
|
}
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
|
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
|
||||||
"--log-level" | "--log-target" | "--log-include" | "--log-exclude" | "--testbin"
|
"--log-level" | "--log-target" | "--log-include" | "--log-exclude" | "--testbin"
|
||||||
|
@ -102,6 +104,8 @@ pub(crate) fn parse_commandline_args(
|
||||||
let execute = call.get_flag_expr("execute");
|
let execute = call.get_flag_expr("execute");
|
||||||
let table_mode: Option<Value> =
|
let table_mode: Option<Value> =
|
||||||
call.get_flag(engine_state, &mut stack, "table-mode")?;
|
call.get_flag(engine_state, &mut stack, "table-mode")?;
|
||||||
|
let error_style: Option<Value> =
|
||||||
|
call.get_flag(engine_state, &mut stack, "error-style")?;
|
||||||
let no_newline = call.get_named_arg("no-newline");
|
let no_newline = call.get_named_arg("no-newline");
|
||||||
|
|
||||||
// ide flags
|
// ide flags
|
||||||
|
@ -245,6 +249,7 @@ pub(crate) fn parse_commandline_args(
|
||||||
ide_check,
|
ide_check,
|
||||||
ide_ast,
|
ide_ast,
|
||||||
table_mode,
|
table_mode,
|
||||||
|
error_style,
|
||||||
no_newline,
|
no_newline,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -278,6 +283,7 @@ pub(crate) struct NushellCliArgs {
|
||||||
pub(crate) log_exclude: Option<Vec<Spanned<String>>>,
|
pub(crate) log_exclude: Option<Vec<Spanned<String>>>,
|
||||||
pub(crate) execute: Option<Spanned<String>>,
|
pub(crate) execute: Option<Spanned<String>>,
|
||||||
pub(crate) table_mode: Option<Value>,
|
pub(crate) table_mode: Option<Value>,
|
||||||
|
pub(crate) error_style: Option<Value>,
|
||||||
pub(crate) no_newline: Option<Spanned<String>>,
|
pub(crate) no_newline: Option<Spanned<String>>,
|
||||||
pub(crate) include_path: Option<Spanned<String>>,
|
pub(crate) include_path: Option<Spanned<String>>,
|
||||||
pub(crate) lsp: bool,
|
pub(crate) lsp: bool,
|
||||||
|
@ -325,6 +331,12 @@ impl Command for Nu {
|
||||||
"the table mode to use. rounded is default.",
|
"the table mode to use. rounded is default.",
|
||||||
Some('m'),
|
Some('m'),
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"error-style",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the error style to use (fancy or plain). default: fancy",
|
||||||
|
None,
|
||||||
|
)
|
||||||
.switch("no-newline", "print the result for --commands(-c) without a newline", None)
|
.switch("no-newline", "print the result for --commands(-c) without a newline", None)
|
||||||
.switch(
|
.switch(
|
||||||
"no-config-file",
|
"no-config-file",
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
use log::trace;
|
use log::trace;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
use nu_cli::read_plugin_file;
|
use nu_cli::read_plugin_file;
|
||||||
use nu_cli::{evaluate_commands, evaluate_file, evaluate_repl};
|
use nu_cli::{evaluate_commands, evaluate_file, evaluate_repl, EvaluateCommandsOpts};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack},
|
engine::{EngineState, Stack},
|
||||||
report_error_new, PipelineData, Spanned,
|
report_error_new, PipelineData, Spanned,
|
||||||
|
@ -114,8 +114,11 @@ pub(crate) fn run_commands(
|
||||||
engine_state,
|
engine_state,
|
||||||
&mut stack,
|
&mut stack,
|
||||||
input,
|
input,
|
||||||
parsed_nu_cli_args.table_mode,
|
EvaluateCommandsOpts {
|
||||||
parsed_nu_cli_args.no_newline.is_some(),
|
table_mode: parsed_nu_cli_args.table_mode,
|
||||||
|
error_style: parsed_nu_cli_args.error_style,
|
||||||
|
no_newline: parsed_nu_cli_args.no_newline.is_some(),
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
report_error_new(engine_state, &err);
|
report_error_new(engine_state, &err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user