forked from extern/nushell
Add virtual path abstraction layer (#9245)
This commit is contained in:
129
crates/nu-std/std/dirs.nu
Normal file
129
crates/nu-std/std/dirs.nu
Normal file
@ -0,0 +1,129 @@
|
||||
# Maintain a list of working directories and navigate them
|
||||
|
||||
# the directory stack
|
||||
# current slot is DIRS_POSITION, but that entry doesn't hold $PWD (until leaving it for some other)
|
||||
# till then, we let CD change PWD freely
|
||||
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 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)
|
||||
|
||||
|
||||
_fetch 1
|
||||
}
|
||||
|
||||
export alias enter = add
|
||||
|
||||
# Advance to the next directory in the list or wrap to beginning.
|
||||
export def-env next [
|
||||
N:int = 1 # number of positions to move.
|
||||
] {
|
||||
_fetch $N
|
||||
}
|
||||
|
||||
export alias n = next
|
||||
|
||||
# Back up to the previous directory or wrap to the end.
|
||||
export def-env prev [
|
||||
N:int = 1 # number of positions to move.
|
||||
] {
|
||||
_fetch (-1 * $N)
|
||||
}
|
||||
|
||||
export alias p = prev
|
||||
|
||||
# Drop the current directory from the list, if it's not the only one.
|
||||
# PWD becomes the next working directory
|
||||
export def-env drop [] {
|
||||
if ($env.DIRS_LIST | length) > 1 {
|
||||
let-env DIRS_LIST = ($env.DIRS_LIST | reject $env.DIRS_POSITION)
|
||||
if ($env.DIRS_POSITION >= ($env.DIRS_LIST | length)) {$env.DIRS_POSITION = 0}
|
||||
}
|
||||
|
||||
_fetch -1 --forget_current # step to previous slot
|
||||
|
||||
}
|
||||
|
||||
export alias dexit = drop
|
||||
|
||||
# Display current working directories.
|
||||
export def-env show [] {
|
||||
mut out = []
|
||||
for $p in ($env.DIRS_LIST | enumerate) {
|
||||
let is_act_slot = $p.index == $env.DIRS_POSITION
|
||||
$out = ($out | append [
|
||||
[active, path];
|
||||
[($is_act_slot),
|
||||
(if $is_act_slot {$env.PWD} else {$p.item}) # show current PWD in lieu of active slot
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
$out
|
||||
}
|
||||
|
||||
export alias shells = show
|
||||
|
||||
export def-env goto [shell?: int] {
|
||||
if $shell == null {
|
||||
return (show)
|
||||
}
|
||||
|
||||
if $shell < 0 or $shell >= ($env.DIRS_LIST | length) {
|
||||
let span = (metadata $shell | get span)
|
||||
error make {
|
||||
msg: $"(ansi red_bold)invalid_shell_index(ansi reset)"
|
||||
label: {
|
||||
text: $"`shell` should be between 0 and (($env.DIRS_LIST | length) - 1)"
|
||||
start: $span.start
|
||||
end: $span.end
|
||||
}
|
||||
}
|
||||
}
|
||||
let-env DIRS_POSITION = $shell
|
||||
|
||||
cd ($env.DIRS_LIST | get $env.DIRS_POSITION)
|
||||
}
|
||||
|
||||
export alias g = goto
|
||||
|
||||
# fetch item helper
|
||||
def-env _fetch [
|
||||
offset: int, # signed change to position
|
||||
--forget_current # true to skip saving PWD
|
||||
] {
|
||||
if not ($forget_current) {
|
||||
# first record current working dir in current slot of ring, to track what CD may have done.
|
||||
$env.DIRS_LIST = ($env.DIRS_LIST | upsert $env.DIRS_POSITION $env.PWD)
|
||||
}
|
||||
|
||||
# figure out which entry to move to
|
||||
# nushell 'mod' operator is really 'remainder', can return negative values.
|
||||
# see: https://stackoverflow.com/questions/13683563/whats-the-difference-between-mod-and-remainder
|
||||
let len = ($env.DIRS_LIST | length)
|
||||
mut pos = ($env.DIRS_POSITION + $offset) mod $len
|
||||
if ($pos < 0) { $pos += $len}
|
||||
|
||||
# if using a different position in ring, CD there.
|
||||
if ($pos != $env.DIRS_POSITION) {
|
||||
$env.DIRS_POSITION = $pos
|
||||
cd ($env.DIRS_LIST | get $pos )
|
||||
}
|
||||
}
|
226
crates/nu-std/std/dt.nu
Normal file
226
crates/nu-std/std/dt.nu
Normal file
@ -0,0 +1,226 @@
|
||||
def borrow-year [from: record, current: record] {
|
||||
mut current = $current
|
||||
|
||||
$current.year = $current.year - 1
|
||||
$current.month = $current.month + 12
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
def leap-year-days [year] {
|
||||
if $year mod 400 == 0 {
|
||||
29
|
||||
} else if $year mod 4 == 0 and $year mod 100 != 0 {
|
||||
29
|
||||
} else {
|
||||
28
|
||||
}
|
||||
}
|
||||
|
||||
def borrow-month [from: record, current: record] {
|
||||
mut current = $current
|
||||
if $from.month in [1, 3, 5, 7, 8, 10, 12] {
|
||||
$current.day = $current.day + 31
|
||||
$current.month = $current.month - 1
|
||||
if $current.month < 0 {
|
||||
$current = (borrow-year $from $current)
|
||||
}
|
||||
} else if $from.month in [4, 6, 9, 11] {
|
||||
$current.day = $current.day + 30
|
||||
$current.month = $current.month - 1
|
||||
if $current.month < 0 {
|
||||
$current = (borrow-year $from $current)
|
||||
}
|
||||
} else {
|
||||
# oh February
|
||||
let num_days_feb = (leap-year-days $current.year)
|
||||
$current.day = $current.day + $num_days_feb
|
||||
$current.month = $current.month - 1
|
||||
if $current.month < 0 {
|
||||
$current = (borrow-year $from $current)
|
||||
}
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
def borrow-day [from: record, current: record] {
|
||||
mut current = $current
|
||||
$current.hour = $current.hour + 24
|
||||
$current.day = $current.day - 1
|
||||
if $current.day < 0 {
|
||||
$current = (borrow-month $from $current)
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
def borrow-hour [from: record, current: record] {
|
||||
mut current = $current
|
||||
$current.minute = $current.minute + 60
|
||||
$current.hour = $current.hour - 1
|
||||
if $current.hour < 0 {
|
||||
$current = (borrow-day $from $current)
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
def borrow-minute [from: record, current: record] {
|
||||
mut current = $current
|
||||
$current.second = $current.second + 60
|
||||
$current.minute = $current.minute - 1
|
||||
if $current.minute < 0 {
|
||||
$current = (borrow-hour $from $current)
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
def borrow-second [from: record, current: record] {
|
||||
mut current = $current
|
||||
$current.millisecond = $current.millisecond + 1_000
|
||||
$current.second = $current.second - 1
|
||||
if $current.second < 0 {
|
||||
$current = (borrow-minute $from $current)
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
def borrow-millisecond [from: record, current: record] {
|
||||
mut current = $current
|
||||
$current.microsecond = $current.microsecond + 1_000_000
|
||||
$current.millisecond = $current.millisecond - 1
|
||||
if $current.millisecond < 0 {
|
||||
$current = (borrow-second $from $current)
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
def borrow-microsecond [from: record, current: record] {
|
||||
mut current = $current
|
||||
$current.nanosecond = $current.nanosecond + 1_000_000_000
|
||||
$current.microsecond = $current.microsecond - 1
|
||||
if $current.microsecond < 0 {
|
||||
$current = (borrow-millisecond $from $current)
|
||||
}
|
||||
|
||||
$current
|
||||
}
|
||||
|
||||
# Subtract two datetimes and return a record with the difference
|
||||
# Examples
|
||||
# print (datetime-diff 2023-05-07T04:08:45+12:00 2019-05-10T09:59:12+12:00)
|
||||
# print (datetime-diff (date now) 2019-05-10T09:59:12-07:00)
|
||||
export def datetime-diff [from: datetime, to: datetime] {
|
||||
let from_expanded = ($from | date to-timezone utc | date to-record | merge { millisecond: 0, microsecond: 0})
|
||||
let to_expanded = ($to | date to-timezone utc | date to-record | merge { millisecond: 0, microsecond: 0})
|
||||
|
||||
mut result = { year: ($from_expanded.year - $to_expanded.year), month: ($from_expanded.month - $to_expanded.month)}
|
||||
|
||||
if $result.month < 0 {
|
||||
$result = (borrow-year $from_expanded $result)
|
||||
}
|
||||
|
||||
$result.day = $from_expanded.day - $to_expanded.day
|
||||
if $result.day < 0 {
|
||||
$result = (borrow-month $from_expanded $result)
|
||||
}
|
||||
|
||||
$result.hour = $from_expanded.hour - $to_expanded.hour
|
||||
if $result.hour < 0 {
|
||||
$result = (borrow-day $from_expanded $result)
|
||||
}
|
||||
|
||||
$result.minute = $from_expanded.minute - $to_expanded.minute
|
||||
if $result.minute < 0 {
|
||||
$result = (borrow-hour $from_expanded $result)
|
||||
}
|
||||
|
||||
$result.second = $from_expanded.second - $to_expanded.second
|
||||
if $result.second < 0 {
|
||||
$result = (borrow-minute $from_expanded $result)
|
||||
}
|
||||
|
||||
$result.nanosecond = $from_expanded.nanosecond - $to_expanded.nanosecond
|
||||
if $result.nanosecond < 0 {
|
||||
$result = (borrow-second $from_expanded $result)
|
||||
}
|
||||
|
||||
$result.millisecond = ($result.nanosecond / 1_000_000 | into int) # don't want a decimal
|
||||
$result.microsecond = (($result.nanosecond mod 1_000_000) / 1_000 | into int)
|
||||
$result.nanosecond = ($result.nanosecond mod 1_000 | into int)
|
||||
|
||||
$result
|
||||
}
|
||||
|
||||
export def pretty-print-duration [dur: duration] {
|
||||
mut result = ""
|
||||
if $dur.year > 0 {
|
||||
if $dur.year > 1 {
|
||||
$result = $"($dur.year)yrs "
|
||||
} else {
|
||||
$result = $"($dur.year)yr "
|
||||
}
|
||||
}
|
||||
if $dur.month > 0 {
|
||||
if $dur.month > 1 {
|
||||
$result = $"($result)($dur.month)months "
|
||||
} else {
|
||||
$result = $"($result)($dur.month)month "
|
||||
}
|
||||
}
|
||||
if $dur.day > 0 {
|
||||
if $dur.day > 1 {
|
||||
$result = $"($result)($dur.day)days "
|
||||
} else {
|
||||
$result = $"($result)($dur.day)day "
|
||||
}
|
||||
}
|
||||
if $dur.hour > 0 {
|
||||
if $dur.hour > 1 {
|
||||
$result = $"($result)($dur.hour)hrs "
|
||||
} else {
|
||||
$result = $"($result)($dur.hour)hr "
|
||||
}
|
||||
}
|
||||
if $dur.minute > 0 {
|
||||
if $dur.minute > 1 {
|
||||
$result = $"($result)($dur.minute)mins "
|
||||
} else {
|
||||
$result = $"($result)($dur.minute)min "
|
||||
}
|
||||
}
|
||||
if $dur.second > 0 {
|
||||
if $dur.second > 1 {
|
||||
$result = $"($result)($dur.second)secs "
|
||||
} else {
|
||||
$result = $"($result)($dur.second)sec "
|
||||
}
|
||||
}
|
||||
if $dur.millisecond > 0 {
|
||||
if $dur.millisecond > 1 {
|
||||
$result = $"($result)($dur.millisecond)ms "
|
||||
} else {
|
||||
$result = $"($result)($dur.millisecond)ms "
|
||||
}
|
||||
}
|
||||
if $dur.microsecond > 0 {
|
||||
if $dur.microsecond > 1 {
|
||||
$result = $"($result)($dur.microsecond)µs "
|
||||
} else {
|
||||
$result = $"($result)($dur.microsecond)µs "
|
||||
}
|
||||
}
|
||||
if $dur.nanosecond > 0 {
|
||||
if $dur.nanosecond > 1 {
|
||||
$result = $"($result)($dur.nanosecond)ns "
|
||||
} else {
|
||||
$result = $"($result)($dur.nanosecond)ns "
|
||||
}
|
||||
}
|
||||
|
||||
$result
|
||||
}
|
747
crates/nu-std/std/help.nu
Normal file
747
crates/nu-std/std/help.nu
Normal file
@ -0,0 +1,747 @@
|
||||
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 get-all-operators [] { return [
|
||||
[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]
|
||||
]}
|
||||
|
||||
def "nu-complete list-aliases" [] {
|
||||
$nu.scope.aliases | select name usage | rename value description
|
||||
}
|
||||
|
||||
def "nu-complete list-modules" [] {
|
||||
$nu.scope.modules | select name usage | rename value description
|
||||
}
|
||||
|
||||
def "nu-complete list-operators" [] {
|
||||
let completions = (
|
||||
get-all-operators
|
||||
| select name description
|
||||
| rename value description
|
||||
)
|
||||
$completions
|
||||
}
|
||||
|
||||
def "nu-complete list-commands" [] {
|
||||
$nu.scope.commands | select name usage | rename value description
|
||||
}
|
||||
|
||||
def "nu-complete list-externs" [] {
|
||||
$nu.scope.commands | where is_extern | select name usage | rename value description
|
||||
}
|
||||
|
||||
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 "Exported aliases"
|
||||
print $" ($module.aliases | str join ', ')"
|
||||
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."
|
||||
print (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 modules [
|
||||
...module: string@"nu-complete list-modules" # the name of module to get help on
|
||||
--find (-f): string # string to find in module names
|
||||
] {
|
||||
let modules = $nu.scope.modules
|
||||
|
||||
if not ($find | is-empty) {
|
||||
$modules | find $find --columns [name usage]
|
||||
} else if not ($module | is-empty) {
|
||||
let found_module = ($modules | where name == ($module | str join " "))
|
||||
|
||||
if ($found_module | is-empty) {
|
||||
module-not-found-error (metadata $module | get span)
|
||||
}
|
||||
|
||||
show-module ($found_module | get 0)
|
||||
" " # signal something was shown
|
||||
} 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
|
||||
# > alias foo = echo "this is foo"
|
||||
# >
|
||||
# > # my bar alias
|
||||
# > alias bar = echo "this is bar"
|
||||
# >
|
||||
# > # my baz alias
|
||||
# > alias baz = echo "this is baz"
|
||||
# >
|
||||
# > # a multiline alias
|
||||
# > 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 aliases [
|
||||
...alias: string@"nu-complete list-aliases" # 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)
|
||||
|
||||
if not ($find | is-empty) {
|
||||
$aliases | find $find --columns [name usage]
|
||||
} else if not ($alias | is-empty) {
|
||||
let found_alias = ($aliases | where name == ($alias | str join " "))
|
||||
|
||||
if ($found_alias | is-empty) {
|
||||
alias-not-found-error (metadata $alias | get span)
|
||||
}
|
||||
|
||||
show-alias ($found_alias | get 0)
|
||||
" " # signal something was shown
|
||||
} 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 externs [
|
||||
...extern: string@"nu-complete list-externs" # 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
|
||||
)
|
||||
|
||||
if not ($find | is-empty) {
|
||||
$externs | find $find --columns [name usage]
|
||||
} else if not ($extern | is-empty) {
|
||||
let found_extern = ($externs | where name == ($extern | str join " "))
|
||||
|
||||
if ($found_extern | is-empty) {
|
||||
extern-not-found-error (metadata $extern | get span)
|
||||
}
|
||||
|
||||
show-extern ($found_extern | get 0)
|
||||
" " # signal something was shown
|
||||
} 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 operators [
|
||||
...operator: string@"nu-complete list-operators" # the name of operator to get help on
|
||||
--find (-f): string # string to find in operator names
|
||||
] {
|
||||
let operators = (get-all-operators)
|
||||
|
||||
if not ($find | is-empty) {
|
||||
$operators | find $find --columns [type name]
|
||||
} else if not ($operator | is-empty) {
|
||||
let found_operator = ($operators | where name == ($operator | str join " "))
|
||||
|
||||
if ($found_operator | is-empty) {
|
||||
operator-not-found-error (metadata $operator | get span)
|
||||
}
|
||||
|
||||
show-operator ($found_operator | get 0)
|
||||
" " # signal something was shown
|
||||
} 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"
|
||||
for flag in $flags {
|
||||
let flag_parts = [ " ",
|
||||
(if ($flag.short_flag | is-empty) { "" } else {
|
||||
$"-(ansi teal)($flag.short_flag)(ansi reset), "
|
||||
}),
|
||||
(if ($flag.parameter_name | is-empty) { "" } else {
|
||||
$"--(ansi teal)($flag.parameter_name)(ansi reset)"
|
||||
}),
|
||||
(if ($flag.syntax_shape | is-empty) { "" } else {
|
||||
$": <(ansi light_blue)($flag.syntax_shape)(ansi reset)>"
|
||||
}),
|
||||
(if ($flag.description | is-empty) { "" } else {
|
||||
$" - ($flag.description)"
|
||||
}),
|
||||
(if ($flag.parameter_default | is-empty) { "" } else {
|
||||
$" \(default: ($flag.parameter_default)\)"
|
||||
}),
|
||||
]
|
||||
print ($flag_parts | str join "")
|
||||
}
|
||||
print $" (ansi teal)-h(ansi reset), --(ansi teal)help(ansi reset) - Display the help message for this command"
|
||||
|
||||
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 {
|
||||
let arg_parts = [ " ",
|
||||
$"(ansi teal)($positional.parameter_name)(ansi reset)",
|
||||
(if ($positional.syntax_shape | is-empty) { "" } else {
|
||||
$": <(ansi light_blue)($positional.syntax_shape)(ansi reset)>"
|
||||
}),
|
||||
(if ($positional.description | is-empty) { "" } else {
|
||||
$" ($positional.description)"
|
||||
}),
|
||||
(if ($positional.parameter_default | is-empty) { "" } else {
|
||||
$" \(optional, default: ($positional.parameter_default)\)"
|
||||
})
|
||||
]
|
||||
print ($arg_parts | str join "")
|
||||
}
|
||||
|
||||
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 commands.
|
||||
export def commands [
|
||||
...command: string@"nu-complete list-commands" # 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)
|
||||
|
||||
if not ($find | is-empty) {
|
||||
# TODO: impl find for external commands
|
||||
$commands | find $find --columns [name usage search_terms] | select name category usage signatures search_terms
|
||||
} else if not ($command | is-empty) {
|
||||
let target_command = ($command | str join " ")
|
||||
let found_command = ($commands | where name == $target_command)
|
||||
|
||||
if ($found_command | is-empty) {
|
||||
try {
|
||||
print $"(ansi default_italic)Help pages from external command ($target_command | pretty-cmd):(ansi reset)"
|
||||
^($env.NU_HELPER? | default "man") $target_command
|
||||
} catch {
|
||||
command-not-found-error (metadata $command | get span)
|
||||
}
|
||||
}
|
||||
|
||||
show-command ($found_command | get 0)
|
||||
" " # signal something was shown
|
||||
} 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 main [
|
||||
...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 target_item = ($item | str join " ")
|
||||
|
||||
let commands = (try { commands $target_item --find $find })
|
||||
if not ($commands | is-empty) { return $commands }
|
||||
|
||||
let aliases = (try { aliases $target_item --find $find })
|
||||
if not ($aliases | is-empty) { return $aliases }
|
||||
|
||||
let modules = (try { modules $target_item --find $find })
|
||||
if not ($modules | is-empty) { return $modules }
|
||||
|
||||
let span = (metadata $item | get span)
|
||||
error make {
|
||||
msg: ("std::help::item_not_found" | error-fmt)
|
||||
label: {
|
||||
text: "item not found"
|
||||
start: $span.start
|
||||
end: $span.end
|
||||
}
|
||||
}
|
||||
}
|
199
crates/nu-std/std/iter.nu
Normal file
199
crates/nu-std/std/iter.nu
Normal file
@ -0,0 +1,199 @@
|
||||
# | Filter Extensions
|
||||
#
|
||||
# This module implements extensions to the `filters` commands
|
||||
#
|
||||
# They are prefixed with `iter` so as to avoid conflicts with
|
||||
# the inbuilt filters
|
||||
|
||||
# Returns the first element of the list that matches the
|
||||
# closure predicate, `null` otherwise
|
||||
#
|
||||
# # Invariant
|
||||
# > The closure has to be a predicate (returning a bool value)
|
||||
# > else `null` is returned
|
||||
# > The closure also has to be valid for the types it receives
|
||||
# > These will be flagged as errors later as closure annotations
|
||||
# > are implemented
|
||||
#
|
||||
# # Example
|
||||
# ```
|
||||
# use std ["assert equal" "iter find"]
|
||||
#
|
||||
# let haystack = ["shell", "abc", "around", "nushell", "std"]
|
||||
#
|
||||
# let found = ($haystack | iter find {|it| $it starts-with "a" })
|
||||
# let not_found = ($haystack | iter find {|it| $it mod 2 == 0})
|
||||
#
|
||||
# assert equal $found "abc"
|
||||
# assert equal $not_found null
|
||||
# ```
|
||||
export def find [ # -> any | null
|
||||
fn: closure # the closure used to perform the search
|
||||
] {
|
||||
try {
|
||||
filter $fn | get 0?
|
||||
} catch {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
# Returns the index of the first element that matches the predicate or
|
||||
# -1 if none
|
||||
#
|
||||
# # Invariant
|
||||
# > The closure has to return a bool
|
||||
#
|
||||
# # Example
|
||||
# ```nu
|
||||
# use std ["assert equal" "iter find-index"]
|
||||
#
|
||||
# let res = (
|
||||
# ["iter", "abc", "shell", "around", "nushell", "std"]
|
||||
# | iter find-index {|x| $x starts-with 's'}
|
||||
# )
|
||||
# assert equal $res 2
|
||||
#
|
||||
# let is_even = {|x| $x mod 2 == 0}
|
||||
# let res = ([3 5 13 91] | iter find-index $is_even)
|
||||
# assert equal $res -1
|
||||
# ```
|
||||
export def find-index [ # -> int
|
||||
fn: closure # the closure used to perform the search
|
||||
] {
|
||||
let matches = (
|
||||
enumerate
|
||||
| each {|it|
|
||||
if (do $fn $it.item) {
|
||||
$it.index
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if ($matches | is-empty) {
|
||||
-1
|
||||
} else {
|
||||
$matches | first
|
||||
}
|
||||
}
|
||||
|
||||
# Returns a new list with the separator between adjacent
|
||||
# items of the original list
|
||||
#
|
||||
# # Example
|
||||
# ```
|
||||
# use std ["assert equal" "iter intersperse"]
|
||||
#
|
||||
# let res = ([1 2 3 4] | iter intersperse 0)
|
||||
# assert equal $res [1 0 2 0 3 0 4]
|
||||
# ```
|
||||
export def intersperse [ # -> list<any>
|
||||
separator: any # the separator to be used
|
||||
] {
|
||||
reduce -f [] {|it, acc|
|
||||
$acc ++ [$it, $separator]
|
||||
}
|
||||
| match $in {
|
||||
[] => [],
|
||||
$xs => ($xs | take (($xs | length) - 1 ))
|
||||
}
|
||||
}
|
||||
|
||||
# Returns a list of intermediate steps performed by `reduce`
|
||||
# (`fold`). It takes two arguments, an initial value to seed the
|
||||
# initial state and a closure that takes two arguments, the first
|
||||
# being the internal state and the second the list element in the
|
||||
# current iteration.
|
||||
#
|
||||
# # Example
|
||||
# ```
|
||||
# use std ["assert equal" "iter scan"]
|
||||
# let scanned = ([1 2 3] | iter scan 0 {|x, y| $x + $y})
|
||||
#
|
||||
# assert equal $scanned [0, 1, 3, 6]
|
||||
#
|
||||
# # use the --noinit(-n) flag to remove the initial value from
|
||||
# # the final result
|
||||
# let scanned = ([1 2 3] | iter scan 0 {|x, y| $x + $y} -n)
|
||||
#
|
||||
# assert equal $scanned [1, 3, 6]
|
||||
# ```
|
||||
export def scan [ # -> list<any>
|
||||
init: any # initial value to seed the initial state
|
||||
fn: closure # the closure to perform the scan
|
||||
--noinit(-n) # remove the initial value from the result
|
||||
] {
|
||||
reduce -f [$init] {|it, acc|
|
||||
$acc ++ [(do $fn ($acc | last) $it)]
|
||||
}
|
||||
| if $noinit {
|
||||
$in | skip
|
||||
} else {
|
||||
$in
|
||||
}
|
||||
}
|
||||
|
||||
# Returns a list of values for which the supplied closure does not
|
||||
# return `null` or an error. It is equivalent to
|
||||
# `$in | each $fn | filter $fn`
|
||||
#
|
||||
# # Example
|
||||
# ```nu
|
||||
# use std ["assert equal" "iter filter-map"]
|
||||
#
|
||||
# let res = ([2 5 "4" 7] | iter filter-map {|it| $it ** 2})
|
||||
#
|
||||
# assert equal $res [4 25 49]
|
||||
# ```
|
||||
export def filter-map [ # -> list<any>
|
||||
fn: closure # the closure to apply to the input
|
||||
] {
|
||||
each {|$it|
|
||||
try {
|
||||
do $fn $it
|
||||
} catch {
|
||||
null
|
||||
}
|
||||
}
|
||||
| filter {|it|
|
||||
$it != null
|
||||
}
|
||||
}
|
||||
|
||||
# Maps a closure to each nested structure and flattens the result
|
||||
#
|
||||
# # Example
|
||||
# ```nu
|
||||
# use std ["assert equal" "iter flat-map"]
|
||||
#
|
||||
# let res = (
|
||||
# [[1 2 3] [2 3 4] [5 6 7]] | iter flat-map {|it| $it | math sum}
|
||||
# )
|
||||
# assert equal $res [6 9 18]
|
||||
# ```
|
||||
export def flat-map [ # -> list<any>
|
||||
fn: closure # the closure to map to the nested structures
|
||||
] {
|
||||
each {|it| do $fn $it } | flatten
|
||||
}
|
||||
|
||||
# Zips two structures and applies a closure to each of the zips
|
||||
#
|
||||
# # Example
|
||||
# ```nu
|
||||
# use std ["assert equal" "iter iter zip-with"]
|
||||
#
|
||||
# let res = (
|
||||
# [1 2 3] | iter zip-with [2 3 4] {|a, b| $a + $b }
|
||||
# )
|
||||
#
|
||||
# assert equal $res [3 5 7]
|
||||
# ```
|
||||
export def zip-with [ # -> list<any>
|
||||
other: any # the structure to zip with
|
||||
fn: closure # the closure to apply to the zips
|
||||
] {
|
||||
zip $other
|
||||
| each {|it|
|
||||
reduce {|it, acc| do $fn $acc $it }
|
||||
}
|
||||
}
|
263
crates/nu-std/std/log.nu
Normal file
263
crates/nu-std/std/log.nu
Normal file
@ -0,0 +1,263 @@
|
||||
export def CRITICAL_LEVEL [] {
|
||||
50
|
||||
}
|
||||
|
||||
export def ERROR_LEVEL [] {
|
||||
40
|
||||
}
|
||||
|
||||
export def WARNING_LEVEL [] {
|
||||
30
|
||||
}
|
||||
|
||||
export def INFO_LEVEL [] {
|
||||
20
|
||||
}
|
||||
|
||||
export def DEBUG_LEVEL [] {
|
||||
10
|
||||
}
|
||||
|
||||
def parse-string-level [
|
||||
level: string
|
||||
] {
|
||||
if $level in [(CRITICAL_LEVEL_PREFIX) (CRITICAL_LEVEL_PREFIX --short) "CRIT" "CRITICAL"] {
|
||||
CRITICAL_LEVEL
|
||||
} else if $level in [(ERROR_LEVEL_PREFIX) (ERROR_LEVEL_PREFIX --short) "ERROR" ] {
|
||||
ERROR_LEVEL
|
||||
} else if $level in [(WARNING_LEVEL_PREFIX) (WARNING_LEVEL_PREFIX --short) "WARN" "WARNING"] {
|
||||
WARNING_LEVEL
|
||||
} else if $level in [(DEBUG_LEVEL_PREFIX) (DEBUG_LEVEL_PREFIX --short) "DEBUG"] {
|
||||
DEBUG_LEVEL
|
||||
} else {
|
||||
INFO_LEVEL
|
||||
}
|
||||
}
|
||||
|
||||
export def CRITICAL_LEVEL_PREFIX [
|
||||
--short (-s)
|
||||
] {
|
||||
if $short {
|
||||
"C"
|
||||
} else {
|
||||
"CRT"
|
||||
}
|
||||
}
|
||||
|
||||
export def ERROR_LEVEL_PREFIX [
|
||||
--short (-s)
|
||||
] {
|
||||
if $short {
|
||||
"E"
|
||||
} else {
|
||||
"ERR"
|
||||
}
|
||||
}
|
||||
|
||||
export def WARNING_LEVEL_PREFIX [
|
||||
--short (-s)
|
||||
] {
|
||||
if $short {
|
||||
"W"
|
||||
} else {
|
||||
"WRN"
|
||||
}
|
||||
}
|
||||
|
||||
export def INFO_LEVEL_PREFIX [
|
||||
--short (-s)
|
||||
] {
|
||||
if $short {
|
||||
"I"
|
||||
} else {
|
||||
"INF"
|
||||
}
|
||||
}
|
||||
|
||||
export def DEBUG_LEVEL_PREFIX [
|
||||
--short (-s)
|
||||
] {
|
||||
if $short {
|
||||
"D"
|
||||
} else {
|
||||
"DBG"
|
||||
}
|
||||
}
|
||||
|
||||
def parse-int-level [
|
||||
level: int,
|
||||
--short (-s)
|
||||
] {
|
||||
if $level >= (CRITICAL_LEVEL) {
|
||||
if $short {
|
||||
CRITICAL_LEVEL_PREFIX --short
|
||||
} else {
|
||||
CRITICAL_LEVEL_PREFIX
|
||||
}
|
||||
} else if $level >= (ERROR_LEVEL) {
|
||||
if $short {
|
||||
ERROR_LEVEL_PREFIX --short
|
||||
} else {
|
||||
ERROR_LEVEL_PREFIX
|
||||
}
|
||||
} else if $level >= (WARNING_LEVEL) {
|
||||
if $short {
|
||||
WARNING_LEVEL_PREFIX --short
|
||||
} else {
|
||||
WARNING_LEVEL_PREFIX
|
||||
}
|
||||
} else if $level >= (INFO_LEVEL) {
|
||||
if $short {
|
||||
INFO_LEVEL_PREFIX --short
|
||||
} else {
|
||||
INFO_LEVEL_PREFIX
|
||||
}
|
||||
} else {
|
||||
if $short {
|
||||
DEBUG_LEVEL_PREFIX --short
|
||||
} else {
|
||||
DEBUG_LEVEL_PREFIX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def current-log-level [] {
|
||||
let env_level = ($env.NU_LOG_LEVEL? | default (INFO_LEVEL))
|
||||
|
||||
try {
|
||||
$env_level | into int
|
||||
} catch {
|
||||
parse-string-level $env_level
|
||||
}
|
||||
}
|
||||
|
||||
def now [] {
|
||||
date now | date format "%Y-%m-%dT%H:%M:%S%.3f"
|
||||
}
|
||||
|
||||
def log-formatted [
|
||||
color: string,
|
||||
prefix: string,
|
||||
message: string
|
||||
] {
|
||||
print --stderr $"($color)(now)|($prefix)|(ansi u)($message)(ansi reset)"
|
||||
}
|
||||
|
||||
# Log a critical message
|
||||
export def critical [
|
||||
message: string, # A message
|
||||
--short (-s) # Whether to use a short prefix
|
||||
] {
|
||||
if (current-log-level) > (CRITICAL_LEVEL) {
|
||||
return
|
||||
}
|
||||
|
||||
let prefix = if $short {
|
||||
CRITICAL_LEVEL_PREFIX --short
|
||||
} else {
|
||||
CRITICAL_LEVEL_PREFIX
|
||||
}
|
||||
log-formatted (ansi red_bold) $prefix $message
|
||||
}
|
||||
|
||||
# Log an error message
|
||||
export def error [
|
||||
message: string, # A message
|
||||
--short (-s) # Whether to use a short prefix
|
||||
] {
|
||||
if (current-log-level) > (ERROR_LEVEL) {
|
||||
return
|
||||
}
|
||||
|
||||
let prefix = if $short {
|
||||
ERROR_LEVEL_PREFIX --short
|
||||
} else {
|
||||
ERROR_LEVEL_PREFIX
|
||||
}
|
||||
log-formatted (ansi red) $prefix $message
|
||||
}
|
||||
|
||||
# Log a warning message
|
||||
export def warning [
|
||||
message: string, # A message
|
||||
--short (-s) # Whether to use a short prefix
|
||||
] {
|
||||
if (current-log-level) > (WARNING_LEVEL) {
|
||||
return
|
||||
}
|
||||
|
||||
let prefix = if $short {
|
||||
WARNING_LEVEL_PREFIX --short
|
||||
} else {
|
||||
WARNING_LEVEL_PREFIX
|
||||
}
|
||||
log-formatted (ansi yellow) $prefix $message
|
||||
}
|
||||
|
||||
# Log an info message
|
||||
export def info [
|
||||
message: string, # A message
|
||||
--short (-s) # Whether to use a short prefix
|
||||
] {
|
||||
if (current-log-level) > (INFO_LEVEL) {
|
||||
return
|
||||
}
|
||||
|
||||
let prefix = if $short {
|
||||
INFO_LEVEL_PREFIX --short
|
||||
} else {
|
||||
INFO_LEVEL_PREFIX
|
||||
}
|
||||
log-formatted (ansi default) $prefix $message
|
||||
}
|
||||
|
||||
# Log a debug message
|
||||
export def debug [
|
||||
message: string, # A message
|
||||
--short (-s) # Whether to use a short prefix
|
||||
] {
|
||||
if (current-log-level) > (DEBUG_LEVEL) {
|
||||
return
|
||||
}
|
||||
|
||||
let prefix = if $short {
|
||||
DEBUG_LEVEL_PREFIX --short
|
||||
} else {
|
||||
DEBUG_LEVEL_PREFIX
|
||||
}
|
||||
log-formatted (ansi default_dimmed) $prefix $message
|
||||
}
|
||||
|
||||
# Log a message with a specific format and verbosity level
|
||||
#
|
||||
# Format reference:
|
||||
# - %MSG% will be replaced by $message
|
||||
# - %DATE% will be replaced by the timestamp of log in standard Nushell's log format: "%Y-%m-%dT%H:%M:%S%.3f"
|
||||
# - %LEVEL% will be replaced by the standard Nushell's log verbosity prefixes, e.g. "CRT"
|
||||
#
|
||||
# Examples:
|
||||
# - std log custom "my message" $"(ansi yellow)[%LEVEL%]MY MESSAGE: %MSG% [%DATE%](ansi reset)" (std log WARNING_LEVEL)
|
||||
export def custom [
|
||||
message: string, # A message
|
||||
format: string, # A format
|
||||
log_level: int # A log level
|
||||
--short (-s) # Whether to use a short prefix
|
||||
] {
|
||||
if (current-log-level) > ($log_level) {
|
||||
return
|
||||
}
|
||||
|
||||
let level = ((if $short {
|
||||
parse-int-level $log_level --short
|
||||
} else {
|
||||
parse-int-level $log_level
|
||||
}) | into string)
|
||||
|
||||
print --stderr ([
|
||||
["%MSG%" $message]
|
||||
["%DATE%" (now)]
|
||||
["%LEVEL%" $level]
|
||||
] | reduce --fold $format {
|
||||
|it, acc| $acc | str replace --all $it.0 $it.1
|
||||
})
|
||||
}
|
252
crates/nu-std/std/mod.nu
Normal file
252
crates/nu-std/std/mod.nu
Normal file
@ -0,0 +1,252 @@
|
||||
# std.nu, `used` to load all standard library components
|
||||
|
||||
export-env {
|
||||
use dirs.nu []
|
||||
}
|
||||
|
||||
export use testing.nu *
|
||||
|
||||
use dt.nu [datetime-diff, pretty-print-duration]
|
||||
|
||||
# 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 path_name = if "PATH" in $env { "PATH" } else { "Path" }
|
||||
|
||||
let-env $path_name = (
|
||||
$env
|
||||
| get $path_name
|
||||
| if $append { append $paths }
|
||||
else { prepend $paths }
|
||||
)
|
||||
|
||||
if $ret {
|
||||
$env | get $path_name
|
||||
}
|
||||
}
|
||||
|
||||
# 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)
|
||||
--no-strip: bool # do not strip ANSI escape sequences from a string
|
||||
--expand (-e): bool # auto-expand the data given as input
|
||||
] {
|
||||
let input = (
|
||||
$in
|
||||
| if $expand { table --expand } else { table }
|
||||
| into string
|
||||
| if $no_strip {} else { ansi strip }
|
||||
)
|
||||
|
||||
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 $"(ansi white_italic)(ansi white_dimmed)saved to clipboard(ansi reset)"
|
||||
}
|
||||
|
||||
if (not $no_notify) and ($nu.os-info.name == linux) {
|
||||
notify-send "std clip" "saved to clipboard"
|
||||
}
|
||||
}
|
||||
|
||||
# convert an integer amount of nanoseconds to a real duration
|
||||
def "from ns" [] {
|
||||
[$in "ns"] | str join | into duration
|
||||
}
|
||||
|
||||
# run a piece of `nushell` code multiple times and measure the time of execution.
|
||||
#
|
||||
# this command returns a benchmark report of the following form:
|
||||
# ```
|
||||
# record<
|
||||
# mean: duration
|
||||
# std: duration
|
||||
# times: list<duration>
|
||||
# >
|
||||
# ```
|
||||
#
|
||||
# > **Note**
|
||||
# > `std bench --pretty` will return a `string`.
|
||||
#
|
||||
# # Examples
|
||||
# measure the performance of simple addition
|
||||
# > std bench { 1 + 2 } -n 10 | table -e
|
||||
# ╭───────┬────────────────────╮
|
||||
# │ mean │ 4µs 956ns │
|
||||
# │ std │ 4µs 831ns │
|
||||
# │ │ ╭───┬────────────╮ │
|
||||
# │ times │ │ 0 │ 19µs 402ns │ │
|
||||
# │ │ │ 1 │ 4µs 322ns │ │
|
||||
# │ │ │ 2 │ 3µs 352ns │ │
|
||||
# │ │ │ 3 │ 2µs 966ns │ │
|
||||
# │ │ │ 4 │ 3µs │ │
|
||||
# │ │ │ 5 │ 3µs 86ns │ │
|
||||
# │ │ │ 6 │ 3µs 84ns │ │
|
||||
# │ │ │ 7 │ 3µs 604ns │ │
|
||||
# │ │ │ 8 │ 3µs 98ns │ │
|
||||
# │ │ │ 9 │ 3µs 653ns │ │
|
||||
# │ │ ╰───┴────────────╯ │
|
||||
# ╰───────┴────────────────────╯
|
||||
#
|
||||
# get a pretty benchmark report
|
||||
# > std bench { 1 + 2 } --pretty
|
||||
# 3µs 125ns +/- 2µs 408ns
|
||||
export def bench [
|
||||
code: closure # the piece of `nushell` code to measure the performance of
|
||||
--rounds (-n): int = 50 # the number of benchmark rounds (hopefully the more rounds the less variance)
|
||||
--verbose (-v): bool # be more verbose (namely prints the progress)
|
||||
--pretty: bool # shows the results in human-readable format: "<mean> +/- <stddev>"
|
||||
] {
|
||||
let times = (
|
||||
seq 1 $rounds | each {|i|
|
||||
if $verbose { print -n $"($i) / ($rounds)\r" }
|
||||
timeit { do $code } | into int | into decimal
|
||||
}
|
||||
)
|
||||
|
||||
if $verbose { print $"($rounds) / ($rounds)" }
|
||||
|
||||
let report = {
|
||||
mean: ($times | math avg | from ns)
|
||||
std: ($times | math stddev | from ns)
|
||||
times: ($times | each { from ns })
|
||||
}
|
||||
|
||||
if $pretty {
|
||||
$"($report.mean) +/- ($report.std)"
|
||||
} else {
|
||||
$report
|
||||
}
|
||||
}
|
||||
|
||||
# print a banner for nushell, with information about the project
|
||||
#
|
||||
# Example:
|
||||
# an example can be found in [this asciinema recording](https://asciinema.org/a/566513)
|
||||
export def banner [] {
|
||||
let dt = (datetime-diff (date now) 2019-05-10T09:59:12-07:00)
|
||||
$"(ansi green) __ ,(ansi reset)
|
||||
(ansi green) .--\(\)°'.' (ansi reset)Welcome to (ansi green)Nushell(ansi reset),
|
||||
(ansi green)'|, . ,' (ansi reset)based on the (ansi green)nu(ansi reset) language,
|
||||
(ansi green) !_-\(_\\ (ansi reset)where all data is structured!
|
||||
|
||||
Please join our (ansi purple)Discord(ansi reset) community at (ansi purple)https://discord.gg/NtAbbGn(ansi reset)
|
||||
Our (ansi green_bold)GitHub(ansi reset) repository is at (ansi green_bold)https://github.com/nushell/nushell(ansi reset)
|
||||
Our (ansi green)Documentation(ansi reset) is located at (ansi green)https://nushell.sh(ansi reset)
|
||||
(ansi cyan)Tweet(ansi reset) us at (ansi cyan_bold)@nu_shell(ansi reset)
|
||||
Learn how to remove this at: (ansi green)https://nushell.sh/book/configuration.html#remove-welcome-message(ansi reset)
|
||||
|
||||
It's been this long since (ansi green)Nushell(ansi reset)'s first commit:
|
||||
(pretty-print-duration $dt)
|
||||
|
||||
Startup Time: ($nu.startup-time)
|
||||
"
|
||||
}
|
437
crates/nu-std/std/testing.nu
Normal file
437
crates/nu-std/std/testing.nu
Normal file
@ -0,0 +1,437 @@
|
||||
##################################################################################
|
||||
#
|
||||
# Module testing
|
||||
#
|
||||
# Assert commands and test runner.
|
||||
#
|
||||
##################################################################################
|
||||
export use log.nu
|
||||
|
||||
# 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: $span.start,
|
||||
end: $span.end
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Negative assertion
|
||||
#
|
||||
# If the condition is not false, it generates an error.
|
||||
#
|
||||
# # Examples
|
||||
#
|
||||
# >_ assert (42 == 3)
|
||||
# >_ assert (3 == 3)
|
||||
# Error:
|
||||
# × Assertion failed:
|
||||
# ╭─[myscript.nu:11:1]
|
||||
# 11 │ assert (42 == 3)
|
||||
# 12 │ assert (3 == 3)
|
||||
# · ───┬────
|
||||
# · ╰── It is not false.
|
||||
# 13 │
|
||||
# ╰────
|
||||
#
|
||||
#
|
||||
# The --error-label flag can be used if you want to create a custom assert command:
|
||||
# ```
|
||||
# def "assert not even" [number: int] {
|
||||
# assert not ($number mod 2 == 0) --error-label {
|
||||
# start: (metadata $number).span.start,
|
||||
# end: (metadata $number).span.end,
|
||||
# text: $"($number) is an even number",
|
||||
# }
|
||||
# }
|
||||
# ```
|
||||
#
|
||||
export def "assert not" [
|
||||
condition: bool, # Condition, which should be false
|
||||
message?: string, # Optional error message
|
||||
--error-label: record # Label for `error make` if you want to create a custom assert
|
||||
] {
|
||||
if $condition {
|
||||
let span = (metadata $condition).span
|
||||
error make {
|
||||
msg: ($message | default "Assertion failed."),
|
||||
label: ($error_label | default {
|
||||
text: "It is not false.",
|
||||
start: $span.start,
|
||||
end: $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)'."
|
||||
}
|
||||
}
|
||||
|
||||
# show a test record in a pretty way
|
||||
#
|
||||
# `$in` must be a `record<file: string, module: string, name: string, pass: bool>`.
|
||||
#
|
||||
# the output would be like
|
||||
# - "<indentation> x <module> <test>" all in red if failed
|
||||
# - "<indentation> s <module> <test>" all in yellow if skipped
|
||||
# - "<indentation> <module> <test>" all in green if passed
|
||||
def show-pretty-test [indent: int = 4] {
|
||||
let test = $in
|
||||
|
||||
[
|
||||
(" " * $indent)
|
||||
(match $test.result {
|
||||
"pass" => { ansi green },
|
||||
"skip" => { ansi yellow },
|
||||
_ => { ansi red }
|
||||
})
|
||||
(match $test.result {
|
||||
"pass" => " ",
|
||||
"skip" => "s",
|
||||
_ => { char failed }
|
||||
})
|
||||
" "
|
||||
$"($test.module) ($test.test)"
|
||||
(ansi reset)
|
||||
] | str join
|
||||
}
|
||||
|
||||
def throw-error [error: record] {
|
||||
error make {
|
||||
msg: $"(ansi red)($error.msg)(ansi reset)"
|
||||
label: {
|
||||
text: ($error.label)
|
||||
start: $error.span.start
|
||||
end: $error.span.end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Run Nushell tests
|
||||
#
|
||||
# It executes exported "test_*" commands in "test_*" modules
|
||||
export def 'run-tests' [
|
||||
--path: path, # Path to look for tests. Default: current directory.
|
||||
--module: string, # Test module to run. Default: all test modules found.
|
||||
--test: string, # Individual test to run. Default: all test command found in the files.
|
||||
--list, # list the selected tests without running them.
|
||||
] {
|
||||
let module_search_pattern = ('**' | path join ({
|
||||
stem: ($module | default "test_*")
|
||||
extension: nu
|
||||
} | path join))
|
||||
|
||||
let path = ($path | default $env.PWD)
|
||||
|
||||
if not ($path | path exists) {
|
||||
throw-error {
|
||||
msg: "directory_not_found"
|
||||
label: "no such directory"
|
||||
span: (metadata $path | get span)
|
||||
}
|
||||
}
|
||||
|
||||
if not ($module | is-empty) {
|
||||
try { ls ($path | path join $module_search_pattern) | null } catch {
|
||||
throw-error {
|
||||
msg: "module_not_found"
|
||||
label: $"no such module in ($path)"
|
||||
span: (metadata $module | get span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tests = (
|
||||
ls ($path | path join $module_search_pattern)
|
||||
| each {|row| {file: $row.name name: ($row.name | path parse | get stem)}}
|
||||
| upsert commands {|module|
|
||||
^$nu.current-exe -c $'use `($module.file)` *; $nu.scope.commands | select name module_name | to nuon'
|
||||
| from nuon
|
||||
| where module_name == $module.name
|
||||
| get name
|
||||
}
|
||||
| upsert test {|module| $module.commands | where ($it | str starts-with "test_") }
|
||||
| upsert setup {|module| "setup" in $module.commands }
|
||||
| upsert teardown {|module| "teardown" in $module.commands }
|
||||
| reject commands
|
||||
| flatten
|
||||
| rename file module test
|
||||
)
|
||||
|
||||
let tests_to_run = (if not ($test | is-empty) {
|
||||
$tests | where test == $test
|
||||
} else if not ($module | is-empty) {
|
||||
$tests | where module == $module
|
||||
} else {
|
||||
$tests
|
||||
})
|
||||
|
||||
if $list {
|
||||
return ($tests_to_run | select module test file)
|
||||
}
|
||||
|
||||
if ($tests_to_run | is-empty) {
|
||||
error make --unspanned {msg: "no test to run"}
|
||||
}
|
||||
|
||||
let tests = (
|
||||
$tests_to_run
|
||||
| group-by module
|
||||
| transpose name tests
|
||||
| each {|module|
|
||||
log info $"Running tests in module ($module.name)"
|
||||
$module.tests | each {|test|
|
||||
log debug $"Running test ($test.test)"
|
||||
|
||||
let context_setup = if $test.setup {
|
||||
$"use `($test.file)` setup; let context = \(setup\)"
|
||||
} else {
|
||||
"let context = {}"
|
||||
}
|
||||
|
||||
let context_teardown = if $test.teardown {
|
||||
$"use `($test.file)` teardown; $context | teardown"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
let nu_script = $'
|
||||
($context_setup)
|
||||
use `($test.file)` ($test.test)
|
||||
try {
|
||||
$context | ($test.test)
|
||||
($context_teardown)
|
||||
} catch { |err|
|
||||
($context_teardown)
|
||||
if $err.msg == "ASSERT:SKIP" {
|
||||
exit 2
|
||||
} else {
|
||||
$err | get raw
|
||||
}
|
||||
}
|
||||
'
|
||||
^$nu.current-exe -c $nu_script
|
||||
|
||||
let result = match $env.LAST_EXIT_CODE {
|
||||
0 => "pass",
|
||||
2 => "skip",
|
||||
_ => "fail",
|
||||
}
|
||||
if $result == "skip" {
|
||||
log warning $"Test case ($test.test) is skipped"
|
||||
}
|
||||
$test | merge ({result: $result})
|
||||
}
|
||||
}
|
||||
| flatten
|
||||
)
|
||||
|
||||
if not ($tests | where result == "fail" | is-empty) {
|
||||
let text = ([
|
||||
$"(ansi purple)some tests did not pass (char lparen)see complete errors above(char rparen):(ansi reset)"
|
||||
""
|
||||
($tests | each {|test| ($test | show-pretty-test 4)} | str join "\n")
|
||||
""
|
||||
] | str join "\n")
|
||||
|
||||
error make --unspanned { msg: $text }
|
||||
}
|
||||
}
|
206
crates/nu-std/std/xml.nu
Normal file
206
crates/nu-std/std/xml.nu
Normal file
@ -0,0 +1,206 @@
|
||||
# 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'})
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user