mirror of
https://github.com/nushell/nushell.git
synced 2025-02-15 01:52:07 +01:00
Merge remote-tracking branch 'upstream/main' into polars_s3
This commit is contained in:
commit
96df4b9b92
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@ -10,4 +10,4 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.28.2
|
||||
uses: crate-ci/typos@v1.28.4
|
||||
|
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -2872,9 +2872,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||
|
||||
[[package]]
|
||||
name = "is_debug"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89"
|
||||
checksum = "e8ea828c9d6638a5bd3d8b14e37502b4d56cae910ccf8a5b7f51c7a0eb1d0508"
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
@ -4150,6 +4150,7 @@ dependencies = [
|
||||
"nix 0.29.0",
|
||||
"num-format",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strip-ansi-escapes",
|
||||
"sys-locale",
|
||||
"unicase",
|
||||
@ -6776,9 +6777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "0.36.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58cfcd0643497a9f780502063aecbcc4a3212cbe4948fd25ee8fd179c2cf9a18"
|
||||
checksum = "974eb8222c62a8588bc0f02794dd1ba5b60b3ec88b58e050729d0907ed6af610"
|
||||
dependencies = [
|
||||
"const_format",
|
||||
"is_debug",
|
||||
|
@ -21,10 +21,10 @@ nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features =
|
||||
nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
|
||||
|
||||
itertools = { workspace = true }
|
||||
shadow-rs = { version = "0.36", default-features = false }
|
||||
shadow-rs = { version = "0.37", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.36", default-features = false }
|
||||
shadow-rs = { version = "0.37", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["os"]
|
||||
@ -42,4 +42,4 @@ mimalloc = []
|
||||
trash-support = []
|
||||
sqlite = []
|
||||
static-link-openssl = []
|
||||
system-clipboard = []
|
||||
system-clipboard = []
|
||||
|
@ -1,12 +1,13 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() -> shadow_rs::SdResult<()> {
|
||||
fn main() {
|
||||
// Look up the current Git commit ourselves instead of relying on shadow_rs,
|
||||
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
|
||||
let hash = get_git_hash().unwrap_or_default();
|
||||
println!("cargo:rustc-env=NU_COMMIT_HASH={hash}");
|
||||
|
||||
shadow_rs::new()
|
||||
shadow_rs::ShadowBuilder::builder()
|
||||
.build()
|
||||
.expect("shadow builder build should success");
|
||||
}
|
||||
|
||||
fn get_git_hash() -> Option<String> {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
pub struct Arguments {
|
||||
struct Arguments {
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
compact: bool,
|
||||
}
|
||||
@ -142,7 +142,7 @@ fn into_binary(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
|
||||
let value = match input {
|
||||
Value::Binary { .. } => input.clone(),
|
||||
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
|
||||
|
@ -116,7 +116,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
let value_span = input.span();
|
||||
match input {
|
||||
Value::Filesize { .. } => input.clone(),
|
||||
|
@ -10,6 +10,7 @@ mod metadata_set;
|
||||
mod profile;
|
||||
mod timeit;
|
||||
mod view;
|
||||
mod view_blocks;
|
||||
mod view_files;
|
||||
mod view_ir;
|
||||
mod view_source;
|
||||
@ -27,6 +28,7 @@ pub use metadata_set::MetadataSet;
|
||||
pub use profile::DebugProfile;
|
||||
pub use timeit::TimeIt;
|
||||
pub use view::View;
|
||||
pub use view_blocks::ViewBlocks;
|
||||
pub use view_files::ViewFiles;
|
||||
pub use view_ir::ViewIr;
|
||||
pub use view_source::ViewSource;
|
||||
|
71
crates/nu-command/src/debug/view_blocks.rs
Normal file
71
crates/nu-command/src/debug/view_blocks.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ViewBlocks;
|
||||
|
||||
impl Command for ViewBlocks {
|
||||
fn name(&self) -> &str {
|
||||
"view blocks"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"View the blocks registered in nushell's EngineState memory."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
"These are blocks parsed and loaded at runtime as well as any blocks that accumulate in the repl."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("view blocks")
|
||||
.input_output_types(vec![(
|
||||
Type::Nothing,
|
||||
Type::Table(
|
||||
[
|
||||
("block_id".into(), Type::Int),
|
||||
("content".into(), Type::String),
|
||||
("start".into(), Type::Int),
|
||||
("end".into(), Type::Int),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
)])
|
||||
.category(Category::Debug)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let mut records = vec![];
|
||||
|
||||
for block_id in 0..engine_state.num_blocks() {
|
||||
let block = engine_state.get_block(nu_protocol::BlockId::new(block_id));
|
||||
|
||||
if let Some(span) = block.span {
|
||||
let contents_bytes = engine_state.get_span_contents(span);
|
||||
let contents_string = String::from_utf8_lossy(contents_bytes);
|
||||
let cur_rec = record! {
|
||||
"block_id" => Value::int(block_id as i64, span),
|
||||
"content" => Value::string(contents_string.trim().to_string(), span),
|
||||
"start" => Value::int(span.start as i64, span),
|
||||
"end" => Value::int(span.end as i64, span),
|
||||
};
|
||||
records.push(Value::record(cur_rec, span));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::list(records, call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "View the blocks registered in Nushell's EngineState memory",
|
||||
example: r#"view blocks"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
@ -33,6 +33,34 @@ impl Command for ViewSource {
|
||||
let arg_span = arg.span();
|
||||
|
||||
let source = match arg {
|
||||
Value::Int { val, .. } => {
|
||||
if let Some(block) =
|
||||
engine_state.try_get_block(nu_protocol::BlockId::new(val as usize))
|
||||
{
|
||||
if let Some(span) = block.span {
|
||||
let contents = engine_state.get_span_contents(span);
|
||||
Ok(Value::string(String::from_utf8_lossy(contents), call.head)
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Cannot view int value".to_string(),
|
||||
msg: "the block does not have a viewable span".to_string(),
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: format!("Block Id {} does not exist", arg.coerce_into_string()?),
|
||||
msg: "this number does not correspond to a block".to_string(),
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Value::String { val, .. } => {
|
||||
if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
|
||||
// arg is a command
|
||||
@ -130,7 +158,7 @@ impl Command for ViewSource {
|
||||
Ok(Value::string(final_contents, call.head).into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Cannot view value".to_string(),
|
||||
error: "Cannot view string value".to_string(),
|
||||
msg: "the command does not have a viewable block span".to_string(),
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
@ -139,7 +167,7 @@ impl Command for ViewSource {
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Cannot view value".to_string(),
|
||||
error: "Cannot view string decl value".to_string(),
|
||||
msg: "the command does not have a viewable block".to_string(),
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
@ -155,7 +183,7 @@ impl Command for ViewSource {
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Cannot view value".to_string(),
|
||||
error: "Cannot view string module value".to_string(),
|
||||
msg: "the module does not have a viewable block".to_string(),
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
@ -164,7 +192,7 @@ impl Command for ViewSource {
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::GenericError {
|
||||
error: "Cannot view value".to_string(),
|
||||
error: "Cannot view string value".to_string(),
|
||||
msg: "this name does not correspond to a viewable value".to_string(),
|
||||
span: Some(arg_span),
|
||||
help: None,
|
||||
|
@ -61,6 +61,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
SplitBy,
|
||||
Take,
|
||||
Merge,
|
||||
MergeDeep,
|
||||
Move,
|
||||
TakeWhile,
|
||||
TakeUntil,
|
||||
@ -160,6 +161,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
MetadataSet,
|
||||
TimeIt,
|
||||
View,
|
||||
ViewBlocks,
|
||||
ViewFiles,
|
||||
ViewIr,
|
||||
ViewSource,
|
||||
@ -349,6 +351,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
WithEnv,
|
||||
ConfigNu,
|
||||
ConfigEnv,
|
||||
ConfigFlatten,
|
||||
ConfigMeta,
|
||||
ConfigReset,
|
||||
};
|
||||
|
22
crates/nu-command/src/env/config/config_env.rs
vendored
22
crates/nu-command/src/env/config/config_env.rs
vendored
@ -18,8 +18,8 @@ impl Command for ConfigEnv {
|
||||
Some('d'),
|
||||
)
|
||||
.switch(
|
||||
"sample",
|
||||
"Print a commented, sample `env.nu` file instead.",
|
||||
"doc",
|
||||
"Print a commented `env.nu` with documentation instead.",
|
||||
Some('s'),
|
||||
)
|
||||
// TODO: Signature narrower than what run actually supports theoretically
|
||||
@ -37,8 +37,8 @@ impl Command for ConfigEnv {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "pretty-print a commented, sample `env.nu` that explains common settings",
|
||||
example: "config env --sample | nu-highlight,",
|
||||
description: "pretty-print a commented `env.nu` that explains common settings",
|
||||
example: "config env --doc | nu-highlight,",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
@ -57,13 +57,13 @@ impl Command for ConfigEnv {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let default_flag = call.has_flag(engine_state, stack, "default")?;
|
||||
let sample_flag = call.has_flag(engine_state, stack, "sample")?;
|
||||
if default_flag && sample_flag {
|
||||
let doc_flag = call.has_flag(engine_state, stack, "doc")?;
|
||||
if default_flag && doc_flag {
|
||||
return Err(ShellError::IncompatibleParameters {
|
||||
left_message: "can't use `--default` at the same time".into(),
|
||||
left_span: call.get_flag_span(stack, "default").expect("has flag"),
|
||||
right_message: "because of `--sample`".into(),
|
||||
right_span: call.get_flag_span(stack, "sample").expect("has flag"),
|
||||
right_message: "because of `--doc`".into(),
|
||||
right_span: call.get_flag_span(stack, "doc").expect("has flag"),
|
||||
});
|
||||
}
|
||||
// `--default` flag handling
|
||||
@ -72,10 +72,10 @@ impl Command for ConfigEnv {
|
||||
return Ok(Value::string(nu_utils::get_default_env(), head).into_pipeline_data());
|
||||
}
|
||||
|
||||
// `--sample` flag handling
|
||||
if sample_flag {
|
||||
// `--doc` flag handling
|
||||
if doc_flag {
|
||||
let head = call.head;
|
||||
return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data());
|
||||
return Ok(Value::string(nu_utils::get_doc_env(), head).into_pipeline_data());
|
||||
}
|
||||
|
||||
super::config_::start_editor("env-path", engine_state, stack, call)
|
||||
|
195
crates/nu-command/src/env/config/config_flatten.rs
vendored
Normal file
195
crates/nu-command/src/env/config/config_flatten.rs
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_utils::JsonFlattener; // Ensure this import is present // Ensure this import is present
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigFlatten;
|
||||
|
||||
impl Command for ConfigFlatten {
|
||||
fn name(&self) -> &str {
|
||||
"config flatten"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.category(Category::Debug)
|
||||
.input_output_types(vec![(Type::Nothing, Type::record())])
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Show the current configuration in a flattened form."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Show the current configuration in a flattened form",
|
||||
example: "config flatten",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
// Get the Config instance from the EngineState
|
||||
let config = engine_state.get_config();
|
||||
// Serialize the Config instance to JSON
|
||||
let serialized_config =
|
||||
serde_json::to_value(&**config).map_err(|err| ShellError::GenericError {
|
||||
error: format!("Failed to serialize config to json: {err}"),
|
||||
msg: "".into(),
|
||||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
// Create a JsonFlattener instance with appropriate arguments
|
||||
let flattener = JsonFlattener {
|
||||
separator: ".",
|
||||
alt_array_flattening: false,
|
||||
preserve_arrays: true,
|
||||
};
|
||||
// Flatten the JSON value
|
||||
let flattened_config_str = flattener.flatten(&serialized_config).to_string();
|
||||
let flattened_values =
|
||||
convert_string_to_value(&flattened_config_str, engine_state, call.head)?;
|
||||
|
||||
Ok(flattened_values.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
// From here below is taken from `from json`. Would be nice to have a nu-utils-value crate that could be shared
|
||||
fn convert_string_to_value(
|
||||
string_input: &str,
|
||||
engine_state: &EngineState,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
match nu_json::from_str(string_input) {
|
||||
Ok(value) => Ok(convert_nujson_to_value(None, value, engine_state, span)),
|
||||
|
||||
Err(x) => match x {
|
||||
nu_json::Error::Syntax(_, row, col) => {
|
||||
let label = x.to_string();
|
||||
let label_span = convert_row_column_to_span(row, col, string_input);
|
||||
Err(ShellError::GenericError {
|
||||
error: "Error while parsing JSON text".into(),
|
||||
msg: "error parsing JSON text".into(),
|
||||
span: Some(span),
|
||||
help: None,
|
||||
inner: vec![ShellError::OutsideSpannedLabeledError {
|
||||
src: string_input.into(),
|
||||
error: "Error while parsing JSON text".into(),
|
||||
msg: label,
|
||||
span: label_span,
|
||||
}],
|
||||
})
|
||||
}
|
||||
x => Err(ShellError::CantConvert {
|
||||
to_type: format!("structured json data ({x})"),
|
||||
from_type: "string".into(),
|
||||
span,
|
||||
help: None,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_nujson_to_value(
|
||||
key: Option<String>,
|
||||
value: nu_json::Value,
|
||||
engine_state: &EngineState,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
match value {
|
||||
nu_json::Value::Array(array) => Value::list(
|
||||
array
|
||||
.into_iter()
|
||||
.map(|x| convert_nujson_to_value(key.clone(), x, engine_state, span))
|
||||
.collect(),
|
||||
span,
|
||||
),
|
||||
nu_json::Value::Bool(b) => Value::bool(b, span),
|
||||
nu_json::Value::F64(f) => Value::float(f, span),
|
||||
nu_json::Value::I64(i) => {
|
||||
if let Some(closure_str) = expand_closure(key.clone(), i, engine_state) {
|
||||
Value::string(closure_str, span)
|
||||
} else {
|
||||
Value::int(i, span)
|
||||
}
|
||||
}
|
||||
nu_json::Value::Null => Value::nothing(span),
|
||||
nu_json::Value::Object(k) => Value::record(
|
||||
k.into_iter()
|
||||
.map(|(k, v)| {
|
||||
let mut key = k.clone();
|
||||
// Keep .Closure.val and .block_id as part of the key during conversion to value
|
||||
let value = convert_nujson_to_value(Some(key.clone()), v, engine_state, span);
|
||||
// Replace .Closure.val and .block_id from the key after the conversion
|
||||
if key.contains(".Closure.val") || key.contains(".block_id") {
|
||||
key = key.replace(".Closure.val", "").replace(".block_id", "");
|
||||
}
|
||||
(key, value)
|
||||
})
|
||||
.collect(),
|
||||
span,
|
||||
),
|
||||
nu_json::Value::U64(u) => {
|
||||
if u > i64::MAX as u64 {
|
||||
Value::error(
|
||||
ShellError::CantConvert {
|
||||
to_type: "i64 sized integer".into(),
|
||||
from_type: "value larger than i64".into(),
|
||||
span,
|
||||
help: None,
|
||||
},
|
||||
span,
|
||||
)
|
||||
} else if let Some(closure_str) = expand_closure(key.clone(), u as i64, engine_state) {
|
||||
Value::string(closure_str, span)
|
||||
} else {
|
||||
Value::int(u as i64, span)
|
||||
}
|
||||
}
|
||||
nu_json::Value::String(s) => Value::string(s, span),
|
||||
}
|
||||
}
|
||||
|
||||
// If the block_id is a real block id, then it should expand into the closure contents, otherwise return None
|
||||
fn expand_closure(
|
||||
key: Option<String>,
|
||||
block_id: i64,
|
||||
engine_state: &EngineState,
|
||||
) -> Option<String> {
|
||||
match key {
|
||||
Some(key) if key.contains(".Closure.val") || key.contains(".block_id") => engine_state
|
||||
.try_get_block(nu_protocol::BlockId::new(block_id as usize))
|
||||
.and_then(|block| block.span)
|
||||
.map(|span| {
|
||||
let contents = engine_state.get_span_contents(span);
|
||||
String::from_utf8_lossy(contents).to_string()
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts row+column to a Span, assuming bytes (1-based rows)
|
||||
fn convert_row_column_to_span(row: usize, col: usize, contents: &str) -> Span {
|
||||
let mut cur_row = 1;
|
||||
let mut cur_col = 1;
|
||||
|
||||
for (offset, curr_byte) in contents.bytes().enumerate() {
|
||||
if curr_byte == b'\n' {
|
||||
cur_row += 1;
|
||||
cur_col = 1;
|
||||
}
|
||||
if cur_row >= row && cur_col >= col {
|
||||
return Span::new(offset, offset);
|
||||
} else {
|
||||
cur_col += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Span::new(contents.len(), contents.len())
|
||||
}
|
23
crates/nu-command/src/env/config/config_nu.rs
vendored
23
crates/nu-command/src/env/config/config_nu.rs
vendored
@ -18,11 +18,10 @@ impl Command for ConfigNu {
|
||||
Some('d'),
|
||||
)
|
||||
.switch(
|
||||
"sample",
|
||||
"Print a commented, sample `config.nu` file instead.",
|
||||
"doc",
|
||||
"Print a commented `config.nu` with documentation instead.",
|
||||
Some('s'),
|
||||
)
|
||||
// TODO: Signature narrower than what run actually supports theoretically
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
@ -37,8 +36,8 @@ impl Command for ConfigNu {
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "pretty-print a commented, sample `config.nu` that explains common settings",
|
||||
example: "config nu --sample | nu-highlight",
|
||||
description: "pretty-print a commented `config.nu` that explains common settings",
|
||||
example: "config nu --doc | nu-highlight",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
@ -58,13 +57,13 @@ impl Command for ConfigNu {
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let default_flag = call.has_flag(engine_state, stack, "default")?;
|
||||
let sample_flag = call.has_flag(engine_state, stack, "sample")?;
|
||||
if default_flag && sample_flag {
|
||||
let doc_flag = call.has_flag(engine_state, stack, "doc")?;
|
||||
if default_flag && doc_flag {
|
||||
return Err(ShellError::IncompatibleParameters {
|
||||
left_message: "can't use `--default` at the same time".into(),
|
||||
left_span: call.get_flag_span(stack, "default").expect("has flag"),
|
||||
right_message: "because of `--sample`".into(),
|
||||
right_span: call.get_flag_span(stack, "sample").expect("has flag"),
|
||||
right_message: "because of `--doc`".into(),
|
||||
right_span: call.get_flag_span(stack, "doc").expect("has flag"),
|
||||
});
|
||||
}
|
||||
|
||||
@ -74,10 +73,10 @@ impl Command for ConfigNu {
|
||||
return Ok(Value::string(nu_utils::get_default_config(), head).into_pipeline_data());
|
||||
}
|
||||
|
||||
// `--sample` flag handling
|
||||
if sample_flag {
|
||||
// `--doc` flag handling
|
||||
if doc_flag {
|
||||
let head = call.head;
|
||||
return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data());
|
||||
return Ok(Value::string(nu_utils::get_doc_config(), head).into_pipeline_data());
|
||||
}
|
||||
|
||||
super::config_::start_editor("config-path", engine_state, stack, call)
|
||||
|
3
crates/nu-command/src/env/config/mod.rs
vendored
3
crates/nu-command/src/env/config/mod.rs
vendored
@ -1,8 +1,11 @@
|
||||
mod config_;
|
||||
mod config_env;
|
||||
mod config_flatten;
|
||||
mod config_nu;
|
||||
mod config_reset;
|
||||
|
||||
pub use config_::ConfigMeta;
|
||||
pub use config_env::ConfigEnv;
|
||||
pub use config_flatten::ConfigFlatten;
|
||||
pub use config_nu::ConfigNu;
|
||||
pub use config_reset::ConfigReset;
|
||||
|
1
crates/nu-command/src/env/mod.rs
vendored
1
crates/nu-command/src/env/mod.rs
vendored
@ -5,6 +5,7 @@ mod source_env;
|
||||
mod with_env;
|
||||
|
||||
pub use config::ConfigEnv;
|
||||
pub use config::ConfigFlatten;
|
||||
pub use config::ConfigMeta;
|
||||
pub use config::ConfigNu;
|
||||
pub use config::ConfigReset;
|
||||
|
@ -39,11 +39,6 @@ impl Command for Du {
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
|
||||
"Starting directory.",
|
||||
)
|
||||
.switch(
|
||||
"all",
|
||||
"Output file sizes as well as directory sizes",
|
||||
Some('a'),
|
||||
)
|
||||
.switch(
|
||||
"deref",
|
||||
"Dereference symlinks to their targets for size",
|
||||
|
174
crates/nu-command/src/filters/merge/common.rs
Normal file
174
crates/nu-command/src/filters/merge/common.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum MergeStrategy {
|
||||
/// Key-value pairs present in lhs and rhs are overwritten by values in rhs
|
||||
Shallow,
|
||||
/// Records are merged recursively, otherwise same behavior as shallow
|
||||
Deep(ListMerge),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ListMerge {
|
||||
/// Lists in lhs are overwritten by lists in rhs
|
||||
Overwrite,
|
||||
/// Lists of records are merged element-wise, other lists are overwritten by rhs
|
||||
Elementwise,
|
||||
/// All lists are concatenated together, lhs ++ rhs
|
||||
Append,
|
||||
/// All lists are concatenated together, rhs ++ lhs
|
||||
Prepend,
|
||||
}
|
||||
|
||||
/// Test whether a value is a list of records.
|
||||
///
|
||||
/// This includes tables and non-tables.
|
||||
fn is_list_of_records(val: &Value) -> bool {
|
||||
match val {
|
||||
list @ Value::List { .. } if matches!(list.get_type(), Type::Table { .. }) => true,
|
||||
// we want to include lists of records, but not lists of mixed types
|
||||
Value::List { vals, .. } => vals
|
||||
.iter()
|
||||
.map(Value::get_type)
|
||||
.all(|val| matches!(val, Type::Record { .. })),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Typecheck a merge operation.
|
||||
///
|
||||
/// Ensures that both arguments are records, tables, or lists of non-matching records.
|
||||
pub(crate) fn typecheck_merge(lhs: &Value, rhs: &Value, head: Span) -> Result<(), ShellError> {
|
||||
match (lhs.get_type(), rhs.get_type()) {
|
||||
(Type::Record { .. }, Type::Record { .. }) => Ok(()),
|
||||
(_, _) if is_list_of_records(lhs) && is_list_of_records(rhs) => Ok(()),
|
||||
_ => Err(ShellError::PipelineMismatch {
|
||||
exp_input_type: "input and argument to be both record or both table".to_string(),
|
||||
dst_span: head,
|
||||
src_span: lhs.span(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn do_merge(
|
||||
lhs: Value,
|
||||
rhs: Value,
|
||||
strategy: MergeStrategy,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
match (strategy, lhs, rhs) {
|
||||
// Propagate errors
|
||||
(_, Value::Error { error, .. }, _) | (_, _, Value::Error { error, .. }) => Err(*error),
|
||||
// Shallow merge records
|
||||
(
|
||||
MergeStrategy::Shallow,
|
||||
Value::Record { val: lhs, .. },
|
||||
Value::Record { val: rhs, .. },
|
||||
) => Ok(Value::record(
|
||||
merge_records(lhs.into_owned(), rhs.into_owned(), strategy, span)?,
|
||||
span,
|
||||
)),
|
||||
// Deep merge records
|
||||
(
|
||||
MergeStrategy::Deep(_),
|
||||
Value::Record { val: lhs, .. },
|
||||
Value::Record { val: rhs, .. },
|
||||
) => Ok(Value::record(
|
||||
merge_records(lhs.into_owned(), rhs.into_owned(), strategy, span)?,
|
||||
span,
|
||||
)),
|
||||
// Merge lists by appending
|
||||
(
|
||||
MergeStrategy::Deep(ListMerge::Append),
|
||||
Value::List { vals: lhs, .. },
|
||||
Value::List { vals: rhs, .. },
|
||||
) => Ok(Value::list(lhs.into_iter().chain(rhs).collect(), span)),
|
||||
// Merge lists by prepending
|
||||
(
|
||||
MergeStrategy::Deep(ListMerge::Prepend),
|
||||
Value::List { vals: lhs, .. },
|
||||
Value::List { vals: rhs, .. },
|
||||
) => Ok(Value::list(rhs.into_iter().chain(lhs).collect(), span)),
|
||||
// Merge lists of records elementwise (tables and non-tables)
|
||||
// Match on shallow since this might be a top-level table
|
||||
(
|
||||
MergeStrategy::Shallow | MergeStrategy::Deep(ListMerge::Elementwise),
|
||||
lhs_list @ Value::List { .. },
|
||||
rhs_list @ Value::List { .. },
|
||||
) if is_list_of_records(&lhs_list) && is_list_of_records(&rhs_list) => {
|
||||
let lhs = lhs_list
|
||||
.into_list()
|
||||
.expect("Value matched as list above, but is not a list");
|
||||
let rhs = rhs_list
|
||||
.into_list()
|
||||
.expect("Value matched as list above, but is not a list");
|
||||
Ok(Value::list(merge_tables(lhs, rhs, strategy, span)?, span))
|
||||
}
|
||||
// Use rhs value (shallow record merge, overwrite list merge, and general scalar merge)
|
||||
(_, _, val) => Ok(val),
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge right-hand table into left-hand table, element-wise
|
||||
///
|
||||
/// For example:
|
||||
/// lhs = [{a: 12, b: 34}]
|
||||
/// rhs = [{a: 56, c: 78}]
|
||||
/// output = [{a: 56, b: 34, c: 78}]
|
||||
fn merge_tables(
|
||||
lhs: Vec<Value>,
|
||||
rhs: Vec<Value>,
|
||||
strategy: MergeStrategy,
|
||||
span: Span,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let mut table_iter = rhs.into_iter();
|
||||
|
||||
lhs.into_iter()
|
||||
.map(move |inp| match (inp.into_record(), table_iter.next()) {
|
||||
(Ok(rec), Some(to_merge)) => match to_merge.into_record() {
|
||||
Ok(to_merge) => Ok(Value::record(
|
||||
merge_records(rec.to_owned(), to_merge.to_owned(), strategy, span)?,
|
||||
span,
|
||||
)),
|
||||
Err(error) => Ok(Value::error(error, span)),
|
||||
},
|
||||
(Ok(rec), None) => Ok(Value::record(rec, span)),
|
||||
(Err(error), _) => Ok(Value::error(error, span)),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn merge_records(
|
||||
mut lhs: Record,
|
||||
rhs: Record,
|
||||
strategy: MergeStrategy,
|
||||
span: Span,
|
||||
) -> Result<Record, ShellError> {
|
||||
match strategy {
|
||||
MergeStrategy::Shallow => {
|
||||
for (col, rval) in rhs.into_iter() {
|
||||
lhs.insert(col, rval);
|
||||
}
|
||||
}
|
||||
strategy => {
|
||||
for (col, rval) in rhs.into_iter() {
|
||||
// in order to both avoid cloning (possibly nested) record values and maintain the ordering of record keys, we can swap a temporary value into the source record.
|
||||
// if we were to remove the value, the ordering would be messed up as we might not insert back into the original index
|
||||
// it's okay to swap a temporary value in, since we know it will be replaced by the end of the function call
|
||||
//
|
||||
// use an error here instead of something like null so if this somehow makes it into the output, the bug will be immediately obvious
|
||||
let failed_error = ShellError::NushellFailed {
|
||||
msg: "Merge failed to properly replace internal temporary value".to_owned(),
|
||||
};
|
||||
|
||||
let value = match lhs.insert(&col, Value::error(failed_error, span)) {
|
||||
Some(lval) => do_merge(lval, rval, strategy, span)?,
|
||||
None => rval,
|
||||
};
|
||||
|
||||
lhs.insert(col, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
157
crates/nu-command/src/filters/merge/deep.rs
Normal file
157
crates/nu-command/src/filters/merge/deep.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use super::common::{do_merge, typecheck_merge, ListMerge, MergeStrategy};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MergeDeep;
|
||||
|
||||
impl Command for MergeDeep {
|
||||
fn name(&self) -> &str {
|
||||
"merge deep"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Merge the input with a record or table, recursively merging values in matching columns."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"The way that key-value pairs which exist in both the input and the argument are merged depends on their types.
|
||||
|
||||
Scalar values (like numbers and strings) in the input are overwritten by the corresponding value from the argument.
|
||||
Records in the input are merged similarly to the merge command, but recursing rather than overwriting inner records.
|
||||
|
||||
The way lists and tables are merged is controlled by the `--strategy` flag:
|
||||
- table: Merges tables element-wise, similarly to the merge command. Non-table lists are overwritten.
|
||||
- overwrite: Lists and tables are overwritten with their corresponding value from the argument, similarly to scalars.
|
||||
- append: Lists and tables in the input are appended with the corresponding list from the argument.
|
||||
- prepend: Lists and tables in the input are prepended with the corresponding list from the argument."#
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("merge deep")
|
||||
.input_output_types(vec![
|
||||
(Type::record(), Type::record()),
|
||||
(Type::table(), Type::table()),
|
||||
])
|
||||
.required(
|
||||
"value",
|
||||
SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Record(vec![]),
|
||||
SyntaxShape::Table(vec![]),
|
||||
SyntaxShape::List(SyntaxShape::Any.into()),
|
||||
]),
|
||||
"The new value to merge with.",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
.named("strategy", SyntaxShape::String, "The list merging strategy to use. One of: table (default), overwrite, append, prepend", Some('s'))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
example: "{a: 1, b: {c: 2, d: 3}} | merge deep {b: {d: 4, e: 5}}",
|
||||
description: "Merge two records recursively",
|
||||
result: Some(Value::test_record(record! {
|
||||
"a" => Value::test_int(1),
|
||||
"b" => Value::test_record(record! {
|
||||
"c" => Value::test_int(2),
|
||||
"d" => Value::test_int(4),
|
||||
"e" => Value::test_int(5),
|
||||
})
|
||||
})),
|
||||
},
|
||||
Example {
|
||||
example: r#"[{columnA: 0, columnB: [{B1: 1}]}] | merge deep [{columnB: [{B2: 2}]}]"#,
|
||||
description: "Merge two tables",
|
||||
result: Some(Value::test_list(vec![Value::test_record(record! {
|
||||
"columnA" => Value::test_int(0),
|
||||
"columnB" => Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"B1" => Value::test_int(1),
|
||||
"B2" => Value::test_int(2),
|
||||
})
|
||||
]),
|
||||
})])),
|
||||
},
|
||||
Example {
|
||||
example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]}"#,
|
||||
description: "Merge two records and their inner tables",
|
||||
result: Some(Value::test_record(record! {
|
||||
"inner" => Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_int(1),
|
||||
"c" => Value::test_int(3),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"b" => Value::test_int(2),
|
||||
})
|
||||
])
|
||||
})),
|
||||
},
|
||||
Example {
|
||||
example: r#"{inner: [{a: 1}, {b: 2}]} | merge deep {inner: [{c: 3}]} --strategy=append"#,
|
||||
description: "Merge two records, appending their inner tables",
|
||||
result: Some(Value::test_record(record! {
|
||||
"inner" => Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_int(1),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"b" => Value::test_int(2),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"c" => Value::test_int(3),
|
||||
}),
|
||||
])
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
let merge_value: Value = call.req(engine_state, stack, 0)?;
|
||||
let strategy_flag: Option<String> = call.get_flag(engine_state, stack, "strategy")?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
// collect input before typechecking, so tables are detected as such
|
||||
let input_span = input.span().unwrap_or(head);
|
||||
let input = input.into_value(input_span)?;
|
||||
|
||||
let strategy = match strategy_flag.as_deref() {
|
||||
None | Some("table") => MergeStrategy::Deep(ListMerge::Elementwise),
|
||||
Some("append") => MergeStrategy::Deep(ListMerge::Append),
|
||||
Some("prepend") => MergeStrategy::Deep(ListMerge::Prepend),
|
||||
Some("overwrite") => MergeStrategy::Deep(ListMerge::Overwrite),
|
||||
Some(_) => {
|
||||
return Err(ShellError::IncorrectValue {
|
||||
msg: "The list merging strategy must be one one of: table, overwrite, append, prepend".to_string(),
|
||||
val_span: call.get_flag_span(stack, "strategy").unwrap_or(head),
|
||||
call_span: head,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
typecheck_merge(&input, &merge_value, head)?;
|
||||
|
||||
let merged = do_merge(input, merge_value, strategy, head)?;
|
||||
Ok(merged.into_pipeline_data_with_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(MergeDeep {})
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
use super::common::{do_merge, typecheck_merge, MergeStrategy};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -28,8 +29,10 @@ repeating this process with row 1, and so on."#
|
||||
])
|
||||
.required(
|
||||
"value",
|
||||
// Both this and `update` should have a shape more like <record> | <table> than just <any>. -Leon 2022-10-27
|
||||
SyntaxShape::Any,
|
||||
SyntaxShape::OneOf(vec![
|
||||
SyntaxShape::Record(vec![]),
|
||||
SyntaxShape::Table(vec![]),
|
||||
]),
|
||||
"The new value to merge with.",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
@ -89,74 +92,17 @@ repeating this process with row 1, and so on."#
|
||||
let merge_value: Value = call.req(engine_state, stack, 0)?;
|
||||
let metadata = input.metadata();
|
||||
|
||||
match (&input, merge_value) {
|
||||
// table (list of records)
|
||||
(
|
||||
PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. },
|
||||
Value::List { vals, .. },
|
||||
) => {
|
||||
let mut table_iter = vals.into_iter();
|
||||
// collect input before typechecking, so tables are detected as such
|
||||
let input_span = input.span().unwrap_or(head);
|
||||
let input = input.into_value(input_span)?;
|
||||
|
||||
let res =
|
||||
input
|
||||
.into_iter()
|
||||
.map(move |inp| match (inp.as_record(), table_iter.next()) {
|
||||
(Ok(inp), Some(to_merge)) => match to_merge.as_record() {
|
||||
Ok(to_merge) => Value::record(do_merge(inp, to_merge), head),
|
||||
Err(error) => Value::error(error, head),
|
||||
},
|
||||
(_, None) => inp,
|
||||
(Err(error), _) => Value::error(error, head),
|
||||
});
|
||||
typecheck_merge(&input, &merge_value, head)?;
|
||||
|
||||
Ok(res.into_pipeline_data_with_metadata(
|
||||
head,
|
||||
engine_state.signals().clone(),
|
||||
metadata,
|
||||
))
|
||||
}
|
||||
// record
|
||||
(
|
||||
PipelineData::Value(Value::Record { val: inp, .. }, ..),
|
||||
Value::Record { val: to_merge, .. },
|
||||
) => Ok(Value::record(do_merge(inp, &to_merge), head).into_pipeline_data()),
|
||||
// Propagate errors in the pipeline
|
||||
(PipelineData::Value(Value::Error { error, .. }, ..), _) => Err(*error.clone()),
|
||||
(PipelineData::Value(val, ..), ..) => {
|
||||
// Only point the "value originates here" arrow at the merge value
|
||||
// if it was generated from a block. Otherwise, point at the pipeline value. -Leon 2022-10-27
|
||||
let span = if val.span() == Span::test_data() {
|
||||
Span::new(head.start, head.start)
|
||||
} else {
|
||||
val.span()
|
||||
};
|
||||
|
||||
Err(ShellError::PipelineMismatch {
|
||||
exp_input_type: "input, and argument, to be both record or both table"
|
||||
.to_string(),
|
||||
dst_span: head,
|
||||
src_span: span,
|
||||
})
|
||||
}
|
||||
_ => Err(ShellError::PipelineMismatch {
|
||||
exp_input_type: "input, and argument, to be both record or both table".to_string(),
|
||||
dst_span: head,
|
||||
src_span: Span::new(head.start, head.start),
|
||||
}),
|
||||
}
|
||||
let merged = do_merge(input, merge_value, MergeStrategy::Shallow, head)?;
|
||||
Ok(merged.into_pipeline_data_with_metadata(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rewrite to mutate the input record
|
||||
fn do_merge(input_record: &Record, to_merge_record: &Record) -> Record {
|
||||
let mut result = input_record.clone();
|
||||
|
||||
for (col, val) in to_merge_record {
|
||||
result.insert(col, val.clone());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
6
crates/nu-command/src/filters/merge/mod.rs
Normal file
6
crates/nu-command/src/filters/merge/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
mod common;
|
||||
pub mod deep;
|
||||
pub mod merge_;
|
||||
|
||||
pub use deep::MergeDeep;
|
||||
pub use merge_::Merge;
|
@ -87,6 +87,7 @@ pub use last::Last;
|
||||
pub use length::Length;
|
||||
pub use lines::Lines;
|
||||
pub use merge::Merge;
|
||||
pub use merge::MergeDeep;
|
||||
pub use move_::Move;
|
||||
pub use par_each::ParEach;
|
||||
pub use prepend::Prepend;
|
||||
|
@ -14,7 +14,7 @@ impl Command for SplitBy {
|
||||
Signature::build("split-by")
|
||||
.input_output_types(vec![(Type::record(), Type::record())])
|
||||
.optional("splitter", SyntaxShape::Any, "The splitter value to use.")
|
||||
.category(Category::Filters)
|
||||
.category(Category::Deprecated)
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
|
@ -4,7 +4,7 @@ use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::Config;
|
||||
|
||||
pub struct Arguments {
|
||||
struct Arguments {
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
config: Arc<Config>,
|
||||
}
|
||||
|
144
crates/nu-command/tests/commands/merge_deep.rs
Normal file
144
crates/nu-command/tests/commands/merge_deep.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
||||
fn table_strategy_table() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep {} | to nuon",
|
||||
"{inner: [{a: 1}, {b: 2}]}",
|
||||
"{inner: [{c: 3}]}"
|
||||
)
|
||||
.out,
|
||||
"{inner: [{a: 1, c: 3}, {b: 2}]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_strategy_list() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep {} | to nuon",
|
||||
"{a: [1, 2, 3]}",
|
||||
"{a: [4, 5, 6]}"
|
||||
)
|
||||
.out,
|
||||
"{a: [4, 5, 6]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overwrite_strategy_table() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep --strategy=overwrite {} | to nuon",
|
||||
"{inner: [{a: 1}, {b: 2}]}",
|
||||
"{inner: [[c]; [3]]}"
|
||||
)
|
||||
.out,
|
||||
"{inner: [[c]; [3]]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overwrite_strategy_list() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep --strategy=overwrite {} | to nuon",
|
||||
"{a: [1, 2, 3]}",
|
||||
"{a: [4, 5, 6]}"
|
||||
)
|
||||
.out,
|
||||
"{a: [4, 5, 6]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_strategy_table() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep --strategy=append {} | to nuon",
|
||||
"{inner: [{a: 1}, {b: 2}]}",
|
||||
"{inner: [{c: 3}]}"
|
||||
)
|
||||
.out,
|
||||
"{inner: [{a: 1}, {b: 2}, {c: 3}]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_strategy_list() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep --strategy=append {} | to nuon",
|
||||
"{inner: [1, 2, 3]}",
|
||||
"{inner: [4, 5, 6]}"
|
||||
)
|
||||
.out,
|
||||
"{inner: [1, 2, 3, 4, 5, 6]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepend_strategy_table() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep --strategy=prepend {} | to nuon",
|
||||
"{inner: [{a: 1}, {b: 2}]}",
|
||||
"{inner: [{c: 3}]}"
|
||||
)
|
||||
.out,
|
||||
"{inner: [{c: 3}, {a: 1}, {b: 2}]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepend_strategy_list() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep --strategy=prepend {} | to nuon",
|
||||
"{inner: [1, 2, 3]}",
|
||||
"{inner: [4, 5, 6]}"
|
||||
)
|
||||
.out,
|
||||
"{inner: [4, 5, 6, 1, 2, 3]}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_nested_with_overwrite() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep {} | to nuon",
|
||||
"{a: {b: {c: {d: 123, e: 456}}}}",
|
||||
"{a: {b: {c: {e: 654, f: 789}}}}"
|
||||
)
|
||||
.out,
|
||||
"{a: {b: {c: {d: 123, e: 654, f: 789}}}}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_row_table() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep {} | to nuon",
|
||||
"[[a]; [{foo: [1, 2, 3]}]]",
|
||||
"[[a]; [{bar: [4, 5, 6]}]]"
|
||||
)
|
||||
.out,
|
||||
"[[a]; [{foo: [1, 2, 3], bar: [4, 5, 6]}]]"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_row_table() {
|
||||
assert_eq!(
|
||||
nu!(
|
||||
"{} | merge deep {} | to nuon ",
|
||||
"[[a b]; [{inner: {foo: abc}} {inner: {baz: ghi}}]]",
|
||||
"[[a b]; [{inner: {bar: def}} {inner: {qux: jkl}}]]"
|
||||
)
|
||||
.out,
|
||||
"[[a, b]; [{inner: {foo: abc, bar: def}}, {inner: {baz: ghi, qux: jkl}}]]"
|
||||
)
|
||||
}
|
@ -66,6 +66,7 @@ mod ls;
|
||||
mod match_;
|
||||
mod math;
|
||||
mod merge;
|
||||
mod merge_deep;
|
||||
mod mktemp;
|
||||
mod move_;
|
||||
mod mut_;
|
||||
|
@ -145,7 +145,5 @@ fn http_delete_timeout() {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert!(&actual.err.contains("timed out reading response"));
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(&actual
|
||||
.err
|
||||
.contains("did not properly respond after a period of time"));
|
||||
assert!(&actual.err.contains(super::WINDOWS_ERROR_TIMEOUT_SLOW_LINK));
|
||||
}
|
||||
|
@ -339,7 +339,5 @@ fn http_get_timeout() {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert!(&actual.err.contains("timed out reading response"));
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(&actual
|
||||
.err
|
||||
.contains("did not properly respond after a period of time"));
|
||||
assert!(&actual.err.contains(super::WINDOWS_ERROR_TIMEOUT_SLOW_LINK));
|
||||
}
|
||||
|
@ -5,3 +5,14 @@ mod options;
|
||||
mod patch;
|
||||
mod post;
|
||||
mod put;
|
||||
|
||||
/// String representation of the Windows error code for timeouts on slow links.
|
||||
///
|
||||
/// Use this constant in tests instead of matching partial error message content,
|
||||
/// such as `"did not properly respond after a period of time"`, which can vary by language.
|
||||
/// The specific string `"(os error 10060)"` is consistent across all locales, as it represents
|
||||
/// the raw error code rather than localized text.
|
||||
///
|
||||
/// For more details, see the [Microsoft docs](https://learn.microsoft.com/en-us/troubleshoot/windows-client/networking/10060-connection-timed-out-with-proxy-server).
|
||||
#[cfg(all(test, windows))]
|
||||
const WINDOWS_ERROR_TIMEOUT_SLOW_LINK: &str = "(os error 10060)";
|
||||
|
@ -64,7 +64,5 @@ fn http_options_timeout() {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert!(&actual.err.contains("timed out reading response"));
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(&actual
|
||||
.err
|
||||
.contains("did not properly respond after a period of time"));
|
||||
assert!(&actual.err.contains(super::WINDOWS_ERROR_TIMEOUT_SLOW_LINK));
|
||||
}
|
||||
|
@ -189,7 +189,5 @@ fn http_patch_timeout() {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert!(&actual.err.contains("timed out reading response"));
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(&actual
|
||||
.err
|
||||
.contains("did not properly respond after a period of time"));
|
||||
assert!(&actual.err.contains(super::WINDOWS_ERROR_TIMEOUT_SLOW_LINK));
|
||||
}
|
||||
|
@ -303,7 +303,5 @@ fn http_post_timeout() {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert!(&actual.err.contains("timed out reading response"));
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(&actual
|
||||
.err
|
||||
.contains("did not properly respond after a period of time"));
|
||||
assert!(&actual.err.contains(super::WINDOWS_ERROR_TIMEOUT_SLOW_LINK));
|
||||
}
|
||||
|
@ -189,7 +189,5 @@ fn http_put_timeout() {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert!(&actual.err.contains("timed out reading response"));
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(&actual
|
||||
.err
|
||||
.contains("did not properly respond after a period of time"));
|
||||
assert!(&actual.err.contains(super::WINDOWS_ERROR_TIMEOUT_SLOW_LINK));
|
||||
}
|
||||
|
@ -3,12 +3,14 @@ use std/dt [datetime-diff, pretty-print-duration]
|
||||
# Print a banner for nushell with information about the project
|
||||
export def banner [] {
|
||||
let dt = (datetime-diff (date now) 2019-05-10T09:59:12-07:00)
|
||||
let ver = (version)
|
||||
|
||||
let banner_msg = $"(ansi green) __ ,(ansi reset)
|
||||
(ansi green) .--\(\)°'.' (ansi reset)Welcome to (ansi green)Nushell(ansi reset),
|
||||
(ansi green)'|, . ,' (ansi reset)based on the (ansi green)nu(ansi reset) language,
|
||||
(ansi green) !_-\(_\\ (ansi reset)where all data is structured!
|
||||
|
||||
Version: (ansi green)($ver.version) \(($ver.build_os)\)
|
||||
Please join our (ansi purple)Discord(ansi reset) community at (ansi purple)https://discord.gg/NtAbbGn(ansi reset)
|
||||
Our (ansi green_bold)GitHub(ansi reset) repository is at (ansi green_bold)https://github.com/nushell/nushell(ansi reset)
|
||||
Our (ansi green)Documentation(ansi reset) is located at (ansi green)https://nushell.sh(ansi reset)
|
||||
|
@ -3,5 +3,5 @@ use std/assert
|
||||
#[test]
|
||||
def banner [] {
|
||||
use std/core
|
||||
assert ((core banner | lines | length) == 15)
|
||||
assert ((core banner | lines | length) == 16)
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ log = { workspace = true }
|
||||
num-format = { workspace = true }
|
||||
strip-ansi-escapes = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sys-locale = "0.3"
|
||||
unicase = "2.8.0"
|
||||
|
||||
@ -38,4 +39,4 @@ crossterm_winapi = "0.9"
|
||||
nix = { workspace = true, default-features = false, features = ["user", "fs"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
workspace = true
|
||||
|
@ -9,7 +9,7 @@
|
||||
* During a startup where the user specifies an alternative `env.nu` via `nu --env-config <path>`
|
||||
* During a `nu -c <commandstring>` or `nu <script>` startup so that `ENV_CONVERSIONS` is properly handled for Windows.
|
||||
* Is *not* loaded when running with an explicit `no --no-config-file (-n)`.
|
||||
* Is not commented - Comments are in `sample_env.nu`.
|
||||
* Is not commented - Comments are in `doc_env.nu`.
|
||||
* Should be optimized for fastest load times.
|
||||
* Can be introspected via `config env --default | nu-highlight`
|
||||
|
||||
@ -27,7 +27,7 @@ Counterpart to `default_env.nu`.
|
||||
* `nu -n/--no-config`
|
||||
* `nu -c "ls"`
|
||||
* `nu <script.nu>`
|
||||
* Is not commented - Comments are in `sample_config.nu`.
|
||||
* Is not commented - Comments are in `doc_config.nu`.
|
||||
* Should be optimized for fastest load times. Whenever possible, values should be set via nu-protocol::config
|
||||
* Exception: `color_config` values are currently set in this file so that user's can introspect the values
|
||||
* TODO: Implement defaults for `color_config` in nu-protocol::config and remove from `default_config.nu`
|
||||
@ -37,24 +37,24 @@ Counterpart to `default_env.nu`.
|
||||
$env.config = {}
|
||||
```
|
||||
|
||||
## `sample_env.nu`
|
||||
## `doc_env.nu`
|
||||
|
||||
* A commented file documenting the most common environment variables that a user might configure in `env.nu`
|
||||
* For convenient in-shell access - Can be pretty-printed via `config env --sample | nu-highlight`
|
||||
* For convenient in-shell access - Can be pretty-printed via `config env --doc | nu-highlight`
|
||||
* Since this file is for documentation only, include actual Nushell code without comments so that it can be pretty-printed
|
||||
* No optimization necessary - Not intended for use other than documentation.
|
||||
* Consider replacing `config env --sample` with `help env.nu` at some point.
|
||||
* Consider replacing `config env --doc` with `help env.nu` at some point.
|
||||
* Uses a mix of default values (explained) as well as other examples that users might want in their own `env.nu`
|
||||
|
||||
## `sample_config.nu`
|
||||
## `doc_config.nu`
|
||||
|
||||
Counterpart to `sample_env.nu`.
|
||||
Counterpart to `doc_env.nu`.
|
||||
|
||||
* A commented file documenting the most common environment variables that a user might configure in `config.nu`
|
||||
* For convenient in-shell access - Can be pretty-printed via `config nu --sample | nu-highlight`
|
||||
* For convenient in-shell access - Can be pretty-printed via `config nu --doc | nu-highlight`
|
||||
* Since this file is for documentation only, include actual Nushell code without comments so that it can be pretty-printed
|
||||
* No optimization necessary - Not intended for use other than documentation.
|
||||
* Consider replacing `config nu --sample` with `help config.nu` at some point.
|
||||
* Consider replacing `config nu --doc` with `help config.nu` at some point.
|
||||
* Uses a mix of default values (explained) as well as other examples that users might want in their own `config.nu`
|
||||
|
||||
## `scaffold_env.nu`
|
||||
@ -70,7 +70,3 @@ Counterpart to `scaffold_env.nu`.
|
||||
* This file is used *one-time* (typically) at **first** startup
|
||||
* If the `$nu.default-config-path` directory does not exist, the directory is created and then both `scaffold_env.nu` and `scaffold_config.nu` are written to it
|
||||
* Contains only commented lines explaining the purpose of the file to the user, along with information on the `config nu` command.
|
||||
|
||||
## `sample_login.nu`
|
||||
|
||||
This file is not used by any Nushell code. Of course, if the user has a `login.nu`, then it will be evaluated during startup of a login shell.
|
@ -60,4 +60,9 @@ $env.config.color_config = {
|
||||
shape_variable: purple
|
||||
shape_vardecl: purple
|
||||
shape_raw_string: light_purple
|
||||
shape_garbage: {
|
||||
fg: white
|
||||
bg: red
|
||||
attr: b
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Nushell Sample Config File
|
||||
# Nushell Config File Documentation
|
||||
#
|
||||
# Warning: This file is intended for documentation purposes only and
|
||||
# is not intended to be used as an actual configuration file as-is.
|
||||
@ -18,7 +18,7 @@
|
||||
# https://nushell.sh/book/configuration
|
||||
#
|
||||
# You can pretty-print and page this file using:
|
||||
# config nu --sample | nu-highlight | less -R
|
||||
# config nu --doc | nu-highlight | less -R
|
||||
|
||||
# $env.config
|
||||
# -----------
|
||||
@ -335,7 +335,7 @@ $env.config.table.header_on_separator = false
|
||||
# If set to an int, all tables will be abbreviated to only show the first <n> and last <n> rows
|
||||
# If set to `null`, all table rows will be displayed
|
||||
# Can be overridden by passing a table to `| table --abbreviated/-a`
|
||||
$env.config.table.abbreviated_row_count
|
||||
$env.config.table.abbreviated_row_count = null
|
||||
|
||||
# footer_inheritance (bool): Footer behavior in nested tables
|
||||
# true: If a nested table is long enough on its own to display a footer (per `footer_mode` above),
|
||||
@ -411,7 +411,7 @@ $env.config.hooks.pre_prompt = []
|
||||
$env.config.hooks.pre_execution = []
|
||||
# When a specified environment variable changes
|
||||
$env.config.hooks.env_change = {
|
||||
# run if the PWD environment is different since the last repl input
|
||||
# Example: Run if the PWD environment is different since the last REPL input
|
||||
PWD: [{|before, after| null }]
|
||||
}
|
||||
# Before Nushell output is displayed in the terminal
|
||||
@ -419,6 +419,10 @@ $env.config.hooks.display_output = "if (term size).columns >= 100 { table -e } e
|
||||
# When a command is not found
|
||||
$env.config.hooks.command_not_found = []
|
||||
|
||||
# The env_change hook accepts a record with environment variable names as keys, and a list
|
||||
# of hooks to run when that variable changes
|
||||
$env.config.hooks.env_change = {}
|
||||
|
||||
# -----------
|
||||
# Keybindings
|
||||
# -----------
|
||||
@ -472,7 +476,9 @@ $env.config.menus ++= [
|
||||
type: {
|
||||
layout: description
|
||||
columns: 4
|
||||
col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
|
||||
# col_width is an optional value. If missing, the entire screen width is used to
|
||||
# calculate the column width
|
||||
col_width: 20
|
||||
col_padding: 2
|
||||
selection_rows: 4
|
||||
description_rows: 10
|
||||
@ -485,29 +491,26 @@ $env.config.menus ++= [
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# ---------------
|
||||
# Plugin behavior
|
||||
# ---------------
|
||||
# Per-plugin configuration. See https://www.nushell.sh/contributor-book/plugins.html#configuration.
|
||||
$env.config.plugins
|
||||
# Per-plugin configuration. See https://www.nushell.sh/contributor-book/plugins.html#plugin-configuration
|
||||
$env.config.plugins = {}
|
||||
|
||||
# Configuration for plugin garbage collection
|
||||
$env.config.plugin_gc
|
||||
$env.config.plugin_gc.default
|
||||
# true to enable stopping of inactive plugins
|
||||
$env.config.plugin_gc.default.enabled
|
||||
# How long to wait after a plugin is inactive before stopping it
|
||||
$env.config.plugin_gc.default.stop_after
|
||||
# Plugin garbage collection configuration
|
||||
# $env.config.plugin_gc.*
|
||||
|
||||
# enabled (bool): true/false to enable/disable stopping inactive plugins
|
||||
$env.config.plugin_gc.default.enabled = true
|
||||
# stop_after (duration): How long to wait after a plugin is inactive before stopping it
|
||||
$env.config.plugin_gc.default.stop_after = 10sec
|
||||
# plugins (record): Alternate garbage collection configuration per-plugin.
|
||||
$env.config.plugin_gc.plugins = {
|
||||
# Alternate configuration for specific plugins, by name, for example:
|
||||
#
|
||||
# gstat: {
|
||||
# enabled: false
|
||||
# enabled: false
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
# -------------------------------------
|
||||
# Themes/Colors and Syntax Highlighting
|
||||
# -------------------------------------
|
@ -1,4 +1,4 @@
|
||||
# Sample Nushell Environment Config File
|
||||
# Nushell Environment Config File Documentation
|
||||
#
|
||||
# version = "0.100.1"
|
||||
#
|
||||
@ -8,4 +8,4 @@
|
||||
|
||||
# To pretty-print the in-shell documentation for Nushell's various configuration
|
||||
# settings, you can run:
|
||||
config nu --sample | nu-highlight | less -R
|
||||
config nu --doc | nu-highlight | less -R
|
@ -1,12 +0,0 @@
|
||||
# Example Nushell Loginshell Config File
|
||||
#
|
||||
# version = "0.100.1"
|
||||
#
|
||||
# - has to be as login.nu in the default config directory
|
||||
# - will be sourced after config.nu and env.nu in case of nushell started as login shell
|
||||
|
||||
# just as an example for overwriting of an environment variable of env.nu
|
||||
$env.PROMPT_INDICATOR = {|| "(LS)> " }
|
||||
|
||||
# Similar to env-path and config-path there is a variable containing the path to login.nu
|
||||
echo $nu.loginshell-path
|
@ -12,11 +12,7 @@
|
||||
# You can open this file in your default editor using:
|
||||
# config nu
|
||||
#
|
||||
# To pretty-print a sample config.nu with documentation, run:
|
||||
# config nu --sample | nu-highlight | less -R
|
||||
#
|
||||
# To pretty-print the default configuration values, run:
|
||||
# config nu --default | nu-highlight | less -R
|
||||
# See `help config nu` for more options
|
||||
#
|
||||
# You can remove these comments if you want or leave
|
||||
# them for future reference.
|
||||
|
@ -12,11 +12,7 @@
|
||||
#
|
||||
# See https://www.nushell.sh/book/configuration.html
|
||||
#
|
||||
# To pretty-print a sample of the configuration settings, run:
|
||||
# config nu --sample | nu-highlight | less -R
|
||||
#
|
||||
# To pretty-print the default env.nu, run:
|
||||
# config env --default | nu-highlight | less -R
|
||||
# Also see `help config env` for more options.
|
||||
#
|
||||
# You can remove these comments if you want or leave
|
||||
# them for future reference.
|
||||
|
259
crates/nu-utils/src/flatten_json.rs
Normal file
259
crates/nu-utils/src/flatten_json.rs
Normal file
@ -0,0 +1,259 @@
|
||||
use serde_json::{json, Map, Value as SerdeValue};
|
||||
|
||||
/// JsonFlattener is the main driver when flattening JSON
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener { ..Default::default() };
|
||||
/// ```
|
||||
pub struct JsonFlattener<'a> {
|
||||
/// Alternate separator used between keys when flattening
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// let flattener = nu_utils::JsonFlattener { separator: "_", ..Default::default()};
|
||||
/// ```
|
||||
pub separator: &'a str,
|
||||
/// Opinionated flattening format that places values in an array if the object is nested inside an array
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// let flattener = nu_utils::JsonFlattener { alt_array_flattening: true, ..Default::default()};
|
||||
/// ```
|
||||
pub alt_array_flattening: bool,
|
||||
/// Completely flatten JSON and keep array structure in the key when flattening
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// let flattener = nu_utils::JsonFlattener { preserve_arrays: true, ..Default::default()};
|
||||
/// ```
|
||||
pub preserve_arrays: bool,
|
||||
}
|
||||
|
||||
impl<'a> Default for JsonFlattener<'a> {
|
||||
fn default() -> Self {
|
||||
JsonFlattener {
|
||||
separator: ".",
|
||||
alt_array_flattening: false,
|
||||
preserve_arrays: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This implementation defines the core usage for the `JsonFlattener` structure.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// use serde_json::json;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener::new();
|
||||
/// let example = json!({
|
||||
/// "a": {
|
||||
/// "b": "c"
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// let flattened_example = flattener.flatten(&example);
|
||||
/// ```
|
||||
impl<'a> JsonFlattener<'a> {
|
||||
/// Returns a flattener with the default arguments
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
JsonFlattener {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Flattens JSON variants into a JSON object
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `json` - A serde_json Value to flatten
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use nu_utils;
|
||||
/// use serde_json::json;
|
||||
///
|
||||
/// let flattener = nu_utils::JsonFlattener::new();
|
||||
/// let example = json!({
|
||||
/// "name": "John Doe",
|
||||
/// "age": 43,
|
||||
/// "address": {
|
||||
/// "street": "10 Downing Street",
|
||||
/// "city": "London"
|
||||
/// },
|
||||
/// "phones": [
|
||||
/// "+44 1234567",
|
||||
/// "+44 2345678"
|
||||
/// ]
|
||||
/// });
|
||||
///
|
||||
/// let flattened_example = flattener.flatten(&example);
|
||||
/// ```
|
||||
pub fn flatten(&self, json: &SerdeValue) -> SerdeValue {
|
||||
let mut flattened_val = Map::<String, SerdeValue>::new();
|
||||
match json {
|
||||
SerdeValue::Array(obj_arr) => {
|
||||
self.flatten_array(&mut flattened_val, &"".to_string(), obj_arr)
|
||||
}
|
||||
SerdeValue::Object(obj_val) => {
|
||||
self.flatten_object(&mut flattened_val, None, obj_val, false)
|
||||
}
|
||||
_ => self.flatten_value(&mut flattened_val, &"".to_string(), json, false),
|
||||
}
|
||||
SerdeValue::Object(flattened_val)
|
||||
}
|
||||
|
||||
fn flatten_object(
|
||||
&self,
|
||||
builder: &mut Map<String, SerdeValue>,
|
||||
identifier: Option<&String>,
|
||||
obj: &Map<String, SerdeValue>,
|
||||
arr: bool,
|
||||
) {
|
||||
for (k, v) in obj {
|
||||
let expanded_identifier = identifier.map_or_else(
|
||||
|| k.clone(),
|
||||
|identifier| format!("{identifier}{}{k}", self.separator),
|
||||
);
|
||||
|
||||
if expanded_identifier.contains("span.start")
|
||||
|| expanded_identifier.contains("span.end")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let expanded_identifier = self.filter_known_keys(&expanded_identifier);
|
||||
|
||||
match v {
|
||||
SerdeValue::Object(obj_val) => {
|
||||
self.flatten_object(builder, Some(&expanded_identifier), obj_val, arr)
|
||||
}
|
||||
SerdeValue::Array(obj_arr) => {
|
||||
self.flatten_array(builder, &expanded_identifier, obj_arr)
|
||||
}
|
||||
_ => self.flatten_value(builder, &expanded_identifier, v, arr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_array(
|
||||
&self,
|
||||
builder: &mut Map<String, SerdeValue>,
|
||||
identifier: &String,
|
||||
obj: &[SerdeValue],
|
||||
) {
|
||||
for (k, v) in obj.iter().enumerate() {
|
||||
let with_key = format!("{identifier}{}{k}", self.separator);
|
||||
if with_key.contains("span.start") || with_key.contains("span.end") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let with_key = self.filter_known_keys(&with_key);
|
||||
|
||||
match v {
|
||||
SerdeValue::Object(obj_val) => self.flatten_object(
|
||||
builder,
|
||||
Some(if self.preserve_arrays {
|
||||
&with_key
|
||||
} else {
|
||||
identifier
|
||||
}),
|
||||
obj_val,
|
||||
self.alt_array_flattening,
|
||||
),
|
||||
SerdeValue::Array(obj_arr) => self.flatten_array(
|
||||
builder,
|
||||
if self.preserve_arrays {
|
||||
&with_key
|
||||
} else {
|
||||
identifier
|
||||
},
|
||||
obj_arr,
|
||||
),
|
||||
_ => self.flatten_value(
|
||||
builder,
|
||||
if self.preserve_arrays {
|
||||
&with_key
|
||||
} else {
|
||||
identifier
|
||||
},
|
||||
v,
|
||||
self.alt_array_flattening,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_value(
|
||||
&self,
|
||||
builder: &mut Map<String, SerdeValue>,
|
||||
identifier: &String,
|
||||
obj: &SerdeValue,
|
||||
arr: bool,
|
||||
) {
|
||||
if let Some(v) = builder.get_mut(identifier) {
|
||||
if let Some(arr) = v.as_array_mut() {
|
||||
arr.push(obj.clone());
|
||||
} else {
|
||||
let new_val = json!(vec![v, obj]);
|
||||
builder.remove(identifier);
|
||||
builder.insert(identifier.to_string(), new_val);
|
||||
}
|
||||
} else {
|
||||
builder.insert(
|
||||
identifier.to_string(),
|
||||
if arr {
|
||||
json!(vec![obj.clone()])
|
||||
} else {
|
||||
obj.clone()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_known_keys(&self, key: &str) -> String {
|
||||
let mut filtered_key = key.to_string();
|
||||
if filtered_key.contains(".String.val") {
|
||||
filtered_key = filtered_key.replace(".String.val", "");
|
||||
}
|
||||
if filtered_key.contains(".Record.val") {
|
||||
filtered_key = filtered_key.replace(".Record.val", "");
|
||||
}
|
||||
if filtered_key.contains(".List.vals") {
|
||||
filtered_key = filtered_key.replace(".List.vals", "");
|
||||
}
|
||||
if filtered_key.contains(".Int.val") {
|
||||
filtered_key = filtered_key.replace(".Int.val", "");
|
||||
}
|
||||
if filtered_key.contains(".Bool.val") {
|
||||
filtered_key = filtered_key.replace(".Bool.val", "");
|
||||
}
|
||||
if filtered_key.contains(".Truncate.suffix") {
|
||||
filtered_key = filtered_key.replace(".Truncate.suffix", ".truncating_suffix");
|
||||
}
|
||||
if filtered_key.contains(".RowCount") {
|
||||
filtered_key = filtered_key.replace(".RowCount", "");
|
||||
}
|
||||
if filtered_key.contains(".Wrap.try_to_keep_words") {
|
||||
filtered_key =
|
||||
filtered_key.replace(".Wrap.try_to_keep_words", ".wrapping_try_keep_words");
|
||||
}
|
||||
// For now, let's skip replacing these because they tell us which
|
||||
// numbers are closures and blocks which is useful for extracting the content
|
||||
// if filtered_key.contains(".Closure.val") {
|
||||
// filtered_key = filtered_key.replace(".Closure.val", "");
|
||||
// }
|
||||
// if filtered_key.contains(".block_id") {
|
||||
// filtered_key = filtered_key.replace(".block_id", "");
|
||||
// }
|
||||
filtered_key
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ mod casing;
|
||||
mod deansi;
|
||||
pub mod emoji;
|
||||
pub mod filesystem;
|
||||
pub mod flatten_json;
|
||||
pub mod locale;
|
||||
mod quoting;
|
||||
mod shared_cow;
|
||||
@ -10,8 +11,8 @@ pub mod utils;
|
||||
|
||||
pub use locale::get_system_locale;
|
||||
pub use utils::{
|
||||
enable_vt_processing, get_default_config, get_default_env, get_ls_colors, get_sample_config,
|
||||
get_sample_env, get_scaffold_config, get_scaffold_env, stderr_write_all_and_flush,
|
||||
enable_vt_processing, get_default_config, get_default_env, get_doc_config, get_doc_env,
|
||||
get_ls_colors, get_scaffold_config, get_scaffold_env, stderr_write_all_and_flush,
|
||||
stdout_write_all_and_flush, terminal_size,
|
||||
};
|
||||
|
||||
@ -20,6 +21,7 @@ pub use deansi::{
|
||||
strip_ansi_likely, strip_ansi_string_likely, strip_ansi_string_unlikely, strip_ansi_unlikely,
|
||||
};
|
||||
pub use emoji::contains_emoji;
|
||||
pub use flatten_json::JsonFlattener;
|
||||
pub use quoting::{escape_quote_string, needs_quoting};
|
||||
pub use shared_cow::SharedCow;
|
||||
|
||||
|
@ -94,8 +94,8 @@ pub fn get_scaffold_env() -> &'static str {
|
||||
include_str!("default_files/scaffold_env.nu")
|
||||
}
|
||||
|
||||
pub fn get_sample_env() -> &'static str {
|
||||
include_str!("default_files/sample_env.nu")
|
||||
pub fn get_doc_env() -> &'static str {
|
||||
include_str!("default_files/doc_env.nu")
|
||||
}
|
||||
|
||||
pub fn get_default_config() -> &'static str {
|
||||
@ -106,8 +106,8 @@ pub fn get_scaffold_config() -> &'static str {
|
||||
include_str!("default_files/scaffold_config.nu")
|
||||
}
|
||||
|
||||
pub fn get_sample_config() -> &'static str {
|
||||
include_str!("default_files/sample_config.nu")
|
||||
pub fn get_doc_config() -> &'static str {
|
||||
include_str!("default_files/doc_config.nu")
|
||||
}
|
||||
|
||||
pub fn get_ls_colors(lscolors_env_string: Option<String>) -> LsColors {
|
||||
|
@ -30,9 +30,13 @@ impl PluginCommand for ProfileDF {
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Profile a lazy dataframe. This will run the query and return a record containing the materialized DataFrame and a DataFrame that contains profiling information of each node that is executed.
|
||||
"Profile a lazy dataframe."
|
||||
}
|
||||
|
||||
The units of the timings are microseconds."
|
||||
fn extra_description(&self) -> &str {
|
||||
r#"This will run the query and return a record containing the materialized DataFrame and a DataFrame that contains profiling information of each node that is executed.
|
||||
|
||||
The units of the timings are microseconds."#
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -8,7 +8,10 @@ use nu_protocol::{
|
||||
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
use polars::prelude::{Expr, JoinType};
|
||||
use polars::{
|
||||
df,
|
||||
prelude::{Expr, JoinCoalesce, JoinType},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LazyJoin;
|
||||
@ -37,6 +40,7 @@ impl PluginCommand for LazyJoin {
|
||||
.switch("left", "left join between lazyframes", Some('l'))
|
||||
.switch("full", "full join between lazyframes", Some('f'))
|
||||
.switch("cross", "cross join between lazyframes", Some('c'))
|
||||
.switch("coalesce-columns", "Sets the join coalesce strategy to colesce columns. Most useful when used with --full, which will not otherwise coalesce.", None)
|
||||
.named(
|
||||
"suffix",
|
||||
SyntaxShape::String,
|
||||
@ -172,6 +176,24 @@ impl PluginCommand for LazyJoin {
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Perform a full join of two dataframes and coalesce columns",
|
||||
example: r#"let table1 = [[A B]; ["common" "common"] ["table1" "only"]] | polars into-df
|
||||
let table2 = [[A C]; ["common" "common"] ["table2" "only"]] | polars into-df
|
||||
$table1 | polars join -f $table2 --coalesce-columns A A"#,
|
||||
result: Some(
|
||||
NuDataFrame::new(
|
||||
false,
|
||||
df!(
|
||||
"A" => [Some("common"), Some("table2"), Some("table1")],
|
||||
"B" => [Some("common"), None, Some("only")],
|
||||
"C" => [Some("common"), Some("only"), None]
|
||||
)
|
||||
.expect("Should have created a DataFrame"),
|
||||
)
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Join one eager dataframe with another using a cross join",
|
||||
example: r#"let tokens = [[monopoly_token]; [hat] [shoe] [boat]] | polars into-df
|
||||
@ -279,9 +301,17 @@ impl PluginCommand for LazyJoin {
|
||||
let lazy = NuLazyFrame::try_from_value_coerce(plugin, &value)?;
|
||||
let from_eager = lazy.from_eager;
|
||||
let lazy = lazy.to_polars();
|
||||
|
||||
let coalesce = if call.has_flag("coalesce-columns")? {
|
||||
JoinCoalesce::CoalesceColumns
|
||||
} else {
|
||||
JoinCoalesce::default()
|
||||
};
|
||||
|
||||
let lazy = if cross {
|
||||
lazy.join_builder()
|
||||
.with(other)
|
||||
.coalesce(coalesce)
|
||||
.left_on(vec![])
|
||||
.right_on(vec![])
|
||||
.how(how)
|
||||
@ -291,6 +321,7 @@ impl PluginCommand for LazyJoin {
|
||||
} else {
|
||||
lazy.join_builder()
|
||||
.with(other)
|
||||
.coalesce(coalesce)
|
||||
.left_on(left_on)
|
||||
.right_on(right_on)
|
||||
.how(how)
|
||||
|
Loading…
Reference in New Issue
Block a user