mirror of
https://github.com/nushell/nushell
synced 2025-01-26 03:45:19 +00:00
Merge branch 'main' into main
This commit is contained in:
commit
ba8c6a56cb
57 changed files with 568 additions and 335 deletions
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -26,7 +26,7 @@ Make sure you've run and fixed any issues with these commands:
|
|||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
|
||||
- `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
|
||||
- `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library
|
||||
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library
|
||||
|
||||
> **Note**
|
||||
> from `nushell` you can also use the `toolkit` as follows
|
||||
|
|
|
@ -55,7 +55,6 @@ It is good practice to cover your changes with a test. Also, try to think about
|
|||
|
||||
Tests can be found in different places:
|
||||
* `/tests`
|
||||
* `src/tests`
|
||||
* command examples
|
||||
* crate-specific tests
|
||||
|
||||
|
|
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -2034,9 +2034,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "2.0.1"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c7fb8583fab9503654385e2bafda123376445a77027a1b106dd7e44cf51122f"
|
||||
checksum = "7b4d0250d41da118226e55b3d50ca3f0d9e0a0f6829b92f543ac0054aeea1572"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"recvmsg",
|
||||
|
@ -3259,6 +3259,7 @@ dependencies = [
|
|||
"nu-test-support",
|
||||
"nu-utils",
|
||||
"num-format",
|
||||
"os_pipe",
|
||||
"pretty_assertions",
|
||||
"rmp-serde",
|
||||
"rstest",
|
||||
|
@ -5056,9 +5057,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.3.0"
|
||||
version = "8.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745"
|
||||
checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
|
@ -5067,9 +5068,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.3.0"
|
||||
version = "8.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8"
|
||||
checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5080,9 +5081,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.3.0"
|
||||
version = "8.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581"
|
||||
checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
|
|
|
@ -94,7 +94,7 @@ heck = "0.5.0"
|
|||
human-date-parser = "0.1.1"
|
||||
indexmap = "2.2"
|
||||
indicatif = "0.17"
|
||||
interprocess = "2.0.1"
|
||||
interprocess = "2.1.0"
|
||||
is_executable = "1.0"
|
||||
itertools = "0.12"
|
||||
libc = "0.2"
|
||||
|
@ -119,7 +119,7 @@ num-traits = "0.2"
|
|||
omnipath = "0.1"
|
||||
once_cell = "1.18"
|
||||
open = "5.1"
|
||||
os_pipe = "1.1"
|
||||
os_pipe = { version = "1.1", features = ["io_safety"] }
|
||||
pathdiff = "0.2"
|
||||
percent-encoding = "2"
|
||||
pretty_assertions = "1.4"
|
||||
|
@ -140,7 +140,7 @@ ropey = "1.6.1"
|
|||
roxmltree = "0.19"
|
||||
rstest = { version = "0.18", default-features = false }
|
||||
rusqlite = "0.31"
|
||||
rust-embed = "8.3.0"
|
||||
rust-embed = "8.4.0"
|
||||
same-file = "1.0"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
serde_json = "1.0"
|
||||
|
|
|
@ -542,7 +542,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) {
|
|||
let shell_integration_osc633 = config.shell_integration_osc633;
|
||||
let shell_integration_reset_application_mode = config.shell_integration_reset_application_mode;
|
||||
|
||||
let mut stack = Stack::unwrap_unique(stack_arc);
|
||||
// TODO: we may clone the stack, this can lead to major performance issues
|
||||
// so we should avoid it or making stack cheaper to clone.
|
||||
let mut stack = Arc::unwrap_or_clone(stack_arc);
|
||||
|
||||
perf(
|
||||
"line_editor setup",
|
||||
|
|
|
@ -10,7 +10,7 @@ impl Command for EachWhile {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each row of the input list until a null is found, then create a new list with the results."
|
||||
"Run a closure on each row of the input list until a null is found, then create a new list with the results."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
|
|
@ -5,7 +5,7 @@ use commands::add_commands_decls;
|
|||
|
||||
pub use values::{
|
||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
||||
open_connection_in_memory_custom, SQLiteDatabase, MEMORY_DB,
|
||||
open_connection_in_memory_custom, values_to_sql, SQLiteDatabase, MEMORY_DB,
|
||||
};
|
||||
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
|
|
|
@ -3,5 +3,5 @@ pub mod sqlite;
|
|||
|
||||
pub use sqlite::{
|
||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
||||
open_connection_in_memory_custom, SQLiteDatabase, MEMORY_DB,
|
||||
open_connection_in_memory_custom, values_to_sql, SQLiteDatabase, MEMORY_DB,
|
||||
};
|
||||
|
|
10
crates/nu-command/src/env/with_env.rs
vendored
10
crates/nu-command/src/env/with_env.rs
vendored
|
@ -90,10 +90,7 @@ fn with_env(
|
|||
return Err(ShellError::CantConvert {
|
||||
to_type: "record".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: call
|
||||
.positional_nth(1)
|
||||
.expect("already checked through .req")
|
||||
.span,
|
||||
span: x.span(),
|
||||
help: None,
|
||||
});
|
||||
}
|
||||
|
@ -124,10 +121,7 @@ fn with_env(
|
|||
return Err(ShellError::CantConvert {
|
||||
to_type: "record".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
span: call
|
||||
.positional_nth(1)
|
||||
.expect("already checked through .req")
|
||||
.span,
|
||||
span: x.span(),
|
||||
help: None,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ impl Command for Touch {
|
|||
#[allow(deprecated)]
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
for (index, glob) in files.into_iter().enumerate() {
|
||||
for glob in files {
|
||||
let path = expand_path_with(glob.item.as_ref(), &cwd, glob.item.is_expand());
|
||||
|
||||
// If --no-create is passed and the file/dir does not exist there's nothing to do
|
||||
|
@ -135,10 +135,7 @@ impl Command for Touch {
|
|||
{
|
||||
return Err(ShellError::CreateNotPossible {
|
||||
msg: format!("Failed to create file: {err}"),
|
||||
span: call
|
||||
.positional_nth(index)
|
||||
.expect("already checked positional")
|
||||
.span,
|
||||
span: glob.span,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -148,10 +145,7 @@ impl Command for Touch {
|
|||
{
|
||||
return Err(ShellError::ChangeModifiedTimeNotPossible {
|
||||
msg: format!("Failed to change the modified time: {err}"),
|
||||
span: call
|
||||
.positional_nth(index)
|
||||
.expect("already checked positional")
|
||||
.span,
|
||||
span: glob.span,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -161,10 +155,7 @@ impl Command for Touch {
|
|||
{
|
||||
return Err(ShellError::ChangeAccessTimeNotPossible {
|
||||
msg: format!("Failed to change the access time: {err}"),
|
||||
span: call
|
||||
.positional_nth(index)
|
||||
.expect("already checked positional")
|
||||
.span,
|
||||
span: glob.span,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,6 +31,10 @@ impl Command for Compact {
|
|||
"Creates a table with non-empty rows."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["empty", "remove"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
|
|
@ -115,7 +115,7 @@ If multiple cell paths are given, this will produce a list of values."#
|
|||
},
|
||||
Example {
|
||||
description:
|
||||
"Extract the name of the 3rd record in a list (same as `ls | $in.name`)",
|
||||
"Extract the name of the 3rd record in a list (same as `ls | $in.name.2`)",
|
||||
example: "ls | get name.2",
|
||||
result: None,
|
||||
},
|
||||
|
|
|
@ -784,10 +784,7 @@ fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result<St
|
|||
None => {
|
||||
return Err(ShellError::TypeMismatch {
|
||||
err_message: String::from("Unknown ansi code"),
|
||||
span: call
|
||||
.positional_nth(0)
|
||||
.expect("Unexpected missing argument")
|
||||
.span,
|
||||
span: code.span(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::database::{SQLiteDatabase, MEMORY_DB};
|
||||
use crate::database::{values_to_sql, SQLiteDatabase, MEMORY_DB};
|
||||
use nu_engine::command_prelude::*;
|
||||
use rusqlite::params_from_iter;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StorInsert;
|
||||
|
@ -57,86 +58,81 @@ impl Command for StorInsert {
|
|||
// let config = engine_state.get_config();
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
|
||||
if table_name.is_none() {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "requires at table name".into(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
let new_table_name = table_name.unwrap_or("table".into());
|
||||
if let Ok(conn) = db.open_connection() {
|
||||
match columns {
|
||||
Some(record) => {
|
||||
let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name);
|
||||
let cols = record.columns();
|
||||
cols.for_each(|col| {
|
||||
create_stmt.push_str(&format!("{}, ", col));
|
||||
});
|
||||
if create_stmt.ends_with(", ") {
|
||||
create_stmt.pop();
|
||||
create_stmt.pop();
|
||||
}
|
||||
process(table_name, span, &db, columns)?;
|
||||
|
||||
create_stmt.push_str(") VALUES ( ");
|
||||
let vals = record.values();
|
||||
vals.for_each(|val| match val {
|
||||
Value::Int { val, .. } => {
|
||||
create_stmt.push_str(&format!("{}, ", val));
|
||||
}
|
||||
Value::Float { val, .. } => {
|
||||
create_stmt.push_str(&format!("{}, ", val));
|
||||
}
|
||||
Value::String { val, .. } => {
|
||||
create_stmt.push_str(&format!("'{}', ", val));
|
||||
}
|
||||
Value::Date { val, .. } => {
|
||||
create_stmt.push_str(&format!("'{}', ", val));
|
||||
}
|
||||
Value::Bool { val, .. } => {
|
||||
create_stmt.push_str(&format!("{}, ", val));
|
||||
}
|
||||
_ => {
|
||||
// return Err(ShellError::UnsupportedInput {
|
||||
// msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item),
|
||||
// input: "value originates from here".to_string(),
|
||||
// msg_span: span,
|
||||
// input_span: val.span(),
|
||||
// });
|
||||
}
|
||||
});
|
||||
if create_stmt.ends_with(", ") {
|
||||
create_stmt.pop();
|
||||
create_stmt.pop();
|
||||
}
|
||||
|
||||
create_stmt.push(')');
|
||||
|
||||
// dbg!(&create_stmt);
|
||||
|
||||
conn.execute(&create_stmt, [])
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: "Failed to open SQLite connection in memory from insert".into(),
|
||||
msg: err.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
}
|
||||
None => {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "requires at least one column".into(),
|
||||
span: call.head,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
// dbg!(db.clone());
|
||||
Ok(Value::custom(db, span).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
fn process(
|
||||
table_name: Option<String>,
|
||||
span: Span,
|
||||
db: &SQLiteDatabase,
|
||||
columns: Option<Record>,
|
||||
) -> Result<(), ShellError> {
|
||||
if table_name.is_none() {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "requires at table name".into(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
let new_table_name = table_name.unwrap_or("table".into());
|
||||
if let Ok(conn) = db.open_connection() {
|
||||
match columns {
|
||||
Some(record) => {
|
||||
let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name);
|
||||
let cols = record.columns();
|
||||
cols.for_each(|col| {
|
||||
create_stmt.push_str(&format!("{}, ", col));
|
||||
});
|
||||
if create_stmt.ends_with(", ") {
|
||||
create_stmt.pop();
|
||||
create_stmt.pop();
|
||||
}
|
||||
|
||||
// Values are set as placeholders.
|
||||
create_stmt.push_str(") VALUES ( ");
|
||||
for (index, _) in record.columns().enumerate() {
|
||||
create_stmt.push_str(&format!("?{}, ", index + 1));
|
||||
}
|
||||
|
||||
if create_stmt.ends_with(", ") {
|
||||
create_stmt.pop();
|
||||
create_stmt.pop();
|
||||
}
|
||||
|
||||
create_stmt.push(')');
|
||||
|
||||
// dbg!(&create_stmt);
|
||||
|
||||
// Get the params from the passed values
|
||||
let params = values_to_sql(record.values().cloned())?;
|
||||
|
||||
conn.execute(&create_stmt, params_from_iter(params))
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: "Failed to open SQLite connection in memory from insert".into(),
|
||||
msg: err.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
}
|
||||
None => {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "requires at least one column".into(),
|
||||
span,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
// dbg!(db.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use chrono::DateTime;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -145,4 +141,160 @@ mod test {
|
|||
|
||||
test_examples(StorInsert {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_with_simple_parameters() {
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
let create_stmt = "CREATE TABLE test_process_with_simple_parameters (
|
||||
int_column INTEGER,
|
||||
real_column REAL,
|
||||
str_column VARCHAR(255),
|
||||
bool_column BOOLEAN,
|
||||
date_column DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'))
|
||||
)";
|
||||
|
||||
let conn = db
|
||||
.open_connection()
|
||||
.expect("Test was unable to open connection.");
|
||||
conn.execute(create_stmt, [])
|
||||
.expect("Failed to create table as part of test.");
|
||||
let table_name = Some("test_process_with_simple_parameters".to_string());
|
||||
let span = Span::unknown();
|
||||
let mut columns = Record::new();
|
||||
columns.insert("int_column".to_string(), Value::test_int(42));
|
||||
columns.insert("real_column".to_string(), Value::test_float(3.1));
|
||||
columns.insert(
|
||||
"str_column".to_string(),
|
||||
Value::test_string("SimpleString".to_string()),
|
||||
);
|
||||
columns.insert("bool_column".to_string(), Value::test_bool(true));
|
||||
columns.insert(
|
||||
"date_column".to_string(),
|
||||
Value::test_date(
|
||||
DateTime::parse_from_str("2021-12-30 00:00:00 +0000", "%Y-%m-%d %H:%M:%S %z")
|
||||
.expect("Date string should parse."),
|
||||
),
|
||||
);
|
||||
|
||||
let result = process(table_name, span, &db, Some(columns));
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_string_with_space() {
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
let create_stmt = "CREATE TABLE test_process_string_with_space (
|
||||
str_column VARCHAR(255)
|
||||
)";
|
||||
|
||||
let conn = db
|
||||
.open_connection()
|
||||
.expect("Test was unable to open connection.");
|
||||
conn.execute(create_stmt, [])
|
||||
.expect("Failed to create table as part of test.");
|
||||
let table_name = Some("test_process_string_with_space".to_string());
|
||||
let span = Span::unknown();
|
||||
let mut columns = Record::new();
|
||||
columns.insert(
|
||||
"str_column".to_string(),
|
||||
Value::test_string("String With Spaces".to_string()),
|
||||
);
|
||||
|
||||
let result = process(table_name, span, &db, Some(columns));
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_errors_when_string_too_long() {
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
let create_stmt = "CREATE TABLE test_errors_when_string_too_long (
|
||||
str_column VARCHAR(8)
|
||||
)";
|
||||
|
||||
let conn = db
|
||||
.open_connection()
|
||||
.expect("Test was unable to open connection.");
|
||||
conn.execute(create_stmt, [])
|
||||
.expect("Failed to create table as part of test.");
|
||||
let table_name = Some("test_errors_when_string_too_long".to_string());
|
||||
let span = Span::unknown();
|
||||
let mut columns = Record::new();
|
||||
columns.insert(
|
||||
"str_column".to_string(),
|
||||
Value::test_string("ThisIsALongString".to_string()),
|
||||
);
|
||||
|
||||
let result = process(table_name, span, &db, Some(columns));
|
||||
// SQLite uses dynamic typing, making any length acceptable for a varchar column
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_errors_when_param_is_wrong_type() {
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
let create_stmt = "CREATE TABLE test_errors_when_param_is_wrong_type (
|
||||
int_column INT
|
||||
)";
|
||||
|
||||
let conn = db
|
||||
.open_connection()
|
||||
.expect("Test was unable to open connection.");
|
||||
conn.execute(create_stmt, [])
|
||||
.expect("Failed to create table as part of test.");
|
||||
let table_name = Some("test_errors_when_param_is_wrong_type".to_string());
|
||||
let span = Span::unknown();
|
||||
let mut columns = Record::new();
|
||||
columns.insert(
|
||||
"int_column".to_string(),
|
||||
Value::test_string("ThisIsTheWrongType".to_string()),
|
||||
);
|
||||
|
||||
let result = process(table_name, span, &db, Some(columns));
|
||||
// SQLite uses dynamic typing, making any type acceptable for a column
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_errors_when_column_doesnt_exist() {
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
let create_stmt = "CREATE TABLE test_errors_when_column_doesnt_exist (
|
||||
int_column INT
|
||||
)";
|
||||
|
||||
let conn = db
|
||||
.open_connection()
|
||||
.expect("Test was unable to open connection.");
|
||||
conn.execute(create_stmt, [])
|
||||
.expect("Failed to create table as part of test.");
|
||||
let table_name = Some("test_errors_when_column_doesnt_exist".to_string());
|
||||
let span = Span::unknown();
|
||||
let mut columns = Record::new();
|
||||
columns.insert(
|
||||
"not_a_column".to_string(),
|
||||
Value::test_string("ThisIsALongString".to_string()),
|
||||
);
|
||||
|
||||
let result = process(table_name, span, &db, Some(columns));
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_errors_when_table_doesnt_exist() {
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
|
||||
let table_name = Some("test_errors_when_table_doesnt_exist".to_string());
|
||||
let span = Span::unknown();
|
||||
let mut columns = Record::new();
|
||||
columns.insert(
|
||||
"str_column".to_string(),
|
||||
Value::test_string("ThisIsALongString".to_string()),
|
||||
);
|
||||
|
||||
let result = process(table_name, span, &db, Some(columns));
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -235,18 +235,18 @@ impl Command for Char {
|
|||
|
||||
// handle -i flag
|
||||
if integer {
|
||||
let int_args: Vec<i64> = call.rest_const(working_set, 0)?;
|
||||
handle_integer_flag(int_args, call, call_span)
|
||||
let int_args = call.rest_const(working_set, 0)?;
|
||||
handle_integer_flag(int_args, call_span)
|
||||
}
|
||||
// handle -u flag
|
||||
else if unicode {
|
||||
let string_args: Vec<String> = call.rest_const(working_set, 0)?;
|
||||
handle_unicode_flag(string_args, call, call_span)
|
||||
let string_args = call.rest_const(working_set, 0)?;
|
||||
handle_unicode_flag(string_args, call_span)
|
||||
}
|
||||
// handle the rest
|
||||
else {
|
||||
let string_args: Vec<String> = call.rest_const(working_set, 0)?;
|
||||
handle_the_rest(string_args, call, call_span)
|
||||
let string_args = call.rest_const(working_set, 0)?;
|
||||
handle_the_rest(string_args, call_span)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,18 +270,18 @@ impl Command for Char {
|
|||
|
||||
// handle -i flag
|
||||
if integer {
|
||||
let int_args: Vec<i64> = call.rest(engine_state, stack, 0)?;
|
||||
handle_integer_flag(int_args, call, call_span)
|
||||
let int_args = call.rest(engine_state, stack, 0)?;
|
||||
handle_integer_flag(int_args, call_span)
|
||||
}
|
||||
// handle -u flag
|
||||
else if unicode {
|
||||
let string_args: Vec<String> = call.rest(engine_state, stack, 0)?;
|
||||
handle_unicode_flag(string_args, call, call_span)
|
||||
let string_args = call.rest(engine_state, stack, 0)?;
|
||||
handle_unicode_flag(string_args, call_span)
|
||||
}
|
||||
// handle the rest
|
||||
else {
|
||||
let string_args: Vec<String> = call.rest(engine_state, stack, 0)?;
|
||||
handle_the_rest(string_args, call, call_span)
|
||||
let string_args = call.rest(engine_state, stack, 0)?;
|
||||
handle_the_rest(string_args, call_span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,8 +309,7 @@ fn generate_character_list(ctrlc: Option<Arc<AtomicBool>>, call_span: Span) -> P
|
|||
}
|
||||
|
||||
fn handle_integer_flag(
|
||||
int_args: Vec<i64>,
|
||||
call: &Call,
|
||||
int_args: Vec<Spanned<i64>>,
|
||||
call_span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if int_args.is_empty() {
|
||||
|
@ -319,20 +318,17 @@ fn handle_integer_flag(
|
|||
span: call_span,
|
||||
});
|
||||
}
|
||||
let mut multi_byte = String::new();
|
||||
for (i, &arg) in int_args.iter().enumerate() {
|
||||
let span = call
|
||||
.positional_nth(i)
|
||||
.expect("Unexpected missing argument")
|
||||
.span;
|
||||
multi_byte.push(integer_to_unicode_char(arg, span)?)
|
||||
}
|
||||
Ok(Value::string(multi_byte, call_span).into_pipeline_data())
|
||||
|
||||
let str = int_args
|
||||
.into_iter()
|
||||
.map(integer_to_unicode_char)
|
||||
.collect::<Result<String, _>>()?;
|
||||
|
||||
Ok(Value::string(str, call_span).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn handle_unicode_flag(
|
||||
string_args: Vec<String>,
|
||||
call: &Call,
|
||||
string_args: Vec<Spanned<String>>,
|
||||
call_span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if string_args.is_empty() {
|
||||
|
@ -341,57 +337,53 @@ fn handle_unicode_flag(
|
|||
span: call_span,
|
||||
});
|
||||
}
|
||||
let mut multi_byte = String::new();
|
||||
for (i, arg) in string_args.iter().enumerate() {
|
||||
let span = call
|
||||
.positional_nth(i)
|
||||
.expect("Unexpected missing argument")
|
||||
.span;
|
||||
multi_byte.push(string_to_unicode_char(arg, span)?)
|
||||
}
|
||||
Ok(Value::string(multi_byte, call_span).into_pipeline_data())
|
||||
|
||||
let str = string_args
|
||||
.into_iter()
|
||||
.map(string_to_unicode_char)
|
||||
.collect::<Result<String, _>>()?;
|
||||
|
||||
Ok(Value::string(str, call_span).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn handle_the_rest(
|
||||
string_args: Vec<String>,
|
||||
call: &Call,
|
||||
string_args: Vec<Spanned<String>>,
|
||||
call_span: Span,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
if string_args.is_empty() {
|
||||
let Some(s) = string_args.first() else {
|
||||
return Err(ShellError::MissingParameter {
|
||||
param_name: "missing name of the character".into(),
|
||||
span: call_span,
|
||||
});
|
||||
}
|
||||
let special_character = str_to_character(&string_args[0]);
|
||||
};
|
||||
|
||||
let special_character = str_to_character(&s.item);
|
||||
|
||||
if let Some(output) = special_character {
|
||||
Ok(Value::string(output, call_span).into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::TypeMismatch {
|
||||
err_message: "error finding named character".into(),
|
||||
span: call
|
||||
.positional_nth(0)
|
||||
.expect("Unexpected missing argument")
|
||||
.span,
|
||||
span: s.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn integer_to_unicode_char(value: i64, t: Span) -> Result<char, ShellError> {
|
||||
let decoded_char = value.try_into().ok().and_then(std::char::from_u32);
|
||||
fn integer_to_unicode_char(value: Spanned<i64>) -> Result<char, ShellError> {
|
||||
let decoded_char = value.item.try_into().ok().and_then(std::char::from_u32);
|
||||
|
||||
if let Some(ch) = decoded_char {
|
||||
Ok(ch)
|
||||
} else {
|
||||
Err(ShellError::TypeMismatch {
|
||||
err_message: "not a valid Unicode codepoint".into(),
|
||||
span: t,
|
||||
span: value.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_unicode_char(s: &str, t: Span) -> Result<char, ShellError> {
|
||||
let decoded_char = u32::from_str_radix(s, 16)
|
||||
fn string_to_unicode_char(s: Spanned<String>) -> Result<char, ShellError> {
|
||||
let decoded_char = u32::from_str_radix(&s.item, 16)
|
||||
.ok()
|
||||
.and_then(std::char::from_u32);
|
||||
|
||||
|
@ -400,7 +392,7 @@ fn string_to_unicode_char(s: &str, t: Span) -> Result<char, ShellError> {
|
|||
} else {
|
||||
Err(ShellError::TypeMismatch {
|
||||
err_message: "error decoding Unicode character".into(),
|
||||
span: t,
|
||||
span: s.span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,8 @@ pub use sys_::Sys;
|
|||
pub use temp::SysTemp;
|
||||
pub use users::SysUsers;
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
use chrono::{DateTime, FixedOffset, Local};
|
||||
use nu_protocol::{record, Record, Span, Value};
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use sysinfo::{
|
||||
Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL,
|
||||
};
|
||||
|
@ -171,22 +170,31 @@ pub fn host(span: Span) -> Record {
|
|||
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
|
||||
}
|
||||
|
||||
record.push(
|
||||
"uptime",
|
||||
Value::duration(1000000000 * System::uptime() as i64, span),
|
||||
);
|
||||
let uptime = System::uptime()
|
||||
.saturating_mul(1_000_000_000)
|
||||
.try_into()
|
||||
.unwrap_or(i64::MAX);
|
||||
|
||||
// Creates a new SystemTime from the specified number of whole seconds
|
||||
let d = UNIX_EPOCH + Duration::from_secs(System::boot_time());
|
||||
// Create DateTime from SystemTime
|
||||
let datetime = DateTime::<Local>::from(d);
|
||||
// Convert to local time and then rfc3339
|
||||
let timestamp_str = datetime.with_timezone(datetime.offset()).to_rfc3339();
|
||||
record.push("boot_time", Value::string(timestamp_str, span));
|
||||
record.push("uptime", Value::duration(uptime, span));
|
||||
|
||||
let boot_time = boot_time()
|
||||
.map(|time| Value::date(time, span))
|
||||
.unwrap_or(Value::nothing(span));
|
||||
|
||||
record.push("boot_time", boot_time);
|
||||
|
||||
record
|
||||
}
|
||||
|
||||
fn boot_time() -> Option<DateTime<FixedOffset>> {
|
||||
// Broken systems can apparently return really high values.
|
||||
// See: https://github.com/nushell/nushell/issues/10155
|
||||
// First, try to convert u64 to i64, and then try to create a `DateTime`.
|
||||
let secs = System::boot_time().try_into().ok()?;
|
||||
let time = DateTime::from_timestamp(secs, 0)?;
|
||||
Some(time.with_timezone(&Local).fixed_offset())
|
||||
}
|
||||
|
||||
pub fn temp(span: Span) -> Value {
|
||||
let components = Components::new_with_refreshed_list()
|
||||
.iter()
|
||||
|
|
|
@ -20,7 +20,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|||
use nu_color_config::{get_color_map, StyleComputer};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Record, Value,
|
||||
Record, Span, Value,
|
||||
};
|
||||
use ratatui::{layout::Rect, widgets::Block};
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
@ -180,7 +180,11 @@ impl<'a> RecordView<'a> {
|
|||
Orientation::Left => (column, row),
|
||||
};
|
||||
|
||||
layer.records[row][column].clone()
|
||||
if layer.records.len() > row && layer.records[row].len() > column {
|
||||
layer.records[row][column].clone()
|
||||
} else {
|
||||
Value::nothing(Span::unknown())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tablew(&'a self, cfg: ViewConfig<'a>) -> TableW<'a> {
|
||||
|
|
|
@ -132,7 +132,7 @@ pub fn lex_item(
|
|||
},
|
||||
Some(ParseError::UnexpectedEof(
|
||||
(start as char).to_string(),
|
||||
Span::new(span.end, span.end),
|
||||
Span::new(span.end - 1, span.end),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
@ -262,26 +262,10 @@ pub fn lex_item(
|
|||
|
||||
let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
|
||||
|
||||
// If there is still unclosed opening delimiters, remember they were missing
|
||||
if let Some(block) = block_level.last() {
|
||||
let delim = block.closing();
|
||||
let cause =
|
||||
ParseError::UnexpectedEof((delim as char).to_string(), Span::new(span.end, span.end));
|
||||
|
||||
return (
|
||||
Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
},
|
||||
Some(cause),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(delim) = quote_start {
|
||||
// The non-lite parse trims quotes on both sides, so we add the expected quote so that
|
||||
// anyone wanting to consume this partial parse (e.g., completions) will be able to get
|
||||
// correct information from the non-lite parse.
|
||||
|
||||
return (
|
||||
Token {
|
||||
contents: TokenContents::Item,
|
||||
|
@ -289,11 +273,28 @@ pub fn lex_item(
|
|||
},
|
||||
Some(ParseError::UnexpectedEof(
|
||||
(delim as char).to_string(),
|
||||
Span::new(span.start, span.end),
|
||||
Span::new(span.end - 1, span.end),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
// If there is still unclosed opening delimiters, remember they were missing
|
||||
if let Some(block) = block_level.last() {
|
||||
let delim = block.closing();
|
||||
let cause = ParseError::UnexpectedEof(
|
||||
(delim as char).to_string(),
|
||||
Span::new(span.end - 1, span.end),
|
||||
);
|
||||
|
||||
return (
|
||||
Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
},
|
||||
Some(cause),
|
||||
);
|
||||
}
|
||||
|
||||
// If we didn't accumulate any characters, it's an unexpected error.
|
||||
if *curr_offset - token_start == 0 {
|
||||
return (
|
||||
|
@ -411,9 +412,11 @@ fn lex_raw_string(
|
|||
*curr_offset += 1
|
||||
}
|
||||
if !matches {
|
||||
let mut expected = '\''.to_string();
|
||||
expected.push_str(&"#".repeat(prefix_sharp_cnt));
|
||||
return Err(ParseError::UnexpectedEof(
|
||||
"#".to_string(),
|
||||
Span::new(span_offset + *curr_offset, span_offset + *curr_offset),
|
||||
expected,
|
||||
Span::new(span_offset + *curr_offset - 1, span_offset + *curr_offset),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -47,6 +47,7 @@ nu-test-support = { path = "../nu-test-support", version = "0.93.1" }
|
|||
pretty_assertions = { workspace = true }
|
||||
rstest = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
os_pipe = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -154,30 +154,10 @@ impl Call {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn positional_iter_mut(&mut self) -> impl Iterator<Item = &mut Expression> {
|
||||
self.arguments
|
||||
.iter_mut()
|
||||
.take_while(|arg| match arg {
|
||||
Argument::Spread(_) => false, // Don't include positional arguments given to rest parameter
|
||||
_ => true,
|
||||
})
|
||||
.filter_map(|arg| match arg {
|
||||
Argument::Named(_) => None,
|
||||
Argument::Positional(positional) => Some(positional),
|
||||
Argument::Unknown(unknown) => Some(unknown),
|
||||
Argument::Spread(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn positional_nth(&self, i: usize) -> Option<&Expression> {
|
||||
self.positional_iter().nth(i)
|
||||
}
|
||||
|
||||
// TODO this method is never used. Delete?
|
||||
pub fn positional_nth_mut(&mut self, i: usize) -> Option<&mut Expression> {
|
||||
self.positional_iter_mut().nth(i)
|
||||
}
|
||||
|
||||
pub fn positional_len(&self) -> usize {
|
||||
self.positional_iter().count()
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs::File,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
|
@ -74,26 +75,10 @@ impl Stack {
|
|||
}
|
||||
}
|
||||
|
||||
/// Unwrap a uniquely-owned stack.
|
||||
///
|
||||
/// In debug mode, this panics if there are multiple references.
|
||||
/// In production this will instead clone the underlying stack.
|
||||
pub fn unwrap_unique(stack_arc: Arc<Stack>) -> Stack {
|
||||
// If you hit an error here, it's likely that you created an extra
|
||||
// Arc pointing to the stack somewhere. Make sure that it gets dropped before
|
||||
// getting here!
|
||||
Arc::try_unwrap(stack_arc).unwrap_or_else(|arc| {
|
||||
// in release mode, we clone the stack, but this can lead to
|
||||
// major performance issues, so we should avoid it
|
||||
debug_assert!(false, "More than one stack reference remaining!");
|
||||
(*arc).clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new child stack from a parent.
|
||||
///
|
||||
/// Changes from this child can be merged back into the parent with
|
||||
/// Stack::with_changes_from_child
|
||||
/// [`Stack::with_changes_from_child`]
|
||||
pub fn with_parent(parent: Arc<Stack>) -> Stack {
|
||||
Stack {
|
||||
// here we are still cloning environment variable-related information
|
||||
|
@ -108,19 +93,17 @@ impl Stack {
|
|||
}
|
||||
}
|
||||
|
||||
/// Take an Arc of a parent (assumed to be unique), and a child, and apply
|
||||
/// all the changes from a child back to the parent.
|
||||
/// Take an [`Arc`] parent, and a child, and apply all the changes from a child back to the parent.
|
||||
///
|
||||
/// Here it is assumed that child was created with a call to Stack::with_parent
|
||||
/// with parent
|
||||
/// Here it is assumed that `child` was created by a call to [`Stack::with_parent`] with `parent`.
|
||||
///
|
||||
/// For this to be performant and not clone `parent`, `child` should be the only other
|
||||
/// referencer of `parent`.
|
||||
pub fn with_changes_from_child(parent: Arc<Stack>, child: Stack) -> Stack {
|
||||
// we're going to drop the link to the parent stack on our new stack
|
||||
// so that we can unwrap the Arc as a unique reference
|
||||
//
|
||||
// This makes the new_stack be in a bit of a weird state, so we shouldn't call
|
||||
// any structs
|
||||
drop(child.parent_stack);
|
||||
let mut unique_stack = Stack::unwrap_unique(parent);
|
||||
let mut unique_stack = Arc::unwrap_or_clone(parent);
|
||||
|
||||
unique_stack
|
||||
.vars
|
||||
|
@ -593,6 +576,57 @@ impl Stack {
|
|||
self
|
||||
}
|
||||
|
||||
/// Replaces the default stdout of the stack with a given file.
|
||||
///
|
||||
/// This method configures the default stdout to redirect to a specified file.
|
||||
/// It is primarily useful for applications using `nu` as a language, where the stdout of
|
||||
/// external commands that are not explicitly piped can be redirected to a file.
|
||||
///
|
||||
/// # Using Pipes
|
||||
///
|
||||
/// For use in third-party applications pipes might be very useful as they allow using the
|
||||
/// stdout of external commands for different uses.
|
||||
/// For example the [`os_pipe`](https://docs.rs/os_pipe) crate provides a elegant way to to
|
||||
/// access the stdout.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::{fs::File, io::{self, Read}, thread, error};
|
||||
/// # use nu_protocol::engine::Stack;
|
||||
/// #
|
||||
/// let (mut reader, writer) = os_pipe::pipe().unwrap();
|
||||
/// // Use a thread to avoid blocking the execution of the called command.
|
||||
/// let reader = thread::spawn(move || {
|
||||
/// let mut buf: Vec<u8> = Vec::new();
|
||||
/// reader.read_to_end(&mut buf)?;
|
||||
/// Ok::<_, io::Error>(buf)
|
||||
/// });
|
||||
///
|
||||
/// #[cfg(windows)]
|
||||
/// let file = std::os::windows::io::OwnedHandle::from(writer).into();
|
||||
/// #[cfg(unix)]
|
||||
/// let file = std::os::unix::io::OwnedFd::from(writer).into();
|
||||
///
|
||||
/// let stack = Stack::new().stdout_file(file);
|
||||
///
|
||||
/// // Execute some nu code.
|
||||
///
|
||||
/// drop(stack); // drop the stack so that the writer will be dropped too
|
||||
/// let buf = reader.join().unwrap().unwrap();
|
||||
/// // Do with your buffer whatever you want.
|
||||
/// ```
|
||||
pub fn stdout_file(mut self, file: File) -> Self {
|
||||
self.out_dest.stdout = OutDest::File(Arc::new(file));
|
||||
self
|
||||
}
|
||||
|
||||
/// Replaces the default stderr of the stack with a given file.
|
||||
///
|
||||
/// For more info, see [`stdout_file`](Self::stdout_file).
|
||||
pub fn stderr_file(mut self, file: File) -> Self {
|
||||
self.out_dest.stderr = OutDest::File(Arc::new(file));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the PWD environment variable to `path`.
|
||||
///
|
||||
/// This method accepts `path` with trailing slashes, but they're removed
|
||||
|
|
|
@ -287,7 +287,7 @@ export def run-tests [
|
|||
--list, # list the selected tests without running them.
|
||||
--threads: int@"nu-complete threads", # Amount of threads to use for parallel execution. Default: All threads are utilized
|
||||
] {
|
||||
let available_threads = (sys | get cpu | length)
|
||||
let available_threads = (sys cpu | length)
|
||||
|
||||
# Can't use pattern matching here due to https://github.com/nushell/nushell/issues/9198
|
||||
let threads = (if $threads == null {
|
||||
|
|
|
@ -38,7 +38,7 @@ impl PluginCommand for Summary {
|
|||
)
|
||||
.named(
|
||||
"quantiles",
|
||||
SyntaxShape::Table(vec![]),
|
||||
SyntaxShape::List(Box::new(SyntaxShape::Float)),
|
||||
"provide optional quantiles",
|
||||
Some('q'),
|
||||
)
|
||||
|
|
|
@ -27,7 +27,7 @@ impl NuDataFrame {
|
|||
let rhs_span = right.span();
|
||||
match right {
|
||||
Value::Custom { .. } => {
|
||||
let rhs = NuDataFrame::try_from_value(plugin, right)?;
|
||||
let rhs = NuDataFrame::try_from_value_coerce(plugin, right, rhs_span)?;
|
||||
|
||||
match (self.is_series(), rhs.is_series()) {
|
||||
(true, true) => {
|
||||
|
|
|
@ -7,7 +7,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::{
|
||||
values::{CustomValueSupport, NuDataFrame, PolarsPluginCustomValue},
|
||||
PolarsPlugin,
|
||||
Cacheable, PolarsPlugin,
|
||||
};
|
||||
|
||||
use super::NuLazyFrame;
|
||||
|
@ -76,11 +76,49 @@ impl PolarsPluginCustomValue for NuLazyFrameCustomValue {
|
|||
_engine: &EngineInterface,
|
||||
other_value: Value,
|
||||
) -> Result<Option<Ordering>, ShellError> {
|
||||
// to compare, we need to convert to NuDataframe
|
||||
let df = NuLazyFrame::try_from_custom_value(plugin, self)?;
|
||||
let df = df.collect(other_value.span())?;
|
||||
let eager = NuLazyFrame::try_from_custom_value(plugin, self)?.collect(Span::unknown())?;
|
||||
let other = NuDataFrame::try_from_value_coerce(plugin, &other_value, other_value.span())?;
|
||||
let res = df.is_equal(&other);
|
||||
let res = eager.is_equal(&other);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn custom_value_operation(
|
||||
&self,
|
||||
plugin: &PolarsPlugin,
|
||||
engine: &EngineInterface,
|
||||
lhs_span: Span,
|
||||
operator: nu_protocol::Spanned<nu_protocol::ast::Operator>,
|
||||
right: Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
let eager = NuLazyFrame::try_from_custom_value(plugin, self)?.collect(Span::unknown())?;
|
||||
Ok(eager
|
||||
.compute_with_value(plugin, lhs_span, operator.item, operator.span, &right)?
|
||||
.cache(plugin, engine, lhs_span)?
|
||||
.into_value(lhs_span))
|
||||
}
|
||||
|
||||
fn custom_value_follow_path_int(
|
||||
&self,
|
||||
plugin: &PolarsPlugin,
|
||||
_engine: &EngineInterface,
|
||||
_self_span: Span,
|
||||
index: nu_protocol::Spanned<usize>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let eager = NuLazyFrame::try_from_custom_value(plugin, self)?.collect(Span::unknown())?;
|
||||
eager.get_value(index.item, index.span)
|
||||
}
|
||||
|
||||
fn custom_value_follow_path_string(
|
||||
&self,
|
||||
plugin: &PolarsPlugin,
|
||||
engine: &EngineInterface,
|
||||
self_span: Span,
|
||||
column_name: nu_protocol::Spanned<String>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let eager = NuLazyFrame::try_from_custom_value(plugin, self)?.collect(Span::unknown())?;
|
||||
let column = eager.column(&column_name.item, self_span)?;
|
||||
Ok(column
|
||||
.cache(plugin, engine, self_span)?
|
||||
.into_value(self_span))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use log::{info, trace};
|
||||
use log::warn;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_cli::read_plugin_file;
|
||||
use nu_cli::{eval_config_contents, eval_source};
|
||||
|
@ -27,7 +27,10 @@ pub(crate) fn read_config_file(
|
|||
config_file: Option<Spanned<String>>,
|
||||
is_env_config: bool,
|
||||
) {
|
||||
trace!("read_config_file {:?}", &config_file);
|
||||
warn!(
|
||||
"read_config_file() config_file_specified: {:?}, is_env_config: {is_env_config}",
|
||||
&config_file
|
||||
);
|
||||
// Load config startup file
|
||||
if let Some(file) = config_file {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
@ -122,21 +125,27 @@ pub(crate) fn read_config_file(
|
|||
}
|
||||
|
||||
pub(crate) fn read_loginshell_file(engine_state: &mut EngineState, stack: &mut Stack) {
|
||||
warn!(
|
||||
"read_loginshell_file() {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
// read and execute loginshell file if exists
|
||||
if let Some(mut config_path) = nu_path::config_dir() {
|
||||
config_path.push(NUSHELL_FOLDER);
|
||||
config_path.push(LOGINSHELL_FILE);
|
||||
|
||||
warn!("loginshell_file: {}", config_path.display());
|
||||
|
||||
if config_path.exists() {
|
||||
eval_config_contents(config_path, engine_state, stack);
|
||||
}
|
||||
}
|
||||
|
||||
info!("read_loginshell_file {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
pub(crate) fn read_default_env_file(engine_state: &mut EngineState, stack: &mut Stack) {
|
||||
trace!("read_default_env_file");
|
||||
let config_file = get_default_env();
|
||||
eval_source(
|
||||
engine_state,
|
||||
|
@ -147,7 +156,13 @@ pub(crate) fn read_default_env_file(engine_state: &mut EngineState, stack: &mut
|
|||
false,
|
||||
);
|
||||
|
||||
info!("read_config_file {}:{}:{}", file!(), line!(), column!());
|
||||
warn!(
|
||||
"read_default_env_file() env_file_contents: {config_file} {}:{}:{}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
|
||||
// Merge the environment in case env vars changed in the config
|
||||
match engine_state.cwd(Some(stack)) {
|
||||
Ok(cwd) => {
|
||||
|
@ -167,10 +182,9 @@ fn eval_default_config(
|
|||
config_file: &str,
|
||||
is_env_config: bool,
|
||||
) {
|
||||
trace!(
|
||||
"eval_default_config: config_file: {:?}, is_env_config: {}",
|
||||
&config_file,
|
||||
is_env_config
|
||||
warn!(
|
||||
"eval_default_config() config_file_specified: {:?}, is_env_config: {}",
|
||||
&config_file, is_env_config
|
||||
);
|
||||
println!("Continuing without config file");
|
||||
// Just use the contents of "default_config.nu" or "default_env.nu"
|
||||
|
@ -208,11 +222,9 @@ pub(crate) fn setup_config(
|
|||
env_file: Option<Spanned<String>>,
|
||||
is_login_shell: bool,
|
||||
) {
|
||||
trace!(
|
||||
"setup_config: config: {:?}, env: {:?}, login: {}",
|
||||
&config_file,
|
||||
&env_file,
|
||||
is_login_shell
|
||||
warn!(
|
||||
"setup_config() config_file_specified: {:?}, env_file_specified: {:?}, login: {}",
|
||||
&config_file, &env_file, is_login_shell
|
||||
);
|
||||
let result = catch_unwind(AssertUnwindSafe(|| {
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -240,12 +252,9 @@ pub(crate) fn set_config_path(
|
|||
key: &str,
|
||||
config_file: Option<&Spanned<String>>,
|
||||
) {
|
||||
trace!(
|
||||
"set_config_path: cwd: {:?}, default_config: {}, key: {}, config_file: {:?}",
|
||||
&cwd,
|
||||
&default_config_name,
|
||||
&key,
|
||||
&config_file
|
||||
warn!(
|
||||
"set_config_path() cwd: {:?}, default_config: {}, key: {}, config_file_specified: {:?}",
|
||||
&cwd, &default_config_name, &key, &config_file
|
||||
);
|
||||
let config_path = match config_file {
|
||||
Some(s) => canonicalize_with(&s.item, cwd).ok(),
|
||||
|
|
|
@ -7,8 +7,6 @@ mod signals;
|
|||
#[cfg(unix)]
|
||||
mod terminal;
|
||||
mod test_bins;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "mimalloc")]
|
||||
#[global_allocator]
|
||||
|
|
|
@ -11,5 +11,6 @@ mod path;
|
|||
mod plugin_persistence;
|
||||
#[cfg(feature = "plugin")]
|
||||
mod plugins;
|
||||
mod repl;
|
||||
mod scope;
|
||||
mod shell;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use rstest::rstest;
|
||||
|
||||
use nu_test_support::nu_with_plugins;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
|
@ -190,3 +192,18 @@ fn generate_sequence() {
|
|||
|
||||
assert_eq!(actual.out, "[0,2,4,6,8,10]");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(std::time::Duration::from_secs(6))]
|
||||
fn echo_interactivity_on_slow_pipelines() {
|
||||
// This test works by putting 0 on the upstream immediately, followed by 1 after 10 seconds.
|
||||
// If values aren't streamed to the plugin as they become available, `example echo` won't emit
|
||||
// anything until both 0 and 1 are available. The desired behavior is that `example echo` gets
|
||||
// the 0 immediately, which is consumed by `first`, allowing the pipeline to terminate early.
|
||||
let actual = nu_with_plugins!(
|
||||
cwd: "tests/fixtures/formats",
|
||||
plugin: ("nu_plugin_example"),
|
||||
r#"[1] | each { |n| sleep 10sec; $n } | prepend 0 | example echo | first"#
|
||||
);
|
||||
assert_eq!(actual.out, "0");
|
||||
}
|
||||
|
|
27
tests/repl/mod.rs
Normal file
27
tests/repl/mod.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
mod test_bits;
|
||||
mod test_cell_path;
|
||||
mod test_commandline;
|
||||
mod test_conditionals;
|
||||
mod test_config;
|
||||
mod test_config_path;
|
||||
mod test_converters;
|
||||
mod test_custom_commands;
|
||||
mod test_engine;
|
||||
mod test_env;
|
||||
mod test_help;
|
||||
mod test_hiding;
|
||||
mod test_ide;
|
||||
mod test_iteration;
|
||||
mod test_known_external;
|
||||
mod test_math;
|
||||
mod test_modules;
|
||||
mod test_parser;
|
||||
mod test_ranges;
|
||||
mod test_regex;
|
||||
mod test_signatures;
|
||||
mod test_spread;
|
||||
mod test_stdlib;
|
||||
mod test_strings;
|
||||
mod test_table_operations;
|
||||
mod test_type_check;
|
||||
mod tests;
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{run_test, TestResult};
|
||||
use crate::repl::tests::{run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn bits_and() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
// Tests for null / null / Value::Nothing
|
||||
#[test]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn commandline_test_get_empty() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{run_test, TestResult};
|
||||
use crate::repl::tests::{run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn if_test1() -> TestResult {
|
|
@ -1,5 +1,4 @@
|
|||
use super::{fail_test, run_test, run_test_std};
|
||||
use crate::tests::TestResult;
|
||||
use crate::repl::tests::{fail_test, run_test, run_test_std, TestResult};
|
||||
|
||||
#[test]
|
||||
fn mutate_nu_config() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{run_test, TestResult};
|
||||
use crate::repl::tests::{run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn from_json_1() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, run_test_contains, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, run_test_contains, TestResult};
|
||||
use nu_test_support::nu;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{run_test, TestResult};
|
||||
use crate::repl::tests::{run_test, TestResult};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
// TODO: Test the use/hide tests also as separate lines in REPL (i.e., with merging the delta in between)
|
||||
#[test]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{test_ide_contains, TestResult};
|
||||
use crate::repl::tests::{test_ide_contains, TestResult};
|
||||
|
||||
#[test]
|
||||
fn parser_recovers() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{run_test, TestResult};
|
||||
use crate::repl::tests::{run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn better_block_types() -> TestResult {
|
|
@ -1,7 +1,6 @@
|
|||
use crate::repl::tests::{fail_test, run_test, run_test_contains, TestResult};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::tests::{fail_test, run_test, run_test_contains, TestResult};
|
||||
|
||||
// cargo version prints a string of the form:
|
||||
// cargo 1.60.0 (d1fd9fe2c 2022-03-01)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn add_simple() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn module_def_imports_1() -> TestResult {
|
|
@ -1,9 +1,7 @@
|
|||
use crate::tests::{fail_test, run_test, run_test_with_env, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, run_test_contains, run_test_with_env, TestResult};
|
||||
use nu_test_support::{nu, nu_repl_code};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::run_test_contains;
|
||||
|
||||
#[test]
|
||||
fn env_shorthand() -> TestResult {
|
||||
run_test("FOO=BAR if false { 3 } else { 4 }", "4")
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn int_in_inc_range() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn contains() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn list_annotations() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
use nu_test_support::nu;
|
||||
|
||||
#[test]
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test_std, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test_std, TestResult};
|
||||
|
||||
#[test]
|
||||
fn library_loaded() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn cjk_in_substrings() -> TestResult {
|
||||
|
@ -154,6 +154,18 @@ fn raw_string_inside_closure() -> TestResult {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_raw_string() -> TestResult {
|
||||
fail_test("r#abc", "expected '")
|
||||
fn incomplete_string() -> TestResult {
|
||||
fail_test("r#abc", "expected '")?;
|
||||
fail_test("r#'bc", "expected closing '#")?;
|
||||
fail_test("'ab\"", "expected closing '")?;
|
||||
fail_test("\"ab'", "expected closing \"")?;
|
||||
fail_test(
|
||||
r#"def func [] {
|
||||
{
|
||||
"A": ""B" # <- the quote is bad
|
||||
}
|
||||
}
|
||||
"#,
|
||||
"expected closing \"",
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn illegal_column_duplication() -> TestResult {
|
|
@ -1,4 +1,4 @@
|
|||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn chained_operator_typecheck() -> TestResult {
|
|
@ -1,30 +1,3 @@
|
|||
mod test_bits;
|
||||
mod test_cell_path;
|
||||
mod test_commandline;
|
||||
mod test_conditionals;
|
||||
mod test_config;
|
||||
mod test_config_path;
|
||||
mod test_converters;
|
||||
mod test_custom_commands;
|
||||
mod test_engine;
|
||||
mod test_env;
|
||||
mod test_help;
|
||||
mod test_hiding;
|
||||
mod test_ide;
|
||||
mod test_iteration;
|
||||
mod test_known_external;
|
||||
mod test_math;
|
||||
mod test_modules;
|
||||
mod test_parser;
|
||||
mod test_ranges;
|
||||
mod test_regex;
|
||||
mod test_signatures;
|
||||
mod test_spread;
|
||||
mod test_stdlib;
|
||||
mod test_strings;
|
||||
mod test_table_operations;
|
||||
mod test_type_check;
|
||||
|
||||
use assert_cmd::prelude::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
Loading…
Reference in a new issue