nushell/crates/nu-std/std.nu

1431 lines
49 KiB
Plaintext
Raw Normal View History

add `dirs` command to std lib (#8368) # Description Prototype replacement for `enter`, `n`, `p`, `exit` built-ins implemented as scripts in standard library. MVP-level capabilities (rough hack), for feedback please. Not intended to merge and ship as is. _(Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience.)_ # User-Facing Changes New command in standard library ```nushell 〉use ~/src/rust/nushell/crates/nu-utils/standard_library/dirs.nu ---------------------------------------------- /home/bobhy ---------------------------------------------- 〉help dirs module dirs.nu -- maintain list of remembered directories + navigate them todo: * expand relative to absolute paths (or relative to some prefix?) * what if user does `cd` by hand? Module: dirs Exported commands: add (dirs add), drop, next (dirs next), prev (dirs prev), show (dirs show) This module exports environment. ---------------------------------------------- /home/bobhy ---------------------------------------------- 〉dirs add ~/src/rust/nushell /etc ~/.cargo -------------------------------------- /home/bobhy/src/rust/nushell -------------------------------------- 〉dirs next 2 ------------------------------------------- /home/bobhy/.cargo ------------------------------------------- 〉dirs show ╭───┬─────────┬────────────────────╮ │ # │ current │ path │ ├───┼─────────┼────────────────────┤ │ 0 │ │ /home/bobhy │ │ 1 │ │ ~/src/rust/nushell │ │ 2 │ │ /etc │ │ 3 │ ==> │ ~/.cargo │ ╰───┴─────────┴────────────────────╯ ------------------------------------------- /home/bobhy/.cargo ------------------------------------------- 〉dirs drop ---------------------------------------------- /home/bobhy ---------------------------------------------- 〉dirs show ╭───┬─────────┬────────────────────╮ │ # │ current │ path │ ├───┼─────────┼────────────────────┤ │ 0 │ ==> │ /home/bobhy │ │ 1 │ │ ~/src/rust/nushell │ │ 2 │ │ /etc │ ╰───┴─────────┴────────────────────╯ ---------------------------------------------- /home/bobhy ---------------------------------------------- 〉 ``` # Tests + Formatting Haven't even looked at stdlib `tests.nu` yet. Other todos: * address module todos. * integrate into std lib, rather than as standalone module. Somehow arrange for `use .../standard_library/std.nu` to load this module without having to put all the source in `std.nu`? * Maybe command should be `std dirs ...`? * what else do `enter` and `exit` do that this should do? Then deprecate those commands. Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-11 23:31:09 +01:00
# std.nu, `used` to load all standard library components
# Universal assert command
#
# If the condition is not true, it generates an error.
#
# # Example
#
# ```nushell
# >_ assert (3 == 3)
# >_ assert (42 == 3)
# Error:
# × Assertion failed:
# ╭─[myscript.nu:11:1]
# 11 │ assert (3 == 3)
# 12 │ assert (42 == 3)
# · ───┬────
# · ╰── It is not true.
# 13 │
# ╰────
# ```
#
# The --error-label flag can be used if you want to create a custom assert command:
# ```
# def "assert even" [number: int] {
# assert ($number mod 2 == 0) --error-label {
# start: (metadata $number).span.start,
# end: (metadata $number).span.end,
# text: $"($number) is not an even number",
# }
# }
# ```
export def assert [
condition: bool, # Condition, which should be true
message?: string, # Optional error message
--error-label: record # Label for `error make` if you want to create a custom assert
] {
if $condition { return }
let span = (metadata $condition).span
error make {
msg: ($message | default "Assertion failed."),
label: ($error_label | default {
text: "It is not true.",
start: (metadata $condition).span.start,
end: (metadata $condition).span.end
})
}
}
# Assert that executing the code generates an error
#
# For more documentation see the assert command
#
# # Examples
#
# > assert error {|| missing_command} # passes
# > assert error {|| 12} # fails
export def "assert error" [
code: closure,
message?: string
] {
let error_raised = (try { do $code; false } catch { true })
assert ($error_raised) $message --error-label {
start: (metadata $code).span.start
end: (metadata $code).span.end
text: $"There were no error during code execution: (view source $code)"
}
}
# Skip the current test case
#
# # Examples
#
# if $condition { assert skip }
export def "assert skip" [] {
error make {msg: "ASSERT:SKIP"}
}
# Assert $left == $right
#
# For more documentation see the assert command
#
# # Examples
#
# > assert equal 1 1 # passes
# > assert equal (0.1 + 0.2) 0.3
# > assert equal 1 2 # fails
export def "assert equal" [left: any, right: any, message?: string] {
assert ($left == $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"They are not equal. Left = ($left). Right = ($right)."
}
}
# Assert $left != $right
#
# For more documentation see the assert command
#
# # Examples
#
# > assert not equal 1 2 # passes
# > assert not equal 1 "apple" # passes
# > assert not equal 7 7 # fails
export def "assert not equal" [left: any, right: any, message?: string] {
assert ($left != $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"They both are ($left)."
}
}
# Assert $left <= $right
#
# For more documentation see the assert command
#
# # Examples
#
# > assert less or equal 1 2 # passes
# > assert less or equal 1 1 # passes
# > assert less or equal 1 0 # fails
export def "assert less or equal" [left: any, right: any, message?: string] {
assert ($left <= $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"Left: ($left), Right: ($right)"
}
}
# Assert $left < $right
#
# For more documentation see the assert command
#
# # Examples
#
# > assert less 1 2 # passes
# > assert less 1 1 # fails
export def "assert less" [left: any, right: any, message?: string] {
assert ($left < $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"Left: ($left), Right: ($right)"
}
}
# Assert $left > $right
#
# For more documentation see the assert command
#
# # Examples
#
# > assert greater 2 1 # passes
# > assert greater 2 2 # fails
export def "assert greater" [left: any, right: any, message?: string] {
assert ($left > $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"Left: ($left), Right: ($right)"
}
}
# Assert $left >= $right
#
# For more documentation see the assert command
#
# # Examples
#
# > assert greater or equal 2 1 # passes
# > assert greater or equal 2 2 # passes
# > assert greater or equal 1 2 # fails
export def "assert greater or equal" [left: any, right: any, message?: string] {
assert ($left >= $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"Left: ($left), Right: ($right)"
}
}
# Assert length of $left is $right
#
# For more documentation see the assert command
#
# # Examples
#
# > assert length [0, 0] 2 # passes
# > assert length [0] 3 # fails
export def "assert length" [left: list, right: int, message?: string] {
assert (($left | length) == $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"Length of ($left) is ($left | length), not ($right)"
}
}
# Assert that ($left | str contains $right)
#
# For more documentation see the assert command
#
# # Examples
#
# > assert str contains "arst" "rs" # passes
# > assert str contains "arst" "k" # fails
export def "assert str contains" [left: string, right: string, message?: string] {
assert ($left | str contains $right) $message --error-label {
start: (metadata $left).span.start
end: (metadata $right).span.end
text: $"'($left)' does not contain '($right)'."
}
}
# Add the given paths to the PATH.
#
# # Example
# - adding some dummy paths to an empty PATH
# ```nushell
# >_ with-env [PATH []] {
# std path add "foo"
# std path add "bar" "baz"
# std path add "fooo" --append
#
# assert equal $env.PATH ["bar" "baz" "foo" "fooo"]
#
# print (std path add "returned" --ret)
# }
# ╭───┬──────────╮
# │ 0 │ returned │
# │ 1 │ bar │
# │ 2 │ baz │
# │ 3 │ foo │
# │ 4 │ fooo │
# ╰───┴──────────╯
# ```
export def-env "path add" [
--ret (-r) # return $env.PATH, useful in pipelines to avoid scoping.
--append (-a) # append to $env.PATH instead of prepending to.
...paths # the paths to add to $env.PATH.
] {
let-env PATH = (
$env.PATH
| if $append { append $paths }
else { prepend $paths }
)
if $ret {
$env.PATH
}
}
# Maintain a list of working directories and navigates them
# the directory stack
export-env {
let-env DIRS_POSITION = 0
let-env DIRS_LIST = [($env.PWD | path expand)]
}
# Add one or more directories to the list.
# PWD becomes first of the newly added directories.
export def-env "dirs add" [
...paths: string # directory or directories to add to working list
] {
mut abspaths = []
for p in $paths {
let exp = ($p | path expand)
if ($exp | path type) != 'dir' {
let span = (metadata $p).span
error make {msg: "not a directory", label: {text: "not a directory", start: $span.start, end: $span.end } }
}
$abspaths = ($abspaths | append $exp)
}
let-env DIRS_LIST = ($env.DIRS_LIST | insert ($env.DIRS_POSITION + 1) $abspaths | flatten)
let-env DIRS_POSITION = $env.DIRS_POSITION + 1
_fetch 0
}
# Advance to the next directory in the list or wrap to beginning.
export def-env "dirs next" [
N:int = 1 # number of positions to move.
] {
_fetch $N
}
# Back up to the previous directory or wrap to the end.
export def-env "dirs prev" [
N:int = 1 # number of positions to move.
] {
_fetch (-1 * $N)
}
# Drop the current directory from the list, if it's not the only one.
# PWD becomes the next working directory
export def-env "dirs drop" [] {
if ($env.DIRS_LIST | length) > 1 {
let-env DIRS_LIST = (
($env.DIRS_LIST | take $env.DIRS_POSITION)
| append ($env.DIRS_LIST | skip ($env.DIRS_POSITION + 1))
)
}
_fetch 0
}
# Display current working directories.
export def-env "dirs show" [] {
mut out = []
for $p in ($env.DIRS_LIST | enumerate) {
$out = ($out | append [
[active, path];
[($p.index == $env.DIRS_POSITION), $p.item]
])
}
$out
}
# fetch item helper
def-env _fetch [
offset: int, # signed change to position
] {
# nushell 'mod' operator is really 'remainder', can return negative values.
# see: https://stackoverflow.com/questions/13683563/whats-the-difference-between-mod-and-remainder
let pos = ($env.DIRS_POSITION
+ $offset
+ ($env.DIRS_LIST | length)
) mod ($env.DIRS_LIST | length)
let-env DIRS_POSITION = $pos
cd ($env.DIRS_LIST | get $pos )
}
def CRITICAL_LEVEL [] { 50 }
def ERROR_LEVEL [] { 40 }
def WARNING_LEVEL [] { 30 }
def INFO_LEVEL [] { 20 }
def DEBUG_LEVEL [] { 10 }
def parse-string-level [level: string] {
(
if $level == "CRITICAL" { (CRITICAL_LEVEL)}
else if $level == "CRIT" { (CRITICAL_LEVEL)}
else if $level == "ERROR" { (ERROR_LEVEL) }
else if $level == "WARNING" { (WARNING_LEVEL) }
else if $level == "WARN" { (WARNING_LEVEL) }
else if $level == "INFO" { (INFO_LEVEL) }
else if $level == "DEBUG" { (DEBUG_LEVEL) }
else { (INFO_LEVEL) }
)
}
def current-log-level [] {
let env_level = ($env | get -i NU_LOG_LEVEL | default (INFO_LEVEL))
try {
($env_level | into int)
} catch {
parse-string-level $env_level
}
}
tweak logging format (#8588) # Description This PR just tweaks the std.nu logging a bit. It looks like this after this PR. I like the ability to have a parse-able file, which is why there are pipes, and I like to have a pretty granular time date stamp in order to get rough performance metrics. ``` nu crates\nu-utils\standard_library\tests.nu INF|2023-03-23T15:02:00.284|Run test test_asserts test_assert INF|2023-03-23T15:02:00.372|Run test test_asserts test_assert_equal INF|2023-03-23T15:02:00.461|Run test test_asserts test_assert_error INF|2023-03-23T15:02:00.585|Run test test_asserts test_assert_greater INF|2023-03-23T15:02:00.674|Run test test_asserts test_assert_greater_or_equal INF|2023-03-23T15:02:00.762|Run test test_asserts test_assert_length INF|2023-03-23T15:02:00.847|Run test test_asserts test_assert_less INF|2023-03-23T15:02:00.933|Run test test_asserts test_assert_less_or_equal INF|2023-03-23T15:02:01.021|Run test test_asserts test_assert_not_equal INF|2023-03-23T15:02:01.110|Run test test_dirs test_dirs_command INF|2023-03-23T15:02:01.300|Run test test_logger test_critical INF|2023-03-23T15:02:01.558|Run test test_logger test_debug INF|2023-03-23T15:02:01.818|Run test test_logger test_error INF|2023-03-23T15:02:02.074|Run test test_logger test_info INF|2023-03-23T15:02:02.331|Run test test_logger test_warning INF|2023-03-23T15:02:02.573|Run test test_std test_match INF|2023-03-23T15:02:02.678|Run test test_std test_path_add ``` # User-Facing Changes _(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_ # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-23 21:59:49 +01:00
def now [] {
date now | date format "%Y-%m-%dT%H:%M:%S%.3f"
}
# Log critical message
export def "log critical" [message: string] {
if (current-log-level) > (CRITICAL_LEVEL) { return }
tweak logging format (#8588) # Description This PR just tweaks the std.nu logging a bit. It looks like this after this PR. I like the ability to have a parse-able file, which is why there are pipes, and I like to have a pretty granular time date stamp in order to get rough performance metrics. ``` nu crates\nu-utils\standard_library\tests.nu INF|2023-03-23T15:02:00.284|Run test test_asserts test_assert INF|2023-03-23T15:02:00.372|Run test test_asserts test_assert_equal INF|2023-03-23T15:02:00.461|Run test test_asserts test_assert_error INF|2023-03-23T15:02:00.585|Run test test_asserts test_assert_greater INF|2023-03-23T15:02:00.674|Run test test_asserts test_assert_greater_or_equal INF|2023-03-23T15:02:00.762|Run test test_asserts test_assert_length INF|2023-03-23T15:02:00.847|Run test test_asserts test_assert_less INF|2023-03-23T15:02:00.933|Run test test_asserts test_assert_less_or_equal INF|2023-03-23T15:02:01.021|Run test test_asserts test_assert_not_equal INF|2023-03-23T15:02:01.110|Run test test_dirs test_dirs_command INF|2023-03-23T15:02:01.300|Run test test_logger test_critical INF|2023-03-23T15:02:01.558|Run test test_logger test_debug INF|2023-03-23T15:02:01.818|Run test test_logger test_error INF|2023-03-23T15:02:02.074|Run test test_logger test_info INF|2023-03-23T15:02:02.331|Run test test_logger test_warning INF|2023-03-23T15:02:02.573|Run test test_std test_match INF|2023-03-23T15:02:02.678|Run test test_std test_path_add ``` # User-Facing Changes _(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_ # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-23 21:59:49 +01:00
print --stderr $"(ansi red_bold)CRT|(now)|($message)(ansi reset)"
}
# Log error message
export def "log error" [message: string] {
if (current-log-level) > (ERROR_LEVEL) { return }
tweak logging format (#8588) # Description This PR just tweaks the std.nu logging a bit. It looks like this after this PR. I like the ability to have a parse-able file, which is why there are pipes, and I like to have a pretty granular time date stamp in order to get rough performance metrics. ``` nu crates\nu-utils\standard_library\tests.nu INF|2023-03-23T15:02:00.284|Run test test_asserts test_assert INF|2023-03-23T15:02:00.372|Run test test_asserts test_assert_equal INF|2023-03-23T15:02:00.461|Run test test_asserts test_assert_error INF|2023-03-23T15:02:00.585|Run test test_asserts test_assert_greater INF|2023-03-23T15:02:00.674|Run test test_asserts test_assert_greater_or_equal INF|2023-03-23T15:02:00.762|Run test test_asserts test_assert_length INF|2023-03-23T15:02:00.847|Run test test_asserts test_assert_less INF|2023-03-23T15:02:00.933|Run test test_asserts test_assert_less_or_equal INF|2023-03-23T15:02:01.021|Run test test_asserts test_assert_not_equal INF|2023-03-23T15:02:01.110|Run test test_dirs test_dirs_command INF|2023-03-23T15:02:01.300|Run test test_logger test_critical INF|2023-03-23T15:02:01.558|Run test test_logger test_debug INF|2023-03-23T15:02:01.818|Run test test_logger test_error INF|2023-03-23T15:02:02.074|Run test test_logger test_info INF|2023-03-23T15:02:02.331|Run test test_logger test_warning INF|2023-03-23T15:02:02.573|Run test test_std test_match INF|2023-03-23T15:02:02.678|Run test test_std test_path_add ``` # User-Facing Changes _(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_ # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-23 21:59:49 +01:00
print --stderr $"(ansi red)ERR|(now)|($message)(ansi reset)"
}
# Log warning message
export def "log warning" [message: string] {
if (current-log-level) > (WARNING_LEVEL) { return }
tweak logging format (#8588) # Description This PR just tweaks the std.nu logging a bit. It looks like this after this PR. I like the ability to have a parse-able file, which is why there are pipes, and I like to have a pretty granular time date stamp in order to get rough performance metrics. ``` nu crates\nu-utils\standard_library\tests.nu INF|2023-03-23T15:02:00.284|Run test test_asserts test_assert INF|2023-03-23T15:02:00.372|Run test test_asserts test_assert_equal INF|2023-03-23T15:02:00.461|Run test test_asserts test_assert_error INF|2023-03-23T15:02:00.585|Run test test_asserts test_assert_greater INF|2023-03-23T15:02:00.674|Run test test_asserts test_assert_greater_or_equal INF|2023-03-23T15:02:00.762|Run test test_asserts test_assert_length INF|2023-03-23T15:02:00.847|Run test test_asserts test_assert_less INF|2023-03-23T15:02:00.933|Run test test_asserts test_assert_less_or_equal INF|2023-03-23T15:02:01.021|Run test test_asserts test_assert_not_equal INF|2023-03-23T15:02:01.110|Run test test_dirs test_dirs_command INF|2023-03-23T15:02:01.300|Run test test_logger test_critical INF|2023-03-23T15:02:01.558|Run test test_logger test_debug INF|2023-03-23T15:02:01.818|Run test test_logger test_error INF|2023-03-23T15:02:02.074|Run test test_logger test_info INF|2023-03-23T15:02:02.331|Run test test_logger test_warning INF|2023-03-23T15:02:02.573|Run test test_std test_match INF|2023-03-23T15:02:02.678|Run test test_std test_path_add ``` # User-Facing Changes _(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_ # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-23 21:59:49 +01:00
print --stderr $"(ansi yellow)WRN|(now)|($message)(ansi reset)"
}
# Log info message
export def "log info" [message: string] {
if (current-log-level) > (INFO_LEVEL) { return }
print --stderr $"(ansi default)INF|(now)|($message)(ansi reset)"
}
# Log debug message
export def "log debug" [message: string] {
if (current-log-level) > (DEBUG_LEVEL) { return }
tweak logging format (#8588) # Description This PR just tweaks the std.nu logging a bit. It looks like this after this PR. I like the ability to have a parse-able file, which is why there are pipes, and I like to have a pretty granular time date stamp in order to get rough performance metrics. ``` nu crates\nu-utils\standard_library\tests.nu INF|2023-03-23T15:02:00.284|Run test test_asserts test_assert INF|2023-03-23T15:02:00.372|Run test test_asserts test_assert_equal INF|2023-03-23T15:02:00.461|Run test test_asserts test_assert_error INF|2023-03-23T15:02:00.585|Run test test_asserts test_assert_greater INF|2023-03-23T15:02:00.674|Run test test_asserts test_assert_greater_or_equal INF|2023-03-23T15:02:00.762|Run test test_asserts test_assert_length INF|2023-03-23T15:02:00.847|Run test test_asserts test_assert_less INF|2023-03-23T15:02:00.933|Run test test_asserts test_assert_less_or_equal INF|2023-03-23T15:02:01.021|Run test test_asserts test_assert_not_equal INF|2023-03-23T15:02:01.110|Run test test_dirs test_dirs_command INF|2023-03-23T15:02:01.300|Run test test_logger test_critical INF|2023-03-23T15:02:01.558|Run test test_logger test_debug INF|2023-03-23T15:02:01.818|Run test test_logger test_error INF|2023-03-23T15:02:02.074|Run test test_logger test_info INF|2023-03-23T15:02:02.331|Run test test_logger test_warning INF|2023-03-23T15:02:02.573|Run test test_std test_match INF|2023-03-23T15:02:02.678|Run test test_std test_path_add ``` # User-Facing Changes _(List of all changes that impact the user experience here. This helps us keep track of breaking changes.)_ # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-23 21:59:49 +01:00
print --stderr $"(ansi default_dimmed)DBG|(now)|($message)(ansi reset)"
}
# Utility functions to read, change and create XML data in format supported
# by `to xml` and `from xml` commands
# Get all xml entries matching simple xpath-inspired query
export def xaccess [
path: list # List of steps. Each step can be a
# 1. String with tag name. Finds all children with specified name. Equivalent to `child::A` in xpath
# 2. `*` string. Get all children without any filter. Equivalent to `descendant` in xpath
# 3. Int. Select n-th among nodes selected by previous path. Equivalent to `(...)[1]` in xpath, but is indexed from 0.
# 4. Closure. Predicate accepting entry. Selects all entries among nodes selected by previous path for which predicate returns true.
] {
let input = $in
if ($path | is-empty) {
let path_span = (metadata $path).span
error make {msg: 'Empty path provided'
label: {text: 'Use a non-empty list of path steps'
start: $path_span.start end: $path_span.end}}
}
# In xpath first element in path is applied to root element
# this way it is possible to apply first step to root element
# of nu xml without unrolling one step of loop
mut values = ()
$values = {content: [ { content: $input } ] }
for $step in ($path) {
match ($step | describe) {
'string' => {
if $step == '*' {
$values = ($values.content | flatten)
} else {
$values = ($values.content | flatten | where tag == $step)
}
},
'int' => {
$values = [ ($values | get $step) ]
},
'closure' => {
$values = ($values | where {|x| do $step $x})
},
$type => {
let step_span = (metadata $step).span
error make {msg: $'Incorrect path step type ($type)'
label: {text: 'Use a string or int as a step'
start: $step_span.start end: $step_span.end}}
}
}
if ($values | is-empty) {
return []
}
}
$values
}
def xupdate-string-step [ step: string rest: list updater: closure ] {
let input = $in
# Get a list of elements to be updated and their indices
let to_update = ($input.content | enumerate | filter {|it|
let item = $it.item
$step == '*' or $item.tag == $step
})
if ($to_update | is-empty) {
return $input
}
let new_values = ($to_update.item | xupdate-internal $rest $updater)
mut reenumerated_new_values = ($to_update.index | zip $new_values | each {|x| {index: $x.0 item: $x.1}})
mut new_content = []
for it in ($input.content | enumerate) {
let item = $it.item
let idx = $it.index
let next = (if (not ($reenumerated_new_values | is-empty)) and $idx == $reenumerated_new_values.0.index {
let tmp = $reenumerated_new_values.0
$reenumerated_new_values = ($reenumerated_new_values | skip 1)
$tmp.item
} else {
$item
})
$new_content = ($new_content | append $next)
}
{tag: $input.tag attributes: $input.attributes content: $new_content}
}
def xupdate-int-step [ step: int rest: list updater: closure ] {
$in | enumerate | each {|it|
let item = $it.item
let idx = $it.index
if $idx == $step {
[ $item ] | xupdate-internal $rest $updater | get 0
} else {
$item
}
}
}
def xupdate-closure-step [ step: closure rest: list updater: closure ] {
$in | each {|it|
if (do $step $it) {
[ $it ] | xupdate-internal $rest $updater | get 0
} else {
$it
}
}
}
def xupdate-internal [ path: list updater: closure ] {
let input = $in
if ($path | is-empty) {
$input | each $updater
} else {
let step = $path.0
let rest = ($path | skip 1)
match ($step | describe) {
'string' => {
$input | each {|x| $x | xupdate-string-step $step $rest $updater}
},
'int' => {
$input | xupdate-int-step $step $rest $updater
},
'closure' => {
$input | xupdate-closure-step $step $rest $updater
},
$type => {
let step_span = (metadata $step).span
error make {msg: $'Incorrect path step type ($type)'
label: {text: 'Use a string or int as a step'
start: $step_span.start end: $step_span.end}}
}
}
}
}
# Update xml data entries matching simple xpath-inspired query
export def xupdate [
path: list # List of steps. Each step can be a
# 1. String with tag name. Finds all children with specified name. Equivalent to `child::A` in xpath
# 2. `*` string. Get all children without any filter. Equivalent to `descendant` in xpath
# 3. Int. Select n-th among nodes selected by previous path. Equivalent to `(...)[1]` in xpath, but is indexed from 0.
# 4. Closure. Predicate accepting entry. Selects all entries among nodes selected by previous path for which predicate returns true.
updater: closure # A closure used to transform entries matching path.
] {
{tag:? attributes:? content: [$in]} | xupdate-internal $path $updater | get content.0
}
# Get type of an xml entry
#
# Possible types are 'tag', 'text', 'pi' and 'comment'
export def xtype [] {
let input = $in
if (($input | describe) == 'string' or
($input.tag? == null and $input.attributes? == null and ($input.content? | describe) == 'string')) {
'text'
} else if $input.tag? == '!' {
'comment'
} else if $input.tag? != null and ($input.tag? | str starts-with '?') {
'pi'
} else if $input.tag? != null {
'tag'
} else {
error make {msg: 'Not an xml emtry. Check valid types of xml entries via `help to xml`'}
}
}
# Insert new entry to elements matching simple xpath-inspired query
export def xinsert [
path: list # List of steps. Each step can be a
# 1. String with tag name. Finds all children with specified name. Equivalent to `child::A` in xpath
# 2. `*` string. Get all children without any filter. Equivalent to `descendant` in xpath
# 3. Int. Select n-th among nodes selected by previous path. Equivalent to `(...)[1]` in xpath, but is indexed from 0.
# 4. Closure. Predicate accepting entry. Selects all entries among nodes selected by previous path for which predicate returns true.
new_entry: record # A new entry to insert into `content` field of record at specified position
position?: int # Position to insert `new_entry` into. If specified inserts entry at given position (or end if
# position is greater than number of elements) in content of all entries of input matched by
# path. If not specified inserts at the end.
] {
$in | xupdate $path {|entry|
match ($entry | xtype) {
'tag' => {
let new_content = if $position == null {
$entry.content | append $new_entry
} else {
let position = if $position > ($entry.content | length) {
$entry.content | length
} else {
$position
}
$entry.content | insert $position $new_entry
}
{tag: $entry.tag attributes: $entry.attributes content: $new_content}
},
_ => (error make {msg: 'Can insert entry only into content of a tag node'})
}
}
}
# print a command name as dimmed and italic
def pretty-command [] {
let command = $in
return $"(ansi default_dimmed)(ansi default_italic)($command)(ansi reset)"
}
# give a hint error when the clip command is not available on the system
def check-clipboard [
clipboard: string # the clipboard command name
--system: string # some information about the system running, for better error
] {
if (which $clipboard | is-empty) {
error make --unspanned {
msg: $"(ansi red)clipboard_not_found(ansi reset):
you are running ($system)
but
the ($clipboard | pretty-command) clipboard command was not found on your system."
}
}
}
# put the end of a pipe into the system clipboard.
#
# Dependencies:
# - xclip on linux x11
# - wl-copy on linux wayland
# - clip.exe on windows
#
# Examples:
# put a simple string to the clipboard, will be stripped to remove ANSI sequences
# >_ "my wonderful string" | clip
# my wonderful string
# saved to clipboard (stripped)
#
# put a whole table to the clipboard
# >_ ls *.toml | clip
# ╭───┬─────────────────────┬──────┬────────┬───────────────╮
# │ # │ name │ type │ size │ modified │
# ├───┼─────────────────────┼──────┼────────┼───────────────┤
# │ 0 │ Cargo.toml │ file │ 5.0 KB │ 3 minutes ago │
# │ 1 │ Cross.toml │ file │ 363 B │ 2 weeks ago │
# │ 2 │ rust-toolchain.toml │ file │ 1.1 KB │ 2 weeks ago │
# ╰───┴─────────────────────┴──────┴────────┴───────────────╯
#
# saved to clipboard
#
# put huge structured data in the clipboard, but silently
# >_ open Cargo.toml --raw | from toml | clip --silent
#
# when the clipboard system command is not installed
# >_ "mm this is fishy..." | clip
# Error:
# × clipboard_not_found:
# │ you are using xorg on linux
# │ but
# │ the xclip clipboard command was not found on your system.
export def clip [
--silent: bool # do not print the content of the clipboard to the standard output
--no-notify: bool # do not throw a notification (only on linux)
] {
let input = $in
let input = if ($input | describe) == "string" {
$input | ansi strip
} else { $input }
match $nu.os-info.name {
"linux" => {
if ($env.WAYLAND_DISPLAY? | is-empty) {
check-clipboard xclip --system $"('xorg' | pretty-command) on linux"
$input | xclip -sel clip
} else {
check-clipboard wl-copy --system $"('wayland' | pretty-command) on linux"
$input | wl-copy
}
},
"windows" => {
chcp 65001 # see https://discord.com/channels/601130461678272522/601130461678272524/1085535756237426778
check-clipboard clip.exe --system $"('xorg' | pretty-command) on linux"
$input | clip.exe
},
"macos" => {
check-clipboard pbcopy --system macOS
$input | pbcopy
},
_ => {
error make --unspanned {
msg: $"(ansi red)unknown_operating_system(ansi reset):
'($nu.os-info.name)' is not supported by the ('clip' | pretty-command) command.
please open a feature request in the [issue tracker](char lparen)https://github.com/nushell/nushell/issues/new/choose(char rparen) to add your operating system to the standard library."
}
},
}
if not $silent {
print $input
print --no-newline $"(ansi white_italic)(ansi white_dimmed)saved to clipboard"
if ($input | describe) == "string" {
print " (stripped)"
}
print --no-newline $"(ansi reset)"
}
if (not $no_notify) and ($nu.os-info.name == linux) {
notify-send "std clip" "saved to clipboard"
}
}
def error-fmt [] {
$"(ansi red)($in)(ansi reset)"
}
def throw-error [error: string, msg: string, span: record] {
error make {
msg: ($error | error-fmt)
label: {
text: $msg
start: $span.start
end: $span.end
}
}
}
def module-not-found-error [span: record] {
throw-error "std::help::module_not_found" "module not found" $span
}
def alias-not-found-error [span: record] {
throw-error "std::help::alias_not_found" "alias not found" $span
}
def extern-not-found-error [span: record] {
throw-error "std::help::extern_not_found" "extern not found" $span
}
def operator-not-found-error [span: record] {
throw-error "std::help::operator_not_found" "operator not found" $span
}
def command-not-found-error [span: record] {
throw-error "std::help::command_not_found" "command not found" $span
}
def print-help-header [
text: string
--no-newline (-n): bool
] {
let header = $"(ansi green)($text)(ansi reset):"
if $no_newline {
print -n $header
} else {
print $header
}
}
def show-module [module: record] {
if not ($module.usage? | is-empty) {
print $module.usage
print ""
}
print-help-header -n "Module"
print $" ($module.name)"
print ""
if not ($module.commands? | is-empty) {
print-help-header "Exported commands"
print -n " "
let commands_string = (
$module.commands
| each {|command|
$"($command) (char lparen)($module.name) ($command)(char rparen)"
}
| str join ", "
)
print $commands_string
print ""
}
if not ($module.aliases? | is-empty) {
print-help-header -n "Exported aliases:"
print $module.aliases
print ""
}
if ($module.env_block? | is-empty) {
print $"This module (ansi cyan)does not export(ansi reset) environment."
} else {
print $"This module (ansi cyan)exports(ansi reset) environment."
view source $module.env_block
}
}
# Show help on nushell modules.
#
# When requesting help for a single module, its commands and aliases will be highlighted if they
# are also available in the current scope. Commands/aliases that were imported under a different name
# (such as with a prefix after `use some-module`) will be highlighted in parentheses.
#
# Examples:
# > let us define some example modules to play with
# > ```nushell
# > # my foo module
# > module foo {
# > def bar [] { "foo::bar" }
# > export def baz [] { "foo::baz" }
# >
# > export-env {
# > let-env FOO = "foo::FOO"
# > }
# > }
# >
# > # my bar module
# > module bar {
# > def bar [] { "bar::bar" }
# > export def baz [] { "bar::baz" }
# >
# > export-env {
# > let-env BAR = "bar::BAR"
# > }
# > }
# >
# > # my baz module
# > module baz {
# > def foo [] { "baz::foo" }
# > export def bar [] { "baz::bar" }
# >
# > export-env {
# > let-env BAZ = "baz::BAZ"
# > }
# > }
# > ```
#
# show all aliases
# > help modules
# ╭───┬──────┬──────────┬────────────────┬──────────────┬───────────────╮
# │ # │ name │ commands │ aliases │ env_block │ usage │
# ├───┼──────┼──────────┼────────────────┼──────────────┼───────────────┤
# │ 0 │ bar │ [baz] │ [list 0 items] │ <Block 1331> │ my bar module │
# │ 1 │ baz │ [bar] │ [list 0 items] │ <Block 1335> │ my baz module │
# │ 2 │ foo │ [baz] │ [list 0 items] │ <Block 1327> │ my foo module │
# ╰───┴──────┴──────────┴────────────────┴──────────────┴───────────────╯
#
# search for string in module names
# > help modules --find ba
# ╭───┬──────┬─────────────┬────────────────┬──────────────┬───────────────╮
# │ # │ name │ commands │ aliases │ env_block │ usage │
# ├───┼──────┼─────────────┼────────────────┼──────────────┼───────────────┤
# │ 0 │ bar │ ╭───┬─────╮ │ [list 0 items] │ <Block 1331> │ my bar module │
# │ │ │ │ 0 │ baz │ │ │ │ │
# │ │ │ ╰───┴─────╯ │ │ │ │
# │ 1 │ baz │ ╭───┬─────╮ │ [list 0 items] │ <Block 1335> │ my baz module │
# │ │ │ │ 0 │ bar │ │ │ │ │
# │ │ │ ╰───┴─────╯ │ │ │ │
# ╰───┴──────┴─────────────┴────────────────┴──────────────┴───────────────╯
#
# search help for single module
# > help modules foo
# my foo module
#
# Module: foo
#
# Exported commands:
# baz [foo baz]
#
# This module exports environment.
# {
# let-env FOO = "foo::FOO"
# }
#
# search for a module that does not exist
# > help modules "does not exist"
# Error:
# × std::help::module_not_found
# ╭─[entry #21:1:1]
# 1 │ help modules "does not exist"
# · ────────┬───────
# · ╰── module not found
# ╰────
export def "help modules" [
...module: string # the name of module to get help on
--find (-f): string # string to find in module names
] {
let modules = ($nu.scope.modules | sort-by name)
let module = ($module | str join " ")
if not ($find | is-empty) {
let found_modules = ($modules | find $find)
if ($found_modules | length) == 1 {
show-module ($found_modules | get 0)
} else {
$found_modules
}
} else if not ($module | is-empty) {
let found_module = ($modules | where name == $module)
if ($found_module | is-empty) {
module_not_found_error (metadata $module | get span)
}
show-module ($found_module | get 0)
} else {
$modules
}
}
def show-alias [alias: record] {
if not ($alias.usage? | is-empty) {
print $alias.usage
print ""
}
print-help-header -n "Alias"
print $" ($alias.name)"
print ""
print-help-header "Expansion"
print $" ($alias.expansion)"
}
# Show help on nushell aliases.
#
# Examples:
# > let us define a bunch of aliases
# > ```nushell
# > # my foo alias
# > old-alias foo = echo "this is foo"
# >
# > # my bar alias
# > old-alias bar = echo "this is bar"
# >
# > # my baz alias
# > old-alias baz = echo "this is baz"
# >
# > # a multiline alias
# > old-alias multi = echo "this
# > is
# > a
# > multiline
# > string"
# > ```
#
# show all aliases
# > help aliases
# ╭───┬───────┬────────────────────┬───────────────────╮
# │ # │ name │ expansion │ usage │
# ├───┼───────┼────────────────────┼───────────────────┤
# │ 0 │ bar │ echo "this is bar" │ my bar alias │
# │ 1 │ baz │ echo "this is baz" │ my baz alias │
# │ 2 │ foo │ echo "this is foo" │ my foo alias │
# │ 3 │ multi │ echo "this │ a multiline alias │
# │ │ │ is │ │
# │ │ │ a │ │
# │ │ │ multiline │ │
# │ │ │ string" │ │
# ╰───┴───────┴────────────────────┴───────────────────╯
#
# search for string in alias names
# > help aliases --find ba
# ╭───┬──────┬────────────────────┬──────────────╮
# │ # │ name │ expansion │ usage │
# ├───┼──────┼────────────────────┼──────────────┤
# │ 0 │ bar │ echo "this is bar" │ my bar alias │
# │ 1 │ baz │ echo "this is baz" │ my baz alias │
# ╰───┴──────┴────────────────────┴──────────────╯
#
# search help for single alias
# > help aliases multi
# a multiline alias
#
# Alias: multi
#
# Expansion:
# echo "this
# is
# a
# multiline
# string"
#
# search for an alias that does not exist
# > help aliases "does not exist"
# Error:
# × std::help::alias_not_found
# ╭─[entry #21:1:1]
# 1 │ help aliases "does not exist"
# · ────────┬───────
# · ╰── alias not found
# ╰────
export def "help aliases" [
...alias: string # the name of alias to get help on
--find (-f): string # string to find in alias names
] {
let aliases = ($nu.scope.aliases | sort-by name)
let alias = ($alias | str join " ")
if not ($find | is-empty) {
let found_aliases = ($aliases | find $find)
if ($found_aliases | length) == 1 {
show-alias ($found_aliases | get 0)
} else {
$found_aliases
}
} else if not ($alias | is-empty) {
let found_alias = ($aliases | where name == $alias)
if ($found_alias | is-empty) {
alias-not-found-error (metadata $alias | get span)
}
show-alias ($found_alias | get 0)
} else {
$aliases
}
}
def show-extern [extern: record] {
if not ($extern.usage? | is-empty) {
print $extern.usage
print ""
}
print-help-header -n "Extern"
print $" ($extern.name)"
}
# Show help on nushell externs.
export def "help externs" [
...extern: string # the name of extern to get help on
--find (-f): string # string to find in extern names
] {
let externs = (
$nu.scope.commands
| where is_extern
| select name module_name usage
| sort-by name
| str trim
)
let extern = ($extern | str join " ")
if not ($find | is-empty) {
let found_externs = ($externs | find $find)
if ($found_externs | length) == 1 {
show-extern ($found_externs | get 0)
} else {
$found_externs
}
} else if not ($extern | is-empty) {
let found_extern = ($externs | where name == $extern)
if ($found_extern | is-empty) {
extern-not-found-error (metadata $extern | get span)
}
show-extern ($found_extern | get 0)
} else {
$externs
}
}
def show-operator [operator: record] {
print-help-header "Description"
print $" ($operator.description)"
print ""
print-help-header -n "Operator"
print ($" ($operator.name) (char lparen)(ansi cyan_bold)($operator.operator)(ansi reset)(char rparen)")
print-help-header -n "Type"
print $" ($operator.type)"
print-help-header -n "Precedence"
print $" ($operator.precedence)"
}
# Show help on nushell operators.
#
# Examples:
# search for string in operators names
# > help operators --find Bit
# ╭───┬─────────┬──────────┬────────┬───────────────────────────────────────┬────────────╮
# │ # │ type │ operator │ name │ description │ precedence │
# ├───┼─────────┼──────────┼────────┼───────────────────────────────────────┼────────────┤
# │ 0 │ Bitwise │ bit-and │ BitAnd │ Performs a bitwise AND on two values. │ 75 │
# │ 1 │ Bitwise │ bit-or │ BitOr │ Performs a bitwise OR on two values. │ 60 │
# │ 2 │ Bitwise │ bit-xor │ BitXor │ Performs a bitwise XOR on two values. │ 70 │
# ╰───┴─────────┴──────────┴────────┴───────────────────────────────────────┴────────────╯
#
# search help for single operator
# > help operators NotRegexMatch
# Description:
# Checks if a value does not match a regular expression.
#
# Operator: NotRegexMatch (!~)
# Type: Comparison
# Precedence: 80
#
# search for an operator that does not exist
# > help operator "does not exist"
# Error:
# × std::help::operator_not_found
# ╭─[entry #21:1:1]
# 1 │ help operator "does not exist"
# · ────────┬───────
# · ╰── operator not found
# ╰────
export def "help operators" [
...operator: string # the name of operator to get help on
--find (-f): string # string to find in operator names
] {
let operators = ([
[type, operator, name, description, precedence];
[Assignment, =, Assign, "Assigns a value to a variable.", 10]
[Assignment, +=, PlusAssign, "Adds a value to a variable.", 10]
[Assignment, ++=, AppendAssign, "Appends a list or a value to a variable.", 10]
[Assignment, -=, MinusAssign, "Subtracts a value from a variable.", 10]
[Assignment, *=, MultiplyAssign, "Multiplies a variable by a value.", 10]
[Assignment, /=, DivideAssign, "Divides a variable by a value.", 10]
[Comparison, ==, Equal, "Checks if two values are equal.", 80]
[Comparison, !=, NotEqual, "Checks if two values are not equal.", 80]
[Comparison, <, LessThan, "Checks if a value is less than another.", 80]
[Comparison, <=, LessThanOrEqual, "Checks if a value is less than or equal to another.", 80]
[Comparison, >, GreaterThan, "Checks if a value is greater than another.", 80]
[Comparison, >=, GreaterThanOrEqual, "Checks if a value is greater than or equal to another.", 80]
[Comparison, =~, RegexMatch, "Checks if a value matches a regular expression.", 80]
[Comparison, !~, NotRegexMatch, "Checks if a value does not match a regular expression.", 80]
[Comparison, in, In, "Checks if a value is in a list or string.", 80]
[Comparison, not-in, NotIn, "Checks if a value is not in a list or string.", 80]
[Comparison, starts-with, StartsWith, "Checks if a string starts with another.", 80]
[Comparison, ends-with, EndsWith, "Checks if a string ends with another.", 80]
[Comparison, not, UnaryNot, "Negates a value or expression.", 0]
[Math, +, Plus, "Adds two values.", 90]
[Math, ++, Append, "Appends two lists or a list and a value.", 80]
[Math, -, Minus, "Subtracts two values.", 90]
[Math, *, Multiply, "Multiplies two values.", 95]
[Math, /, Divide, "Divides two values.", 95]
[Math, //, FloorDivision, "Divides two values and floors the result.", 95]
[Math, mod, Modulo, "Divides two values and returns the remainder.", 95]
[Math, **, "Pow ", "Raises one value to the power of another.", 100]
[Bitwise, bit-or, BitOr, "Performs a bitwise OR on two values.", 60]
[Bitwise, bit-xor, BitXor, "Performs a bitwise XOR on two values.", 70]
[Bitwise, bit-and, BitAnd, "Performs a bitwise AND on two values.", 75]
[Bitwise, bit-shl, ShiftLeft, "Shifts a value left by another.", 85]
[Bitwise, bit-shr, ShiftRight, "Shifts a value right by another.", 85]
[Boolean, and, And, "Checks if two values are true.", 50]
[Boolean, or, Or, "Checks if either value is true.", 40]
[Boolean, xor, Xor, "Checks if one value is true and the other is false.", 45]
] | sort-by name)
let operator = ($operator | str join " ")
if not ($find | is-empty) {
let found_operators = ($operators | find $find)
if ($found_operators | length) == 1 {
show-operator ($found_operators | get 0)
} else {
$found_operators
}
} else if not ($operator | is-empty) {
let found_operator = ($operators | where name == $operator)
if ($found_operator | is-empty) {
operator-not-found-error (metadata $operator | get span)
}
show-operator ($found_operator | get 0)
} else {
$operators
}
}
def show-command [command: record] {
if not ($command.usage? | is-empty) {
print $command.usage
}
if not ($command.extra_usage? | is-empty) {
print ""
print $command.extra_usage
}
if not ($command.search_terms? | is-empty) {
print ""
print-help-header -n "Search terms"
print $" ($command.search_terms)"
}
if not ($command.module_name? | is-empty) {
print ""
print-help-header -n "Module"
print $" ($command.module_name)"
}
if not ($command.category? | is-empty) {
print ""
print-help-header -n "Category"
print $" ($command.category)"
}
print ""
print "This command:"
if ($command.creates_scope) {
print $"- (ansi cyan)does create(ansi reset) a scope."
} else {
print $"- (ansi cyan)does not create(ansi reset) a scope."
}
if ($command.is_builtin) {
print $"- (ansi cyan)is(ansi reset) a built-in command."
} else {
print $"- (ansi cyan)is not(ansi reset) a built-in command."
}
if ($command.is_sub) {
print $"- (ansi cyan)is(ansi reset) a subcommand."
} else {
print $"- (ansi cyan)is not(ansi reset) a subcommand."
}
if ($command.is_plugin) {
print $"- (ansi cyan)is part(ansi reset) of a plugin."
} else {
print $"- (ansi cyan)is not part(ansi reset) of a plugin."
}
if ($command.is_custom) {
print $"- (ansi cyan)is(ansi reset) a custom command."
} else {
print $"- (ansi cyan)is not(ansi reset) a custom command."
}
if ($command.is_keyword) {
print $"- (ansi cyan)is(ansi reset) a keyword."
} else {
print $"- (ansi cyan)is not(ansi reset) a keyword."
}
let signatures = ($command.signatures | transpose | get column1)
if not ($signatures | is-empty) {
let parameters = ($signatures | get 0 | where parameter_type != input and parameter_type != output)
let positionals = ($parameters | where parameter_type == positional and parameter_type != rest)
let flags = ($parameters | where parameter_type != positional and parameter_type != rest)
print ""
print-help-header "Usage"
print -n " > "
print -n $"($command.name) "
if not ($flags | is-empty) {
print -n $"{flags} "
}
for param in $positionals {
print -n $"<($param.parameter_name)> "
}
print ""
}
let subcommands = ($nu.scope.commands | where name =~ $"^($command.name) " | select name usage)
if not ($subcommands | is-empty) {
print ""
print-help-header "Subcommands"
for subcommand in $subcommands {
print $" (ansi teal)($subcommand.name)(ansi reset) - ($subcommand.usage)"
}
}
if not ($signatures | is-empty) {
let parameters = ($signatures | get 0 | where parameter_type != input and parameter_type != output)
let positionals = ($parameters | where parameter_type == positional and parameter_type != rest)
let flags = ($parameters | where parameter_type != positional and parameter_type != rest)
let is_rest = (not ($parameters | where parameter_type == rest | is-empty))
print ""
print-help-header "Flags"
print $" (ansi teal)-h(ansi reset), (ansi teal)--help(ansi reset) - Display the help message for this command"
for flag in $flags {
print -n $" (ansi teal)-($flag.short_flag)(ansi reset), (ansi teal)--($flag.parameter_name)(ansi reset)"
if not ($flag.syntax_shape | is-empty) {
print -n $" <(ansi light_blue)($flag.syntax_shape)(ansi reset)>"
}
print $" - ($flag.description)"
}
print ""
print-help-header "Signatures"
for signature in $signatures {
let input = ($signature | where parameter_type == input | get 0)
let output = ($signature | where parameter_type == output | get 0)
print -n $" <($input.syntax_shape)> | ($command.name)"
for positional in $positionals {
print -n $" <($positional.syntax_shape)>"
}
print $" -> <($output.syntax_shape)>"
}
if (not ($positionals | is-empty)) or $is_rest {
print ""
print-help-header "Parameters"
for positional in $positionals {
print -n " "
if ($positional.is_optional) {
print -n "(optional) "
}
print $"(ansi teal)($positional.parameter_name)(ansi reset) <(ansi light_blue)($positional.syntax_shape)(ansi reset)>: ($positional.description)"
}
if $is_rest {
let rest = ($parameters | where parameter_type == rest | get 0)
print $" ...(ansi teal)rest(ansi reset) <(ansi light_blue)($rest.syntax_shape)(ansi reset)>: ($rest.description)"
}
}
}
if not ($command.examples | is-empty) {
print ""
print-help-header -n "Examples"
for example in $command.examples {
print ""
print $" ($example.description)"
print $" > ($example.example | nu-highlight)"
if not ($example.result | is-empty) {
for line in (
$example.result | table | if ($example.result | describe) == "binary" { str join } else { lines }
) {
print $" ($line)"
}
}
}
}
print ""
}
# Show help on nushell commands.
export def "help commands" [
...command: string # the name of command to get help on
--find (-f): string # string to find in command names and usage
] {
let commands = ($nu.scope.commands | where not is_extern | reject is_extern | sort-by name)
let command = ($command | str join " ")
if not ($find | is-empty) {
let found_commands = ($commands | find $find)
if ($found_commands | length) == 1 {
show-command ($found_commands | get 0)
} else {
$found_commands | select name category usage signatures search_terms
}
} else if not ($command | is-empty) {
let found_command = ($commands | where name == $command)
if ($found_command | is-empty) {
command-not-found-error (metadata $command | get span)
}
show-command ($found_command | get 0)
} else {
$commands | select name category usage signatures search_terms
}
}
def pretty-cmd [] {
let cmd = $in
$"(ansi default_dimmed)(ansi default_italic)($cmd)(ansi reset)"
}
# Display help information about different parts of Nushell.
#
# `help word` searches for "word" in commands, aliases and modules, in that order.
#
# Examples:
# show help for single command, alias, or module
# > help match
#
# show help for single sub-command, alias, or module
# > help str lpad
#
# search for string in command names, usage and search terms
# > help --find char
export def help [
...item: string # the name of the help item to get help on
--find (-f): string # string to find in help items names and usage
] {
if ($item | is-empty) and ($find | is-empty) {
print $"Welcome to Nushell.
Here are some tips to help you get started.
* ('help -h' | pretty-cmd) or ('help help' | pretty-cmd) - show available ('help' | pretty-cmd) subcommands and examples
* ('help commands' | pretty-cmd) - list all available commands
* ('help <name>' | pretty-cmd) - display help about a particular command, alias, or module
* ('help --find <text to search>' | pretty-cmd) - search through all help commands table
Nushell works on the idea of a "(ansi default_italic)pipeline(ansi reset)". Pipelines are commands connected with the '|' character.
Each stage in the pipeline works together to load, parse, and display information to you.
(ansi green)Examples(ansi reset):
List the files in the current directory, sorted by size
> ('ls | sort-by size' | nu-highlight)
Get information about the current system
> ('sys | get host' | nu-highlight)
Get the processes on your system actively using CPU
> ('ps | where cpu > 0' | nu-highlight)
You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https://www.nushell.sh/book/(ansi reset)"
return
}
let item = ($item | str join " ")
let commands = (try { help commands $item --find $find })
if not ($commands | is-empty) { return $commands }
let aliases = (try { help aliases $item --find $find })
if not ($aliases | is-empty) { return $aliases }
let modules = (try { help modules $item --find $find })
if not ($modules | is-empty) { return $modules }
}