Merge branch 'main' into typed-tagged-path-forms
This commit is contained in:
commit
feee1c5ec5
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
|||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- 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
|
||||
run: cargo fmt --all -- --check
|
||||
|
@ -69,7 +69,7 @@ jobs:
|
|||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- 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
|
||||
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }}
|
||||
|
@ -98,7 +98,7 @@ jobs:
|
|||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- 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
|
||||
run: cargo install --path . --locked --no-default-features
|
||||
|
@ -149,7 +149,7 @@ jobs:
|
|||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
- 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
|
||||
run: cargo clippy --package nu_plugin_* -- $CLIPPY_OPTIONS
|
||||
|
|
2
.github/workflows/nightly-build.yml
vendored
2
.github/workflows/nightly-build.yml
vendored
|
@ -122,7 +122,7 @@ jobs:
|
|||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- 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`
|
||||
with:
|
||||
rustflags: ''
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -69,7 +69,7 @@ jobs:
|
|||
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
|
||||
|
||||
- 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`
|
||||
with:
|
||||
cache: false
|
||||
|
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
|
@ -10,4 +10,4 @@ jobs:
|
|||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.22.1
|
||||
uses: crate-ci/typos@v1.22.4
|
||||
|
|
54
Cargo.lock
generated
54
Cargo.lock
generated
|
@ -3011,7 +3011,7 @@ dependencies = [
|
|||
"uu_mv",
|
||||
"uu_uname",
|
||||
"uu_whoami",
|
||||
"uucore 0.0.25",
|
||||
"uucore",
|
||||
"uuid",
|
||||
"v_htmlescape",
|
||||
"wax",
|
||||
|
@ -6352,93 +6352,77 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
|||
|
||||
[[package]]
|
||||
name = "uu_cp"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcbe045dc92209114afdfd366bd18f7b95dbf999f3eaa85ad6dca910b0be3d56"
|
||||
checksum = "c31fc5c95f7668999e129464a29e9080f69ba01ccf7a0ae43ff2cfdb15baa340"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"filetime",
|
||||
"indicatif",
|
||||
"libc",
|
||||
"quick-error 2.0.1",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
"walkdir",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_mkdir"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "040aa4584036b2f65e05387b0ea9ac468afce1db325743ce5f350689fd9ce4ae"
|
||||
checksum = "496d95e0e3121e4d424ba62019eb84a6f1102213ca8ca16c0a2f8c652c7236c3"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_mktemp"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f240a99c36d768153874d198c43605a45c86996b576262689a0f18248cc3bc57"
|
||||
checksum = "a28a0d9744bdc28ceaf13f70b959bacded91aedfd008402d72fa1e3224158653"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"rand",
|
||||
"tempfile",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_mv"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c99fd7c75e6e85553c92537314be3d9a64b4927051aa1608513feea2f933022"
|
||||
checksum = "53680908b01c5ac3cc0ee8a376de3e51a36dde2c5a5227a115a3d0977cc4539b"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"fs_extra",
|
||||
"indicatif",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_uname"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5951832d73199636bde6c0d61cf960932b3c4450142c290375bc10c7abed6db5"
|
||||
checksum = "a7f4125fb4f286313bca8f222abaefe39db54d65179ea788c91ebd3162345f4e"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"platform-info",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_whoami"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b44166eb6335aeac42744ea368cc4c32d3f2287a4ff765a5ce44d927ab8bb4"
|
||||
checksum = "7f7b313901a15cfde2d88f434fcd077903d690f73cc36d1cec20f47906960aec"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"uucore 0.0.26",
|
||||
"uucore",
|
||||
"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]]
|
||||
name = "uucore"
|
||||
version = "0.0.26"
|
||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -159,13 +159,13 @@ unicode-segmentation = "1.11"
|
|||
unicode-width = "0.1"
|
||||
ureq = { version = "2.9", default-features = false }
|
||||
url = "2.2"
|
||||
uu_cp = "0.0.25"
|
||||
uu_mkdir = "0.0.25"
|
||||
uu_mktemp = "0.0.25"
|
||||
uu_mv = "0.0.25"
|
||||
uu_whoami = "0.0.25"
|
||||
uu_uname = "0.0.25"
|
||||
uucore = "0.0.25"
|
||||
uu_cp = "0.0.26"
|
||||
uu_mkdir = "0.0.26"
|
||||
uu_mktemp = "0.0.26"
|
||||
uu_mv = "0.0.26"
|
||||
uu_whoami = "0.0.26"
|
||||
uu_uname = "0.0.26"
|
||||
uucore = "0.0.26"
|
||||
uuid = "1.8.0"
|
||||
v_htmlescape = "0.15.0"
|
||||
wax = "0.6"
|
||||
|
@ -195,6 +195,7 @@ reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
|||
|
||||
crossterm = { workspace = true }
|
||||
ctrlc = { workspace = true }
|
||||
dirs-next = { workspace = true }
|
||||
log = { workspace = true }
|
||||
miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] }
|
||||
mimalloc = { version = "0.1.42", default-features = false, optional = true }
|
||||
|
|
|
@ -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:
|
||||
|
||||
[](https://repology.org/project/nushell/versions)
|
||||
[](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).
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ const DEFAULT_HELP_MENU: &str = r#"
|
|||
// Adds all menus to line editor
|
||||
pub(crate) fn add_menus(
|
||||
mut line_editor: Reedline,
|
||||
engine_state: Arc<EngineState>,
|
||||
engine_state_ref: Arc<EngineState>,
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Result<Reedline, ShellError> {
|
||||
|
@ -83,7 +83,7 @@ pub(crate) fn add_menus(
|
|||
line_editor = line_editor.clear_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
|
||||
|
@ -93,13 +93,16 @@ pub(crate) fn add_menus(
|
|||
("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 {
|
||||
if !config
|
||||
.menus
|
||||
.iter()
|
||||
.any(|menu| menu.name.to_expanded_string("", config) == name)
|
||||
{
|
||||
let (block, _) = {
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let output = parse(
|
||||
&mut working_set,
|
||||
|
@ -111,15 +114,31 @@ pub(crate) fn add_menus(
|
|||
(output, working_set.render())
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
|
||||
let mut temp_stack = Stack::new().capture();
|
||||
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 {
|
||||
for menu in create_menus(&value)? {
|
||||
line_editor =
|
||||
add_menu(line_editor, &menu, engine_state.clone(), stack, config)?;
|
||||
}
|
||||
let new_engine_state_ref = Arc::new(engine_state);
|
||||
|
||||
for res in menu_eval_results.into_iter() {
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -763,11 +763,13 @@ fn variables_completions() {
|
|||
// Test completions for $nu
|
||||
let suggestions = completer.complete("$nu.", 4);
|
||||
|
||||
assert_eq!(15, suggestions.len());
|
||||
assert_eq!(17, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"cache-dir".into(),
|
||||
"config-path".into(),
|
||||
"current-exe".into(),
|
||||
"data-dir".into(),
|
||||
"default-config-dir".into(),
|
||||
"env-path".into(),
|
||||
"history-enabled".into(),
|
||||
|
|
|
@ -67,8 +67,8 @@ impl Command for Def {
|
|||
},
|
||||
Example {
|
||||
description: "Define a custom wrapper for an external command",
|
||||
example: r#"def --wrapped my-echo [...rest] { echo $rest }; my-echo spam"#,
|
||||
result: Some(Value::test_list(vec![Value::test_string("spam")])),
|
||||
example: r#"def --wrapped my-echo [...rest] { ^echo ...$rest }; my-echo -e 'spam\tspam'"#,
|
||||
result: Some(Value::test_string("spam\tspam")),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression};
|
||||
use nu_protocol::engine::CommandType;
|
||||
use nu_protocol::ParseWarning;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct For;
|
||||
|
@ -30,7 +31,7 @@ impl Command for For {
|
|||
.required("block", SyntaxShape::Block, "The block to run.")
|
||||
.switch(
|
||||
"numbered",
|
||||
"return a numbered item ($it.index and $it.item)",
|
||||
"DEPRECATED: return a numbered item ($it.index and $it.item)",
|
||||
Some('n'),
|
||||
)
|
||||
.creates_scope()
|
||||
|
@ -78,6 +79,20 @@ impl Command for For {
|
|||
let value = eval_expression(engine_state, stack, keyword_expr)?;
|
||||
|
||||
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 engine_state = engine_state.clone();
|
||||
|
@ -198,8 +213,7 @@ impl Command for For {
|
|||
},
|
||||
Example {
|
||||
description: "Number each item and print a message",
|
||||
example:
|
||||
"for $it in ['bob' 'fred'] --numbered { print $\"($it.index) is ($it.item)\" }",
|
||||
example: r#"for $it in (['bob' 'fred'] | enumerate) { print $"($it.index) is ($it.item)" }"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -122,7 +122,7 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
|||
Value::Filesize { .. } => input.clone(),
|
||||
Value::Int { val, .. } => Value::filesize(*val, 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),
|
||||
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
|
||||
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
|
||||
if let Some(stripped_negative_string) = clean_string.strip_prefix('-') {
|
||||
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)),
|
||||
}
|
||||
} else if let Some(stripped_positive_string) = clean_string.strip_prefix('+') {
|
||||
match stripped_positive_string.parse::<bytesize::ByteSize>() {
|
||||
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)),
|
||||
}
|
||||
} else {
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
ShellError::CantConvert {
|
||||
to_type: "filesize".into(),
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use super::PathSubcommandArguments;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pwd: PathBuf,
|
||||
|
@ -36,7 +39,7 @@ impl Command for SubCommand {
|
|||
|
||||
fn extra_usage(&self) -> &str {
|
||||
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 {
|
||||
|
@ -104,9 +107,11 @@ If nothing is found, an empty string will be returned."#
|
|||
|
||||
fn path_type(path: &Path, span: Span, args: &Arguments) -> Value {
|
||||
let path = nu_path::expand_path_with(path, &args.pwd, true);
|
||||
let meta = path.symlink_metadata();
|
||||
let ty = meta.as_ref().map(get_file_type).unwrap_or("");
|
||||
Value::string(ty, span)
|
||||
match path.symlink_metadata() {
|
||||
Ok(metadata) => Value::string(get_file_type(&metadata), 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 {
|
||||
|
|
|
@ -10,7 +10,6 @@ struct Arguments {
|
|||
substring: String,
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
case_insensitive: bool,
|
||||
not_contain: bool,
|
||||
}
|
||||
|
||||
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.",
|
||||
)
|
||||
.switch("ignore-case", "search is case insensitive", Some('i'))
|
||||
.switch("not", "DEPRECATED OPTION: does not contain", Some('n'))
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
|
@ -63,27 +61,12 @@ impl Command for SubCommand {
|
|||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> 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 = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
let args = Arguments {
|
||||
substring: call.req::<String>(engine_state, stack, 0)?,
|
||||
cell_paths,
|
||||
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())
|
||||
}
|
||||
|
@ -114,7 +97,6 @@ impl Command for SubCommand {
|
|||
substring: call.req_const::<String>(working_set, 0)?,
|
||||
cell_paths,
|
||||
case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
|
||||
not_contain: call.has_flag_const(working_set, "not")?,
|
||||
};
|
||||
operate(
|
||||
action,
|
||||
|
@ -183,7 +165,6 @@ fn action(
|
|||
input: &Value,
|
||||
Arguments {
|
||||
case_insensitive,
|
||||
not_contain,
|
||||
substring,
|
||||
..
|
||||
}: &Arguments,
|
||||
|
@ -191,23 +172,11 @@ fn action(
|
|||
) -> Value {
|
||||
match input {
|
||||
Value::String { val, .. } => Value::bool(
|
||||
match case_insensitive {
|
||||
true => {
|
||||
if *not_contain {
|
||||
!val.to_folded_case()
|
||||
.contains(substring.to_folded_case().as_str())
|
||||
} else {
|
||||
val.to_folded_case()
|
||||
.contains(substring.to_folded_case().as_str())
|
||||
}
|
||||
}
|
||||
false => {
|
||||
if *not_contain {
|
||||
!val.contains(substring)
|
||||
} else {
|
||||
val.contains(substring)
|
||||
}
|
||||
}
|
||||
if *case_insensitive {
|
||||
val.to_folded_case()
|
||||
.contains(substring.to_folded_case().as_str())
|
||||
} else {
|
||||
val.contains(substring)
|
||||
},
|
||||
head,
|
||||
),
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::{CpuRefreshKind, System, MINIMUM_CPU_UPDATE_INTERVAL};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysCpu;
|
||||
|
@ -26,7 +28,7 @@ impl Command for SysCpu {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::cpu(call.head).into_pipeline_data())
|
||||
Ok(cpu(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Disks;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysDisks;
|
||||
|
@ -26,7 +28,7 @@ impl Command for SysDisks {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::disks(call.head).into_pipeline_data())
|
||||
Ok(disks(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use super::trim_cstyle_null;
|
||||
use chrono::{DateTime, FixedOffset, Local};
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysHost;
|
||||
|
@ -26,8 +29,7 @@ impl Command for SysHost {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let host = super::host(call.head);
|
||||
Ok(Value::record(host, call.head).into_pipeline_data())
|
||||
Ok(host(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::System;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysMem;
|
||||
|
@ -26,7 +27,7 @@ impl Command for SysMem {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::mem(call.head).into_pipeline_data())
|
||||
Ok(mem(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -16,202 +16,6 @@ pub use sys_::Sys;
|
|||
pub use temp::SysTemp;
|
||||
pub use users::SysUsers;
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Local};
|
||||
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 {
|
||||
fn trim_cstyle_null(s: impl AsRef<str>) -> String {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Networks;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysNet;
|
||||
|
@ -26,7 +28,7 @@ impl Command for SysNet {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::net(call.head).into_pipeline_data())
|
||||
Ok(net(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sys;
|
||||
|
@ -20,41 +20,17 @@ impl Command for Sys {
|
|||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
nu_protocol::report_error_new(
|
||||
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())
|
||||
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Components;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysTemp;
|
||||
|
@ -30,7 +31,7 @@ impl Command for SysTemp {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::temp(call.head).into_pipeline_data())
|
||||
Ok(temp(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::trim_cstyle_null;
|
||||
use nu_engine::command_prelude::*;
|
||||
use sysinfo::Users;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SysUsers;
|
||||
|
@ -25,7 +27,7 @@ impl Command for SysUsers {
|
|||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(super::users(call.head).into_pipeline_data())
|
||||
Ok(users(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -76,6 +76,13 @@ fn into_filesize_wrong_negative_str_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]
|
||||
fn into_filesize_negative_str() {
|
||||
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"));
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn into_filesize_positive_str() {
|
||||
let actual = nu!("'+1' | into filesize");
|
||||
|
|
|
@ -906,7 +906,7 @@ fn test_cp_debug_default() {
|
|||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
if !actual
|
||||
.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));
|
||||
}
|
||||
|
|
|
@ -6,18 +6,36 @@ pub fn home_dir() -> Option<PathBuf> {
|
|||
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> {
|
||||
match std::env::var("XDG_CONFIG_HOME").map(PathBuf::from) {
|
||||
Ok(xdg_config) if xdg_config.is_absolute() => {
|
||||
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 config_dir_old() -> Option<PathBuf> {
|
||||
let path = dirs_next::config_dir()?;
|
||||
pub fn get_canonicalized_path(path: Option<PathBuf>) -> Option<PathBuf> {
|
||||
let path = path?;
|
||||
Some(canonicalize(&path).unwrap_or(path))
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ mod trailing_slash;
|
|||
|
||||
pub use components::components;
|
||||
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 tilde::expand_tilde;
|
||||
pub use trailing_slash::{has_trailing_slash, strip_trailing_slash};
|
||||
|
|
|
@ -10,7 +10,7 @@ pub enum ParseWarning {
|
|||
DeprecatedWarning {
|
||||
old_command: 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,
|
||||
url: String,
|
||||
},
|
||||
|
|
|
@ -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", {
|
||||
let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
|
||||
Value::string(canon_temp_path.to_string_lossy(), span)
|
||||
|
|
|
@ -77,6 +77,7 @@ $env.ENV_CONVERSIONS = {
|
|||
# The default for this is $nu.default-config-dir/scripts
|
||||
$env.NU_LIB_DIRS = [
|
||||
($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
|
||||
|
|
|
@ -35,6 +35,7 @@ impl PluginCommand for ToNu {
|
|||
Some('n'),
|
||||
)
|
||||
.switch("tail", "shows tail rows", Some('t'))
|
||||
.switch("index", "add an index column", Some('i'))
|
||||
.input_output_types(vec![
|
||||
(Type::Custom("expression".into()), Type::Any),
|
||||
(Type::Custom("dataframe".into()), Type::table()),
|
||||
|
@ -62,18 +63,18 @@ impl PluginCommand for ToNu {
|
|||
vec![
|
||||
Example {
|
||||
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())),
|
||||
},
|
||||
Example {
|
||||
description: "Shows tail rows from dataframe",
|
||||
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())),
|
||||
},
|
||||
Example {
|
||||
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! {
|
||||
"expr" => Value::test_string("column"),
|
||||
"value" => Value::test_string("a"),
|
||||
|
@ -106,17 +107,18 @@ fn dataframe_command(
|
|||
) -> Result<PipelineData, ShellError> {
|
||||
let rows: Option<usize> = call.get_flag("rows")?;
|
||||
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 values = if tail {
|
||||
df.tail(rows, call.head)?
|
||||
df.tail(rows, index, call.head)?
|
||||
} else {
|
||||
// if rows is specified, return those rows, otherwise return everything
|
||||
if rows.is_some() {
|
||||
df.head(rows, call.head)?
|
||||
df.head(rows, index, call.head)?
|
||||
} else {
|
||||
df.head(Some(df.height()), call.head)?
|
||||
df.head(Some(df.height()), index, call.head)?
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -326,22 +326,22 @@ impl NuDataFrame {
|
|||
}
|
||||
|
||||
// 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 size: usize = 20;
|
||||
|
||||
if df.height() > size {
|
||||
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);
|
||||
let remaining = df.height() - 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);
|
||||
|
||||
Ok(values)
|
||||
} else {
|
||||
Ok(self.head(Some(size), span)?)
|
||||
Ok(self.head(Some(size), include_index, span)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,26 +349,38 @@ impl NuDataFrame {
|
|||
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 values = self.to_rows(0, to_row, span)?;
|
||||
let values = self.to_rows(0, to_row, include_index, span)?;
|
||||
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 to_row = df.height();
|
||||
let size = rows.unwrap_or(DEFAULT_ROWS);
|
||||
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)
|
||||
}
|
||||
|
||||
/// Converts the dataframe to a nushell list of values
|
||||
pub fn to_rows(
|
||||
&self,
|
||||
from_row: usize,
|
||||
to_row: usize,
|
||||
include_index: bool,
|
||||
span: Span,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let df = &self.df;
|
||||
|
@ -400,7 +412,7 @@ impl NuDataFrame {
|
|||
.map(|i| {
|
||||
let mut record = Record::new();
|
||||
|
||||
if !has_index {
|
||||
if !has_index && include_index {
|
||||
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> {
|
||||
let vals = self.print(span)?;
|
||||
let vals = self.print(true, span)?;
|
||||
Ok(Value::list(vals, span))
|
||||
}
|
||||
|
||||
|
|
|
@ -63,45 +63,16 @@ fn compute_with_value(
|
|||
op: Span,
|
||||
right: &Value,
|
||||
) -> Result<Value, ShellError> {
|
||||
let rhs_span = right.span();
|
||||
match right {
|
||||
Value::Custom { val: rhs, .. } => {
|
||||
let rhs = rhs.as_any().downcast_ref::<NuExpression>().ok_or_else(|| {
|
||||
ShellError::TypeMismatch {
|
||||
err_message: "Right hand side not a dataframe expression".into(),
|
||||
span: rhs_span,
|
||||
}
|
||||
})?;
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
let rhs = NuExpression::try_from_value(plugin, right)?;
|
||||
with_operator(
|
||||
(plugin, engine),
|
||||
operator,
|
||||
left,
|
||||
&rhs,
|
||||
lhs_span,
|
||||
right.span(),
|
||||
op,
|
||||
)
|
||||
}
|
||||
|
||||
fn with_operator(
|
||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -104,7 +104,9 @@ fn main() -> Result<()> {
|
|||
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
|
||||
.read_dir()
|
||||
.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();
|
||||
default_nu_lib_dirs_path.push("scripts");
|
||||
engine_state.add_env_var(
|
||||
"NU_LIB_DIRS".to_string(),
|
||||
Value::test_list(vec![Value::test_string(
|
||||
default_nu_lib_dirs_path.to_string_lossy(),
|
||||
)]),
|
||||
Value::test_list(vec![
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user