Merge branch 'main' into typed-tagged-path-forms

This commit is contained in:
Ian Manske 2024-06-11 21:56:22 -07:00
commit feee1c5ec5
34 changed files with 458 additions and 410 deletions

View File

@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: cargo fmt - name: cargo fmt
run: cargo fmt --all -- --check run: cargo fmt --all -- --check
@ -69,7 +69,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Tests - name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }} run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
@ -98,7 +98,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Install Nushell - name: Install Nushell
run: cargo install --path . --locked --no-default-features run: cargo install --path . --locked --no-default-features
@ -149,7 +149,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Clippy - name: Clippy
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS

View File

@ -122,7 +122,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
rustflags: '' rustflags: ''

View File

@ -69,7 +69,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
cache: false cache: false

View File

@ -10,4 +10,4 @@ jobs:
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.6
- name: Check spelling - name: Check spelling
uses: crate-ci/typos@v1.22.1 uses: crate-ci/typos@v1.22.4

54
Cargo.lock generated
View File

@ -3011,7 +3011,7 @@ dependencies = [
"uu_mv", "uu_mv",
"uu_uname", "uu_uname",
"uu_whoami", "uu_whoami",
"uucore 0.0.25", "uucore",
"uuid", "uuid",
"v_htmlescape", "v_htmlescape",
"wax", "wax",
@ -6352,93 +6352,77 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "uu_cp" name = "uu_cp"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcbe045dc92209114afdfd366bd18f7b95dbf999f3eaa85ad6dca910b0be3d56" checksum = "c31fc5c95f7668999e129464a29e9080f69ba01ccf7a0ae43ff2cfdb15baa340"
dependencies = [ dependencies = [
"clap", "clap",
"filetime", "filetime",
"indicatif", "indicatif",
"libc", "libc",
"quick-error 2.0.1", "quick-error 2.0.1",
"uucore 0.0.26", "uucore",
"walkdir", "walkdir",
"xattr", "xattr",
] ]
[[package]] [[package]]
name = "uu_mkdir" name = "uu_mkdir"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "040aa4584036b2f65e05387b0ea9ac468afce1db325743ce5f350689fd9ce4ae" checksum = "496d95e0e3121e4d424ba62019eb84a6f1102213ca8ca16c0a2f8c652c7236c3"
dependencies = [ dependencies = [
"clap", "clap",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_mktemp" name = "uu_mktemp"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f240a99c36d768153874d198c43605a45c86996b576262689a0f18248cc3bc57" checksum = "a28a0d9744bdc28ceaf13f70b959bacded91aedfd008402d72fa1e3224158653"
dependencies = [ dependencies = [
"clap", "clap",
"rand", "rand",
"tempfile", "tempfile",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_mv" name = "uu_mv"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c99fd7c75e6e85553c92537314be3d9a64b4927051aa1608513feea2f933022" checksum = "53680908b01c5ac3cc0ee8a376de3e51a36dde2c5a5227a115a3d0977cc4539b"
dependencies = [ dependencies = [
"clap", "clap",
"fs_extra", "fs_extra",
"indicatif", "indicatif",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_uname" name = "uu_uname"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5951832d73199636bde6c0d61cf960932b3c4450142c290375bc10c7abed6db5" checksum = "a7f4125fb4f286313bca8f222abaefe39db54d65179ea788c91ebd3162345f4e"
dependencies = [ dependencies = [
"clap", "clap",
"platform-info", "platform-info",
"uucore 0.0.26", "uucore",
] ]
[[package]] [[package]]
name = "uu_whoami" name = "uu_whoami"
version = "0.0.25" version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b44166eb6335aeac42744ea368cc4c32d3f2287a4ff765a5ce44d927ab8bb4" checksum = "7f7b313901a15cfde2d88f434fcd077903d690f73cc36d1cec20f47906960aec"
dependencies = [ dependencies = [
"clap", "clap",
"libc", "libc",
"uucore 0.0.26", "uucore",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "uucore"
version = "0.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23994a722acb43dbc56877e271c9723f167ae42c4c089f909b2d7dd106c3a9b4"
dependencies = [
"clap",
"glob",
"libc",
"nix",
"once_cell",
"os_display",
"uucore_procs",
"wild",
]
[[package]] [[package]]
name = "uucore" name = "uucore"
version = "0.0.26" version = "0.0.26"

View File

@ -159,13 +159,13 @@ unicode-segmentation = "1.11"
unicode-width = "0.1" unicode-width = "0.1"
ureq = { version = "2.9", default-features = false } ureq = { version = "2.9", default-features = false }
url = "2.2" url = "2.2"
uu_cp = "0.0.25" uu_cp = "0.0.26"
uu_mkdir = "0.0.25" uu_mkdir = "0.0.26"
uu_mktemp = "0.0.25" uu_mktemp = "0.0.26"
uu_mv = "0.0.25" uu_mv = "0.0.26"
uu_whoami = "0.0.25" uu_whoami = "0.0.26"
uu_uname = "0.0.25" uu_uname = "0.0.26"
uucore = "0.0.25" uucore = "0.0.26"
uuid = "1.8.0" uuid = "1.8.0"
v_htmlescape = "0.15.0" v_htmlescape = "0.15.0"
wax = "0.6" wax = "0.6"
@ -195,6 +195,7 @@ reedline = { workspace = true, features = ["bashisms", "sqlite"] }
crossterm = { workspace = true } crossterm = { workspace = true }
ctrlc = { workspace = true } ctrlc = { workspace = true }
dirs-next = { workspace = true }
log = { workspace = true } log = { workspace = true }
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] } miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
mimalloc = { version = "0.1.42", default-features = false, optional = true } mimalloc = { version = "0.1.42", default-features = false, optional = true }

View File

@ -52,7 +52,7 @@ To use `Nu` in GitHub Action, check [setup-nu](https://github.com/marketplace/ac
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers: Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg?columns=3)](https://repology.org/project/nushell/versions)
For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md). For details about which platforms the Nushell team actively supports, see [our platform support policy](devdocs/PLATFORM_SUPPORT.md).

View File

@ -75,7 +75,7 @@ const DEFAULT_HELP_MENU: &str = r#"
// Adds all menus to line editor // Adds all menus to line editor
pub(crate) fn add_menus( pub(crate) fn add_menus(
mut line_editor: Reedline, mut line_editor: Reedline,
engine_state: Arc<EngineState>, engine_state_ref: Arc<EngineState>,
stack: &Stack, stack: &Stack,
config: &Config, config: &Config,
) -> Result<Reedline, ShellError> { ) -> Result<Reedline, ShellError> {
@ -83,7 +83,7 @@ pub(crate) fn add_menus(
line_editor = line_editor.clear_menus(); line_editor = line_editor.clear_menus();
for menu in &config.menus { for menu in &config.menus {
line_editor = add_menu(line_editor, menu, engine_state.clone(), stack, config)? line_editor = add_menu(line_editor, menu, engine_state_ref.clone(), stack, config)?
} }
// Checking if the default menus have been added from the config file // Checking if the default menus have been added from the config file
@ -93,13 +93,16 @@ pub(crate) fn add_menus(
("help_menu", DEFAULT_HELP_MENU), ("help_menu", DEFAULT_HELP_MENU),
]; ];
let mut engine_state = (*engine_state_ref).clone();
let mut menu_eval_results = vec![];
for (name, definition) in default_menus { for (name, definition) in default_menus {
if !config if !config
.menus .menus
.iter() .iter()
.any(|menu| menu.name.to_expanded_string("", config) == name) .any(|menu| menu.name.to_expanded_string("", config) == name)
{ {
let (block, _) = { let (block, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state); let mut working_set = StateWorkingSet::new(&engine_state);
let output = parse( let output = parse(
&mut working_set, &mut working_set,
@ -111,15 +114,31 @@ pub(crate) fn add_menus(
(output, working_set.render()) (output, working_set.render())
}; };
engine_state.merge_delta(delta)?;
let mut temp_stack = Stack::new().capture(); let mut temp_stack = Stack::new().capture();
let input = PipelineData::Empty; let input = PipelineData::Empty;
let res = eval_block::<WithoutDebug>(&engine_state, &mut temp_stack, &block, input)?; menu_eval_results.push(eval_block::<WithoutDebug>(
&engine_state,
&mut temp_stack,
&block,
input,
)?);
}
}
if let PipelineData::Value(value, None) = res { let new_engine_state_ref = Arc::new(engine_state);
for menu in create_menus(&value)? {
line_editor = for res in menu_eval_results.into_iter() {
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?; if let PipelineData::Value(value, None) = res {
} for menu in create_menus(&value)? {
line_editor = add_menu(
line_editor,
&menu,
new_engine_state_ref.clone(),
stack,
config,
)?;
} }
} }
} }

View File

@ -763,11 +763,13 @@ fn variables_completions() {
// Test completions for $nu // Test completions for $nu
let suggestions = completer.complete("$nu.", 4); let suggestions = completer.complete("$nu.", 4);
assert_eq!(15, suggestions.len()); assert_eq!(17, suggestions.len());
let expected: Vec<String> = vec![ let expected: Vec<String> = vec![
"cache-dir".into(),
"config-path".into(), "config-path".into(),
"current-exe".into(), "current-exe".into(),
"data-dir".into(),
"default-config-dir".into(), "default-config-dir".into(),
"env-path".into(), "env-path".into(),
"history-enabled".into(), "history-enabled".into(),

View File

@ -67,8 +67,8 @@ impl Command for Def {
}, },
Example { Example {
description: "Define a custom wrapper for an external command", description: "Define a custom wrapper for an external command",
example: r#"def --wrapped my-echo [...rest] { echo $rest }; my-echo spam"#, example: r#"def --wrapped my-echo [...rest] { ^echo ...$rest }; my-echo -e 'spam\tspam'"#,
result: Some(Value::test_list(vec![Value::test_string("spam")])), result: Some(Value::test_string("spam\tspam")),
}, },
] ]
} }

View File

@ -1,5 +1,6 @@
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression}; use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
use nu_protocol::engine::CommandType; use nu_protocol::engine::CommandType;
use nu_protocol::ParseWarning;
#[derive(Clone)] #[derive(Clone)]
pub struct For; pub struct For;
@ -30,7 +31,7 @@ impl Command for For {
.required("block", SyntaxShape::Block, "The block to run.") .required("block", SyntaxShape::Block, "The block to run.")
.switch( .switch(
"numbered", "numbered",
"return a numbered item ($it.index and $it.item)", "DEPRECATED: return a numbered item ($it.index and $it.item)",
Some('n'), Some('n'),
) )
.creates_scope() .creates_scope()
@ -78,6 +79,20 @@ impl Command for For {
let value = eval_expression(engine_state, stack, keyword_expr)?; let value = eval_expression(engine_state, stack, keyword_expr)?;
let numbered = call.has_flag(engine_state, stack, "numbered")?; let numbered = call.has_flag(engine_state, stack, "numbered")?;
if numbered {
nu_protocol::report_error_new(
engine_state,
&ParseWarning::DeprecatedWarning {
old_command: "--numbered/-n".into(),
new_suggestion: "use `enumerate`".into(),
span: call
.get_named_arg("numbered")
.expect("`get_named_arg` found `--numbered` but still failed")
.span,
url: "See `help for` examples".into(),
},
);
}
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
@ -198,8 +213,7 @@ impl Command for For {
}, },
Example { Example {
description: "Number each item and print a message", description: "Number each item and print a message",
example: example: r#"for $it in (['bob' 'fred'] | enumerate) { print $"($it.index) is ($it.item)" }"#,
"for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }",
result: None, result: None,
}, },
] ]

View File

@ -122,7 +122,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
Value::Filesize { .. } => input.clone(), Value::Filesize { .. } => input.clone(),
Value::Int { val, .. } => Value::filesize(*val, value_span), Value::Int { val, .. } => Value::filesize(*val, value_span),
Value::Float { val, .. } => Value::filesize(*val as i64, value_span), Value::Float { val, .. } => Value::filesize(*val as i64, value_span),
Value::String { val, .. } => match int_from_string(val, value_span) { Value::String { val, .. } => match i64_from_string(val, value_span) {
Ok(val) => Value::filesize(val, value_span), Ok(val) => Value::filesize(val, value_span),
Err(error) => Value::error(error, value_span), Err(error) => Value::error(error, value_span),
}, },
@ -139,7 +139,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
} }
} }
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> { fn i64_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
// Get the Locale so we know what the thousands separator is // Get the Locale so we know what the thousands separator is
let locale = get_system_locale(); let locale = get_system_locale();
@ -151,24 +151,35 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
// Hadle negative file size // Hadle negative file size
if let Some(stripped_negative_string) = clean_string.strip_prefix('-') { if let Some(stripped_negative_string) = clean_string.strip_prefix('-') {
match stripped_negative_string.parse::<bytesize::ByteSize>() { match stripped_negative_string.parse::<bytesize::ByteSize>() {
Ok(n) => Ok(-(n.as_u64() as i64)), Ok(n) => i64_from_byte_size(n, true, span),
Err(_) => Err(string_convert_error(span)), Err(_) => Err(string_convert_error(span)),
} }
} else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') { } else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') {
match stripped_positive_string.parse::<bytesize::ByteSize>() { match stripped_positive_string.parse::<bytesize::ByteSize>() {
Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => { Ok(n) if stripped_positive_string.starts_with(|c: char| c.is_ascii_digit()) => {
Ok(n.as_u64() as i64) i64_from_byte_size(n, false, span)
} }
_ => Err(string_convert_error(span)), _ => Err(string_convert_error(span)),
} }
} else { } else {
match clean_string.parse::<bytesize::ByteSize>() { match clean_string.parse::<bytesize::ByteSize>() {
Ok(n) => Ok(n.as_u64() as i64), Ok(n) => i64_from_byte_size(n, false, span),
Err(_) => Err(string_convert_error(span)), Err(_) => Err(string_convert_error(span)),
} }
} }
} }
fn i64_from_byte_size(
byte_size: bytesize::ByteSize,
is_negative: bool,
span: Span,
) -> Result<i64, ShellError> {
match i64::try_from(byte_size.as_u64()) {
Ok(n) => Ok(if is_negative { -n } else { n }),
Err(_) => Err(string_convert_error(span)),
}
}
fn string_convert_error(span: Span) -> ShellError { fn string_convert_error(span: Span) -> ShellError {
ShellError::CantConvert { ShellError::CantConvert {
to_type: "filesize".into(), to_type: "filesize".into(),

View File

@ -1,7 +1,10 @@
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet; use nu_protocol::engine::StateWorkingSet;
use std::path::{Path, PathBuf}; use std::{
io,
path::{Path, PathBuf},
};
struct Arguments { struct Arguments {
pwd: PathBuf, pwd: PathBuf,
@ -36,7 +39,7 @@ impl Command for SubCommand {
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
r#"This checks the file system to confirm the path's object type. r#"This checks the file system to confirm the path's object type.
If nothing is found, an empty string will be returned."# If the path does not exist, null will be returned."#
} }
fn is_const(&self) -> bool { fn is_const(&self) -> bool {
@ -104,9 +107,11 @@ If nothing is found, an empty string will be returned."#
fn path_type(path: &Path, span: Span, args: &Arguments) -> Value { fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
let path = nu_path::expand_path_with(path, &args.pwd, true); let path = nu_path::expand_path_with(path, &args.pwd, true);
let meta = path.symlink_metadata(); match path.symlink_metadata() {
let ty = meta.as_ref().map(get_file_type).unwrap_or(""); Ok(metadata) => Value::string(get_file_type(&metadata), span),
Value::string(ty, span) Err(err) if err.kind() == io::ErrorKind::NotFound => Value::nothing(span),
Err(err) => Value::error(err.into_spanned(span).into(), span),
}
} }
fn get_file_type(md: &std::fs::Metadata) -> &str { fn get_file_type(md: &std::fs::Metadata) -> &str {

View File

@ -10,7 +10,6 @@ struct Arguments {
substring: String, substring: String,
cell_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
case_insensitive: bool, case_insensitive: bool,
not_contain: bool,
} }
impl CmdArgument for Arguments { impl CmdArgument for Arguments {
@ -40,7 +39,6 @@ impl Command for SubCommand {
"For a data structure input, check strings at the given cell paths, and replace with result.", "For a data structure input, check strings at the given cell paths, and replace with result.",
) )
.switch("ignore-case", "search is case insensitive", Some('i')) .switch("ignore-case", "search is case insensitive", Some('i'))
.switch("not", "DEPRECATED OPTION: does not contain", Some('n'))
.category(Category::Strings) .category(Category::Strings)
} }
@ -63,27 +61,12 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
if call.has_flag(engine_state, stack, "not")? {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError {
error: "Deprecated option".into(),
msg: "`str contains --not {string}` is deprecated and will be removed in 0.95."
.into(),
span: Some(call.head),
help: Some("Please use the `not` operator instead.".into()),
inner: vec![],
},
);
}
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?; let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments { let args = Arguments {
substring: call.req::<String>(engine_state, stack, 0)?, substring: call.req::<String>(engine_state, stack, 0)?,
cell_paths, cell_paths,
case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?,
not_contain: call.has_flag(engine_state, stack, "not")?,
}; };
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
@ -114,7 +97,6 @@ impl Command for SubCommand {
substring: call.req_const::<String>(working_set, 0)?, substring: call.req_const::<String>(working_set, 0)?,
cell_paths, cell_paths,
case_insensitive: call.has_flag_const(working_set, "ignore-case")?, case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
not_contain: call.has_flag_const(working_set, "not")?,
}; };
operate( operate(
action, action,
@ -183,7 +165,6 @@ fn action(
input: &Value, input: &Value,
Arguments { Arguments {
case_insensitive, case_insensitive,
not_contain,
substring, substring,
.. ..
}: &Arguments, }: &Arguments,
@ -191,23 +172,11 @@ fn action(
) -> Value { ) -> Value {
match input { match input {
Value::String { val, .. } => Value::bool( Value::String { val, .. } => Value::bool(
match case_insensitive { if *case_insensitive {
true => { val.to_folded_case()
if *not_contain { .contains(substring.to_folded_case().as_str())
!val.to_folded_case() } else {
.contains(substring.to_folded_case().as_str()) val.contains(substring)
} else {
val.to_folded_case()
.contains(substring.to_folded_case().as_str())
}
}
false => {
if *not_contain {
!val.contains(substring)
} else {
val.contains(substring)
}
}
}, },
head, head,
), ),

View File

@ -1,4 +1,6 @@
use super::trim_cstyle_null;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::{CpuRefreshKind, System, MINIMUM_CPU_UPDATE_INTERVAL};
#[derive(Clone)] #[derive(Clone)]
pub struct SysCpu; pub struct SysCpu;
@ -26,7 +28,7 @@ impl Command for SysCpu {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::cpu(call.head).into_pipeline_data()) Ok(cpu(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -37,3 +39,42 @@ impl Command for SysCpu {
}] }]
} }
} }
fn cpu(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
// We must refresh the CPU twice a while apart to get valid usage data.
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
let cpus = sys
.cpus()
.iter()
.map(|cpu| {
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
let load_avg = System::load_average();
let load_avg = format!(
"{:.2}, {:.2}, {:.2}",
load_avg.one, load_avg.five, load_avg.fifteen
);
let record = record! {
"name" => Value::string(trim_cstyle_null(cpu.name()), span),
"brand" => Value::string(trim_cstyle_null(cpu.brand()), span),
"freq" => Value::int(cpu.frequency() as i64, span),
"cpu_usage" => Value::float(rounded_usage.into(), span),
"load_average" => Value::string(load_avg, span),
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span),
};
Value::record(record, span)
})
.collect();
Value::list(cpus, span)
}

View File

@ -1,4 +1,6 @@
use super::trim_cstyle_null;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::Disks;
#[derive(Clone)] #[derive(Clone)]
pub struct SysDisks; pub struct SysDisks;
@ -26,7 +28,7 @@ impl Command for SysDisks {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::disks(call.head).into_pipeline_data()) Ok(disks(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -37,3 +39,27 @@ impl Command for SysDisks {
}] }]
} }
} }
fn disks(span: Span) -> Value {
let disks = Disks::new_with_refreshed_list()
.iter()
.map(|disk| {
let device = trim_cstyle_null(disk.name().to_string_lossy());
let typ = trim_cstyle_null(disk.file_system().to_string_lossy());
let record = record! {
"device" => Value::string(device, span),
"type" => Value::string(typ, span),
"mount" => Value::string(disk.mount_point().to_string_lossy(), span),
"total" => Value::filesize(disk.total_space() as i64, span),
"free" => Value::filesize(disk.available_space() as i64, span),
"removable" => Value::bool(disk.is_removable(), span),
"kind" => Value::string(disk.kind().to_string(), span),
};
Value::record(record, span)
})
.collect();
Value::list(disks, span)
}

View File

@ -1,4 +1,7 @@
use super::trim_cstyle_null;
use chrono::{DateTime, FixedOffset, Local};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::System;
#[derive(Clone)] #[derive(Clone)]
pub struct SysHost; pub struct SysHost;
@ -26,8 +29,7 @@ impl Command for SysHost {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let host = super::host(call.head); Ok(host(call.head).into_pipeline_data())
Ok(Value::record(host, call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -38,3 +40,53 @@ impl Command for SysHost {
}] }]
} }
} }
fn host(span: Span) -> Value {
let mut record = Record::new();
if let Some(name) = System::name() {
record.push("name", Value::string(trim_cstyle_null(name), span));
}
if let Some(version) = System::os_version() {
record.push("os_version", Value::string(trim_cstyle_null(version), span));
}
if let Some(long_version) = System::long_os_version() {
record.push(
"long_os_version",
Value::string(trim_cstyle_null(long_version), span),
);
}
if let Some(version) = System::kernel_version() {
record.push(
"kernel_version",
Value::string(trim_cstyle_null(version), span),
);
}
if let Some(hostname) = System::host_name() {
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
}
let uptime = System::uptime()
.saturating_mul(1_000_000_000)
.try_into()
.unwrap_or(i64::MAX);
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);
Value::record(record, span)
}
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())
}

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::System;
#[derive(Clone)] #[derive(Clone)]
pub struct SysMem; pub struct SysMem;
@ -26,7 +27,7 @@ impl Command for SysMem {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::mem(call.head).into_pipeline_data()) Ok(mem(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -37,3 +38,20 @@ impl Command for SysMem {
}] }]
} }
} }
fn mem(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_memory();
let record = record! {
"total" => Value::filesize(sys.total_memory() as i64, span),
"free" => Value::filesize(sys.free_memory() as i64, span),
"used" => Value::filesize(sys.used_memory() as i64, span),
"available" => Value::filesize(sys.available_memory() as i64, span),
"swap total" => Value::filesize(sys.total_swap() as i64, span),
"swap free" => Value::filesize(sys.free_swap() as i64, span),
"swap used" => Value::filesize(sys.used_swap() as i64, span),
};
Value::record(record, span)
}

View File

@ -16,202 +16,6 @@ pub use sys_::Sys;
pub use temp::SysTemp; pub use temp::SysTemp;
pub use users::SysUsers; pub use users::SysUsers;
use chrono::{DateTime, FixedOffset, Local}; fn trim_cstyle_null(s: impl AsRef<str>) -> String {
use nu_protocol::{record, Record, Span, Value};
use sysinfo::{
Components, CpuRefreshKind, Disks, Networks, System, Users, MINIMUM_CPU_UPDATE_INTERVAL,
};
pub fn trim_cstyle_null(s: impl AsRef<str>) -> String {
s.as_ref().trim_matches('\0').into() s.as_ref().trim_matches('\0').into()
} }
pub fn disks(span: Span) -> Value {
let disks = Disks::new_with_refreshed_list()
.iter()
.map(|disk| {
let device = trim_cstyle_null(disk.name().to_string_lossy());
let typ = trim_cstyle_null(disk.file_system().to_string_lossy());
let record = record! {
"device" => Value::string(device, span),
"type" => Value::string(typ, span),
"mount" => Value::string(disk.mount_point().to_string_lossy(), span),
"total" => Value::filesize(disk.total_space() as i64, span),
"free" => Value::filesize(disk.available_space() as i64, span),
"removable" => Value::bool(disk.is_removable(), span),
"kind" => Value::string(disk.kind().to_string(), span),
};
Value::record(record, span)
})
.collect();
Value::list(disks, span)
}
pub fn net(span: Span) -> Value {
let networks = Networks::new_with_refreshed_list()
.iter()
.map(|(iface, data)| {
let record = record! {
"name" => Value::string(trim_cstyle_null(iface), span),
"sent" => Value::filesize(data.total_transmitted() as i64, span),
"recv" => Value::filesize(data.total_received() as i64, span),
};
Value::record(record, span)
})
.collect();
Value::list(networks, span)
}
pub fn cpu(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
// We must refresh the CPU twice a while apart to get valid usage data.
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
let cpus = sys
.cpus()
.iter()
.map(|cpu| {
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
let load_avg = System::load_average();
let load_avg = format!(
"{:.2}, {:.2}, {:.2}",
load_avg.one, load_avg.five, load_avg.fifteen
);
let record = record! {
"name" => Value::string(trim_cstyle_null(cpu.name()), span),
"brand" => Value::string(trim_cstyle_null(cpu.brand()), span),
"freq" => Value::int(cpu.frequency() as i64, span),
"cpu_usage" => Value::float(rounded_usage.into(), span),
"load_average" => Value::string(load_avg, span),
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span),
};
Value::record(record, span)
})
.collect();
Value::list(cpus, span)
}
pub fn mem(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_memory();
let record = record! {
"total" => Value::filesize(sys.total_memory() as i64, span),
"free" => Value::filesize(sys.free_memory() as i64, span),
"used" => Value::filesize(sys.used_memory() as i64, span),
"available" => Value::filesize(sys.available_memory() as i64, span),
"swap total" => Value::filesize(sys.total_swap() as i64, span),
"swap free" => Value::filesize(sys.free_swap() as i64, span),
"swap used" => Value::filesize(sys.used_swap() as i64, span),
};
Value::record(record, span)
}
pub fn users(span: Span) -> Value {
let users = Users::new_with_refreshed_list()
.iter()
.map(|user| {
let groups = user
.groups()
.iter()
.map(|group| Value::string(trim_cstyle_null(group.name()), span))
.collect();
let record = record! {
"name" => Value::string(trim_cstyle_null(user.name()), span),
"groups" => Value::list(groups, span),
};
Value::record(record, span)
})
.collect();
Value::list(users, span)
}
pub fn host(span: Span) -> Record {
let mut record = Record::new();
if let Some(name) = System::name() {
record.push("name", Value::string(trim_cstyle_null(name), span));
}
if let Some(version) = System::os_version() {
record.push("os_version", Value::string(trim_cstyle_null(version), span));
}
if let Some(long_version) = System::long_os_version() {
record.push(
"long_os_version",
Value::string(trim_cstyle_null(long_version), span),
);
}
if let Some(version) = System::kernel_version() {
record.push(
"kernel_version",
Value::string(trim_cstyle_null(version), span),
);
}
if let Some(hostname) = System::host_name() {
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
}
let uptime = System::uptime()
.saturating_mul(1_000_000_000)
.try_into()
.unwrap_or(i64::MAX);
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()
.map(|component| {
let mut record = record! {
"unit" => Value::string(component.label(), span),
"temp" => Value::float(component.temperature().into(), span),
"high" => Value::float(component.max().into(), span),
};
if let Some(critical) = component.critical() {
record.push("critical", Value::float(critical.into(), span));
}
Value::record(record, span)
})
.collect();
Value::list(components, span)
}

View File

@ -1,4 +1,6 @@
use super::trim_cstyle_null;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::Networks;
#[derive(Clone)] #[derive(Clone)]
pub struct SysNet; pub struct SysNet;
@ -26,7 +28,7 @@ impl Command for SysNet {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::net(call.head).into_pipeline_data()) Ok(net(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -37,3 +39,20 @@ impl Command for SysNet {
}] }]
} }
} }
fn net(span: Span) -> Value {
let networks = Networks::new_with_refreshed_list()
.iter()
.map(|(iface, data)| {
let record = record! {
"name" => Value::string(trim_cstyle_null(iface), span),
"sent" => Value::filesize(data.total_transmitted() as i64, span),
"recv" => Value::filesize(data.total_received() as i64, span),
};
Value::record(record, span)
})
.collect();
Value::list(networks, span)
}

View File

@ -1,4 +1,4 @@
use nu_engine::command_prelude::*; use nu_engine::{command_prelude::*, get_full_help};
#[derive(Clone)] #[derive(Clone)]
pub struct Sys; pub struct Sys;
@ -20,41 +20,17 @@ impl Command for Sys {
} }
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
"Note that this command may take a noticeable amount of time to run. To reduce the time taken, you can use the various `sys` sub commands to get the subset of information you are interested in." "You must use one of the following subcommands. Using this command as-is will only produce this help message."
} }
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
_stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
nu_protocol::report_error_new( Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
engine_state,
&ShellError::GenericError {
error: "Deprecated command".into(),
msg: "the `sys` command is deprecated, please use the new subcommands (`sys host`, `sys mem`, etc.)."
.into(),
span: Some(call.head),
help: None,
inner: vec![],
},
);
let head = call.head;
let mut host = super::host(head);
host.push("sessions", super::users(head));
let record = record! {
"host" => Value::record(host, head),
"cpu" => super::cpu(head),
"disks" => super::disks(head),
"mem" => super::mem(head),
"temp" => super::temp(head),
"net" => super::net(head),
};
Ok(Value::record(record, head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::Components;
#[derive(Clone)] #[derive(Clone)]
pub struct SysTemp; pub struct SysTemp;
@ -30,7 +31,7 @@ impl Command for SysTemp {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::temp(call.head).into_pipeline_data()) Ok(temp(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -41,3 +42,24 @@ impl Command for SysTemp {
}] }]
} }
} }
fn temp(span: Span) -> Value {
let components = Components::new_with_refreshed_list()
.iter()
.map(|component| {
let mut record = record! {
"unit" => Value::string(component.label(), span),
"temp" => Value::float(component.temperature().into(), span),
"high" => Value::float(component.max().into(), span),
};
if let Some(critical) = component.critical() {
record.push("critical", Value::float(critical.into(), span));
}
Value::record(record, span)
})
.collect();
Value::list(components, span)
}

View File

@ -1,4 +1,6 @@
use super::trim_cstyle_null;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use sysinfo::Users;
#[derive(Clone)] #[derive(Clone)]
pub struct SysUsers; pub struct SysUsers;
@ -25,7 +27,7 @@ impl Command for SysUsers {
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
Ok(super::users(call.head).into_pipeline_data()) Ok(users(call.head).into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -36,3 +38,25 @@ impl Command for SysUsers {
}] }]
} }
} }
fn users(span: Span) -> Value {
let users = Users::new_with_refreshed_list()
.iter()
.map(|user| {
let groups = user
.groups()
.iter()
.map(|group| Value::string(trim_cstyle_null(group.name()), span))
.collect();
let record = record! {
"name" => Value::string(trim_cstyle_null(user.name()), span),
"groups" => Value::list(groups, span),
};
Value::record(record, span)
})
.collect();
Value::list(users, span)
}

View File

@ -76,6 +76,13 @@ fn into_filesize_wrong_negative_str_filesize() {
assert!(actual.err.contains("can't convert string to filesize")); assert!(actual.err.contains("can't convert string to filesize"));
} }
#[test]
fn into_filesize_large_negative_str_filesize() {
let actual = nu!("'-10000PiB' | into filesize");
assert!(actual.err.contains("can't convert string to filesize"));
}
#[test] #[test]
fn into_filesize_negative_str() { fn into_filesize_negative_str() {
let actual = nu!("'-1' | into filesize"); let actual = nu!("'-1' | into filesize");
@ -104,6 +111,13 @@ fn into_filesize_wrong_positive_str_filesize() {
assert!(actual.err.contains("can't convert string to filesize")); assert!(actual.err.contains("can't convert string to filesize"));
} }
#[test]
fn into_filesize_large_positive_str_filesize() {
let actual = nu!("'+10000PiB' | into filesize");
assert!(actual.err.contains("can't convert string to filesize"));
}
#[test] #[test]
fn into_filesize_positive_str() { fn into_filesize_positive_str() {
let actual = nu!("'+1' | into filesize"); let actual = nu!("'+1' | into filesize");

View File

@ -906,7 +906,7 @@ fn test_cp_debug_default() {
#[cfg(any(target_os = "linux", target_os = "freebsd"))] #[cfg(any(target_os = "linux", target_os = "freebsd"))]
if !actual if !actual
.out .out
.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") .contains("copy offload: yes, reflink: unsupported, sparse detection: no")
{ {
panic!("{}", format!("Failure: stdout was \n{}", actual.out)); panic!("{}", format!("Failure: stdout was \n{}", actual.out));
} }

View File

@ -6,18 +6,36 @@ pub fn home_dir() -> Option<PathBuf> {
dirs_next::home_dir() dirs_next::home_dir()
} }
/// Return the data directory for the current platform or XDG_DATA_HOME if specified.
pub fn data_dir() -> Option<PathBuf> {
match std::env::var("XDG_DATA_HOME").map(PathBuf::from) {
Ok(xdg_data) if xdg_data.is_absolute() => Some(canonicalize(&xdg_data).unwrap_or(xdg_data)),
_ => get_canonicalized_path(dirs_next::data_dir()),
}
}
/// Return the cache directory for the current platform or XDG_CACHE_HOME if specified.
pub fn cache_dir() -> Option<PathBuf> {
match std::env::var("XDG_CACHE_HOME").map(PathBuf::from) {
Ok(xdg_cache) if xdg_cache.is_absolute() => {
Some(canonicalize(&xdg_cache).unwrap_or(xdg_cache))
}
_ => get_canonicalized_path(dirs_next::cache_dir()),
}
}
/// Return the config directory for the current platform or XDG_CONFIG_HOME if specified.
pub fn config_dir() -> Option<PathBuf> { pub fn config_dir() -> Option<PathBuf> {
match std::env::var("XDG_CONFIG_HOME").map(PathBuf::from) { match std::env::var("XDG_CONFIG_HOME").map(PathBuf::from) {
Ok(xdg_config) if xdg_config.is_absolute() => { Ok(xdg_config) if xdg_config.is_absolute() => {
Some(canonicalize(&xdg_config).unwrap_or(xdg_config)) Some(canonicalize(&xdg_config).unwrap_or(xdg_config))
} }
_ => config_dir_old(), _ => get_canonicalized_path(dirs_next::config_dir()),
} }
} }
/// Get the old default config directory. Outside of Linux, this will ignore `XDG_CONFIG_HOME` pub fn get_canonicalized_path(path: Option<PathBuf>) -> Option<PathBuf> {
pub fn config_dir_old() -> Option<PathBuf> { let path = path?;
let path = dirs_next::config_dir()?;
Some(canonicalize(&path).unwrap_or(path)) Some(canonicalize(&path).unwrap_or(path))
} }

View File

@ -10,7 +10,7 @@ mod trailing_slash;
pub use components::components; pub use components::components;
pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs}; pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs};
pub use helpers::{config_dir, config_dir_old, home_dir}; pub use helpers::{cache_dir, config_dir, data_dir, get_canonicalized_path, home_dir};
pub use path::*; pub use path::*;
pub use tilde::expand_tilde; pub use tilde::expand_tilde;
pub use trailing_slash::{has_trailing_slash, strip_trailing_slash}; pub use trailing_slash::{has_trailing_slash, strip_trailing_slash};

View File

@ -10,7 +10,7 @@ pub enum ParseWarning {
DeprecatedWarning { DeprecatedWarning {
old_command: String, old_command: String,
new_suggestion: String, new_suggestion: String,
#[label("`{old_command}` is deprecated and will be removed in 0.94. Please {new_suggestion} instead")] #[label("`{old_command}` is deprecated and will be removed in a future release. Please {new_suggestion} instead")]
span: Span, span: Span,
url: String, url: String,
}, },

View File

@ -149,6 +149,38 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu
}, },
); );
record.push(
"data-dir",
if let Some(path) = nu_path::data_dir() {
let mut canon_data_path = canonicalize_path(engine_state, &path);
canon_data_path.push("nushell");
Value::string(canon_data_path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError {
msg: "Could not get data path".into(),
},
span,
)
},
);
record.push(
"cache-dir",
if let Some(path) = nu_path::cache_dir() {
let mut canon_cache_path = canonicalize_path(engine_state, &path);
canon_cache_path.push("nushell");
Value::string(canon_cache_path.to_string_lossy(), span)
} else {
Value::error(
ShellError::IOError {
msg: "Could not get cache path".into(),
},
span,
)
},
);
record.push("temp-path", { record.push("temp-path", {
let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir()); let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
Value::string(canon_temp_path.to_string_lossy(), span) Value::string(canon_temp_path.to_string_lossy(), span)

View File

@ -77,6 +77,7 @@ $env.ENV_CONVERSIONS = {
# The default for this is $nu.default-config-dir/scripts # The default for this is $nu.default-config-dir/scripts
$env.NU_LIB_DIRS = [ $env.NU_LIB_DIRS = [
($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts ($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts
($nu.data-dir | path join 'completions') # default home for nushell completions
] ]
# Directories to search for plugin binaries when calling register # Directories to search for plugin binaries when calling register

View File

@ -35,6 +35,7 @@ impl PluginCommand for ToNu {
Some('n'), Some('n'),
) )
.switch("tail", "shows tail rows", Some('t')) .switch("tail", "shows tail rows", Some('t'))
.switch("index", "add an index column", Some('i'))
.input_output_types(vec![ .input_output_types(vec![
(Type::Custom("expression".into()), Type::Any), (Type::Custom("expression".into()), Type::Any),
(Type::Custom("dataframe".into()), Type::table()), (Type::Custom("dataframe".into()), Type::table()),
@ -62,18 +63,18 @@ impl PluginCommand for ToNu {
vec![ vec![
Example { Example {
description: "Shows head rows from dataframe", description: "Shows head rows from dataframe",
example: "[[a b]; [1 2] [3 4]] | polars into-df | polars into-nu", example: "[[a b]; [1 2] [3 4]] | polars into-df | polars into-nu --index",
result: Some(Value::list(vec![rec_1, rec_2], Span::test_data())), result: Some(Value::list(vec![rec_1, rec_2], Span::test_data())),
}, },
Example { Example {
description: "Shows tail rows from dataframe", description: "Shows tail rows from dataframe",
example: example:
"[[a b]; [1 2] [5 6] [3 4]] | polars into-df | polars into-nu --tail --rows 1", "[[a b]; [1 2] [5 6] [3 4]] | polars into-df | polars into-nu --tail --rows 1 --index",
result: Some(Value::list(vec![rec_3], Span::test_data())), result: Some(Value::list(vec![rec_3], Span::test_data())),
}, },
Example { Example {
description: "Convert a col expression into a nushell value", description: "Convert a col expression into a nushell value",
example: "polars col a | polars into-nu", example: "polars col a | polars into-nu --index",
result: Some(Value::test_record(record! { result: Some(Value::test_record(record! {
"expr" => Value::test_string("column"), "expr" => Value::test_string("column"),
"value" => Value::test_string("a"), "value" => Value::test_string("a"),
@ -106,17 +107,18 @@ fn dataframe_command(
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let rows: Option<usize> = call.get_flag("rows")?; let rows: Option<usize> = call.get_flag("rows")?;
let tail: bool = call.has_flag("tail")?; let tail: bool = call.has_flag("tail")?;
let index: bool = call.has_flag("index")?;
let df = NuDataFrame::try_from_value_coerce(plugin, &input, call.head)?; let df = NuDataFrame::try_from_value_coerce(plugin, &input, call.head)?;
let values = if tail { let values = if tail {
df.tail(rows, call.head)? df.tail(rows, index, call.head)?
} else { } else {
// if rows is specified, return those rows, otherwise return everything // if rows is specified, return those rows, otherwise return everything
if rows.is_some() { if rows.is_some() {
df.head(rows, call.head)? df.head(rows, index, call.head)?
} else { } else {
df.head(Some(df.height()), call.head)? df.head(Some(df.height()), index, call.head)?
} }
}; };

View File

@ -326,22 +326,22 @@ impl NuDataFrame {
} }
// Print is made out a head and if the dataframe is too large, then a tail // Print is made out a head and if the dataframe is too large, then a tail
pub fn print(&self, span: Span) -> Result<Vec<Value>, ShellError> { pub fn print(&self, include_index: bool, span: Span) -> Result<Vec<Value>, ShellError> {
let df = &self.df; let df = &self.df;
let size: usize = 20; let size: usize = 20;
if df.height() > size { if df.height() > size {
let sample_size = size / 2; let sample_size = size / 2;
let mut values = self.head(Some(sample_size), span)?; let mut values = self.head(Some(sample_size), include_index, span)?;
conversion::add_separator(&mut values, df, self.has_index(), span); conversion::add_separator(&mut values, df, self.has_index(), span);
let remaining = df.height() - sample_size; let remaining = df.height() - sample_size;
let tail_size = remaining.min(sample_size); let tail_size = remaining.min(sample_size);
let mut tail_values = self.tail(Some(tail_size), span)?; let mut tail_values = self.tail(Some(tail_size), include_index, span)?;
values.append(&mut tail_values); values.append(&mut tail_values);
Ok(values) Ok(values)
} else { } else {
Ok(self.head(Some(size), span)?) Ok(self.head(Some(size), include_index, span)?)
} }
} }
@ -349,26 +349,38 @@ impl NuDataFrame {
self.df.height() self.df.height()
} }
pub fn head(&self, rows: Option<usize>, span: Span) -> Result<Vec<Value>, ShellError> { pub fn head(
&self,
rows: Option<usize>,
include_index: bool,
span: Span,
) -> Result<Vec<Value>, ShellError> {
let to_row = rows.unwrap_or(5); let to_row = rows.unwrap_or(5);
let values = self.to_rows(0, to_row, span)?; let values = self.to_rows(0, to_row, include_index, span)?;
Ok(values) Ok(values)
} }
pub fn tail(&self, rows: Option<usize>, span: Span) -> Result<Vec<Value>, ShellError> { pub fn tail(
&self,
rows: Option<usize>,
include_index: bool,
span: Span,
) -> Result<Vec<Value>, ShellError> {
let df = &self.df; let df = &self.df;
let to_row = df.height(); let to_row = df.height();
let size = rows.unwrap_or(DEFAULT_ROWS); let size = rows.unwrap_or(DEFAULT_ROWS);
let from_row = to_row.saturating_sub(size); let from_row = to_row.saturating_sub(size);
let values = self.to_rows(from_row, to_row, span)?; let values = self.to_rows(from_row, to_row, include_index, span)?;
Ok(values) Ok(values)
} }
/// Converts the dataframe to a nushell list of values
pub fn to_rows( pub fn to_rows(
&self, &self,
from_row: usize, from_row: usize,
to_row: usize, to_row: usize,
include_index: bool,
span: Span, span: Span,
) -> Result<Vec<Value>, ShellError> { ) -> Result<Vec<Value>, ShellError> {
let df = &self.df; let df = &self.df;
@ -400,7 +412,7 @@ impl NuDataFrame {
.map(|i| { .map(|i| {
let mut record = Record::new(); let mut record = Record::new();
if !has_index { if !has_index && include_index {
record.push("index", Value::int((i + from_row) as i64, span)); record.push("index", Value::int((i + from_row) as i64, span));
} }
@ -602,7 +614,7 @@ impl CustomValueSupport for NuDataFrame {
} }
fn base_value(self, span: Span) -> Result<Value, ShellError> { fn base_value(self, span: Span) -> Result<Value, ShellError> {
let vals = self.print(span)?; let vals = self.print(true, span)?;
Ok(Value::list(vals, span)) Ok(Value::list(vals, span))
} }

View File

@ -63,45 +63,16 @@ fn compute_with_value(
op: Span, op: Span,
right: &Value, right: &Value,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let rhs_span = right.span(); let rhs = NuExpression::try_from_value(plugin, right)?;
match right { with_operator(
Value::Custom { val: rhs, .. } => { (plugin, engine),
let rhs = rhs.as_any().downcast_ref::<NuExpression>().ok_or_else(|| { operator,
ShellError::TypeMismatch { left,
err_message: "Right hand side not a dataframe expression".into(), &rhs,
span: rhs_span, lhs_span,
} right.span(),
})?; op,
)
match rhs.as_ref() {
polars::prelude::Expr::Literal(..) => with_operator(
(plugin, engine),
operator,
left,
rhs,
lhs_span,
right.span(),
op,
),
_ => Err(ShellError::TypeMismatch {
err_message: "Only literal expressions or number".into(),
span: right.span(),
}),
}
}
_ => {
let rhs = NuExpression::try_from_value(plugin, right)?;
with_operator(
(plugin, engine),
operator,
left,
&rhs,
lhs_span,
right.span(),
op,
)
}
}
} }
fn with_operator( fn with_operator(

View File

@ -104,7 +104,9 @@ fn main() -> Result<()> {
default: nushell_config_path.display().to_string(), default: nushell_config_path.display().to_string(),
}, },
); );
} else if let Some(old_config) = nu_path::config_dir_old().map(|p| p.join("nushell")) { } else if let Some(old_config) =
nu_path::get_canonicalized_path(dirs_next::config_dir()).map(|p| p.join("nushell"))
{
let xdg_config_empty = nushell_config_path let xdg_config_empty = nushell_config_path
.read_dir() .read_dir()
.map_or(true, |mut dir| dir.next().is_none()); .map_or(true, |mut dir| dir.next().is_none());
@ -125,13 +127,22 @@ fn main() -> Result<()> {
} }
} }
let default_nushell_completions_path = if let Some(mut path) = nu_path::data_dir() {
path.push("nushell");
path.push("completions");
path
} else {
std::path::PathBuf::new()
};
let mut default_nu_lib_dirs_path = nushell_config_path.clone(); let mut default_nu_lib_dirs_path = nushell_config_path.clone();
default_nu_lib_dirs_path.push("scripts"); default_nu_lib_dirs_path.push("scripts");
engine_state.add_env_var( engine_state.add_env_var(
"NU_LIB_DIRS".to_string(), "NU_LIB_DIRS".to_string(),
Value::test_list(vec![Value::test_string( Value::test_list(vec![
default_nu_lib_dirs_path.to_string_lossy(), Value::test_string(default_nu_lib_dirs_path.to_string_lossy()),
)]), Value::test_string(default_nushell_completions_path.to_string_lossy()),
]),
); );
let mut default_nu_plugin_dirs_path = nushell_config_path; let mut default_nu_plugin_dirs_path = nushell_config_path;