1
Fork 0
mirror of https://github.com/RGBCube/nu_scripts synced 2025-08-03 07:37:47 +00:00

Deprecate the nu_scripts version of stdlib-candidate (#1042)

Deprecates the existing `stdlib-candidate` directories in `nu_scripts`
since `std-rfc` has now transitioned to the main repo. Updates readme
and renamed directories.
This commit is contained in:
Douglas 2025-02-09 15:34:40 -05:00 committed by GitHub
parent a31f8490fb
commit 5869e0b529
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 18 additions and 40 deletions

View file

@ -0,0 +1,3 @@
# std-lib candidate
This is the deprecated `stdlib-candidate` module. Please see the [new version](https://github.com/nushell/nu_scripts/tree/main/stdlib-candidate). If you have a submission here, you can move it to the new repo. Please refer to the [README](https://github.com/nushell/nu_scripts/blob/main/stdlib-candidate/README.md) there for the process.

View file

@ -0,0 +1,8 @@
{
name: "std-rfc"
description: "Official candidates for Nushell standard library"
documentation: "https://github.com/nushell/nu_scripts/blob/main/stdlib-candidate/std-rfc/README.md"
license: "https://github.com/nushell/nu_scripts/blob/main/LICENSE"
version: 0.3.0
type: "module"
}

View file

@ -0,0 +1,83 @@
# A proposal for improving the original `std bench` command by @amtoine
# https://github.com/nushell/nushell/blob/31f3d2f6642b585f0d88192724723bf0ce330ecf/crates/nu-std/std/mod.nu#L134
# round an integer amount of nanoseconds to 4th digit and convert to a real duration
def "from ns" [] {
if $in == 0 {0} else { #math log errors on 0
math round -p (3 - ($in | math log 10 | math floor)) # rounds to 4th digit including, with maximum relative err 0.05%
| math round # second math round as a fix for `> 123456 | math round -p -5` = 99999.99999999999
}
| 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
# min: duration
# max: duration
# std: duration
# times: list<duration>
# >
# ```
#
# > **Note**
# > `std bench --pretty` will return a `string`.
#
# # Examples
# measure the performance of simple addition
# > bench {1 + 2}
# ╭──────┬───────────╮
# │ mean │ 716ns │
# │ min │ 583ns │
# │ max │ 4µs 541ns │
# │ std │ 549ns │
# ╰──────┴───────────╯
#
# get a pretty benchmark report
# > bench {1 + 2} --pretty
# 716ns ± 76.67%
#
# measure the performance of simple addition with 1ms delay and output each timing
# > bench {sleep 1ms; 1 + 2} --rounds 2 --timings | table -e
# ╭───────┬───────────────╮
# │ mean │ 1ms 272µs │
# │ min │ 1ms 259µs │
# │ max │ 1ms 285µs │
# │ std │ 13µs 370ns │
# │ │ ╭───────────╮ │
# │ times │ │ 1ms 285µs │ │
# │ │ │ 1ms 259µs │ │
# │ │ ╰───────────╯ │
# ╰───────┴───────────────╯
export def main [
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) # be more verbose (namely prints the progress)
--pretty # shows the results in human-readable format: "<mean> +/- <stddev>"
--timings (-t) # list all rounds' timings in a `times` field
] {
let times = seq 1 $rounds
| each {|i|
if $verbose { print -n $"($i) / ($rounds)\r" }
timeit { do $code } | into int | into float
}
if $verbose { print $"($rounds) / ($rounds)" }
{
mean: ($times | math avg | from ns)
min: ($times | math min | from ns)
max: ($times | math max | from ns)
std: ($times | math stddev | from ns)
}
| if $pretty {
$"($in.mean) ± (($in.std / $in.mean) * 100 | math round -p 2)%"
} else {
if $timings {
merge { times: ($times | each { from ns }) }
} else {}
}
}

View file

@ -0,0 +1,49 @@
# Rename bulk input files in parallel using a closure.
#
# The reason behind this command is quite simple:
# - Sometimes one receives a bunch of files with integer ids: 1, 2, 3, ...
# - These ids come rarely with padding... i.e. 1 instead of 001 when there are 3-digit ids
# - This means that file with id 9 will be sorted way after file with id 1000
#
# This command allows to do such a task!
#
# Examples:
# Rename `.mise.toml` files to `.mise.local.toml` recursively
# > glob **/.mise.toml | bulk-rename { str append .local }
#
# Rename files in `/foo` with a name that has an id to have 3 digits with 0-padding
# > ls /foo | bulk-rename { |path|
# if $path.input.type == file {
# $path.stem | parse "some_format_{id}"
# | get 0
# | update id { fill --alignment r --character 0 --width 3 }
# | $"some_format_($in.id)"
# }
# # else skip dirs
# }
export def main [
update_stem: closure, # The code to rename the file stem: receives the old stem as input and a record param with both `stem` and `input` keys
--verbose (-v), # Show which files were renamed, if any
--no-execute (-n) # Do not make any changes; add --verbose to see what would be made
]: [list<any> -> nothing, list<any> -> table<old: path new: path>] {
let renamed = par-each --keep-order { |input|
let update_or_keep_stem = { |parts|
do $update_stem { stem: $in input: $input } | default $parts.stem
}
let old = if ($input | describe) == string {
$input
} else {
$input.name # convenience for ls
}
let new = $old | path parse | update stem $update_or_keep_stem | path join
if $new != $old {
if not $no_execute {
mv --force --verbose=$verbose $old $new
}
{ old: $old new: $new }
}
}
if $verbose {
$renamed
}
}

View file

@ -0,0 +1,11 @@
# A command to flatten many levels by using recursion
# by @jturner 2/10/21
# Example: sys | flatter 3
def flatter [levels:int] {
let input = $in
if $levels > 0 {
$input | columns | reduce -f $input {|it acc| $acc | flatten $it } | flatter ($levels - 1)
} else {
$input
}
}

View file

@ -0,0 +1,44 @@
# A small script to auto-update nushell in linux
# requires nushell 0.36.0 or greater
def get-latest-linux [] {
# fetch the information about the latest release
let metadata = (http get https://api.github.com/repos/nushell/nushell/releases/latest)
let release_name = ($metadata | get name | split row ' ' | first)
# get the body that shows information about this release
let body = ($metadata | get body)
# find the linux download
let asset_info = ($metadata | get assets | where name =~ 'x86_64-linux-gnu-full.tar.gz' | first)
# construct the url
let download_url = ($asset_info | get browser_download_url)
let file_name = ($asset_info | get name)
# tell you what i'm doing
print $"Release name is ($release_name)(char newline)(char newline)"
print $"($body)(char newline)(char newline)Downloading and following redirects ..."
# fetch follows redirects now
http get $download_url | save $file_name
# tell you what i'm doing
print $"(char newline)Extracting ($file_name) to /tmp(char newline)"
# extract the tar file to the temp folder
tar -xf ($file_name) -C /tmp
# parse the $file_name to get the folder
# echo nu_0_31_0_linux.tar.gz | path parse | get stem | path parse | get stem
# echo nu_0_31_0_linux.tar.gz | split column '.' | get Column1
# now get the file name using the path commands. it was a little tricky because
# there are two extensions .tar and .gz
let root_file_name = ($file_name | path parse | get stem | path parse | get stem)
# update our progress
print $"Copying files from /tmp/($root_file_name)/*/* to ~/.cargo/bin(char newline)"
# this is for testing so it doesn't overwrite my real nu. this should really
# be a parameter
mkdir release
# construct the copy from and to paths
let cp_from_path = $"/tmp/($root_file_name)/**/*"
let cp_to_path = "./release"
# actually run the cp command
# we may want to make this overwrite and not prompt
cp ($cp_from_path) ($cp_to_path)
# exec ~/.cargo/bin/nu
print $"Starting nushell(char nl)"
exec ./release/nu
}

View file

@ -0,0 +1,73 @@
# replace all insignificant digits with 0
#
# | Significant Digits | Maximum Relative Error |
# |--------------------|------------------------|
# | 1 | 50% |
# | 2 | 5% |
# | 3 | 0.5% |
# | 4 | 0.05% |
# | 5 | 0.005% |
# | 6 | 0.0005% |
#
# > 0.0000012346789 | significant-digits 2
# 0.0000012
#
# > 1.2346789 | significant-digits 3
# 1.23
#
# > 123456789.89 | significant-digits 5
# 123450000
#
# > 1sec / 3 | math significant-digits
# 333ms
export def 'significant-digits' [
n: int = 3 # a number of significant digits
]: [int -> int, float -> float, duration -> duration] {
let $input = $in
let $type = $input | describe
let $num = match $type {
'duration' => {$input | into int}
_ => {$input}
}
let insignif_position = $num
| if $in == 0 {
0 # it's impoosbile to calculate `math log` from 0, thus 0 errors here
} else {
math abs
| math log 10
| math floor
| $n - 1 - $in
}
# See the note below the code for an explanation of the construct used.
let scaling_factor = 10 ** ($insignif_position | math abs)
let res = $num
| if $insignif_position > 0 {
$in * $scaling_factor
} else {
$in / $scaling_factor
}
| math floor
| if $insignif_position <= 0 {
$in * $scaling_factor
} else {
$in / $scaling_factor
}
match $type {
'duration' => {$res | into duration}
'int' => {$res | into int}
_ => {$res}
}
}
# I started with `10.0 ** $insignif_position`, but it was sometimes producing
# not rounded digits in `$num / $scaling_factor` if `$insignif_position` was negative
# like with
# > 3456789 | math round --precision -5
# 3499999.9999999995
# so I use what I have now.

View file

@ -0,0 +1,12 @@
# Modules
export module record/
export module str/
export module math/
# Commands
export use bulk-rename.nu *
export use set-env.nu *
export use bench.nu
export use script-parsing.nu [ parse-arg ]
export use conversions *
export use tables *

View file

@ -0,0 +1,110 @@
# This is a port of powershells $psstyle
# found here https://github.com/PowerShell/PowerShell/blob/5f3dd938b792e1a395fd011ac1461246db7c0e1f/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs
# Example Usage
# echo (fg_blue) Darren (relet) ' ' Schroeder | str collect
# More examples in the short_list.nu script
alias fmt_error = ansi -e '31;1m'
alias fmt_warning = ansi -e '33;1m'
alias fmt_verbose = ansi -e '33;1m'
alias fmt_debug = ansi -e '33;1m'
alias fmt_format_accent = ansi -e '32;1m'
alias fmt_error_accent = ansi -e '36;1m'
alias relet = ansi -e '0m' # really reset but there are external commands for reset already
alias blink_off = ansi -e '25m'
alias blink_on = ansi -e '5m'
alias bold_off = ansi -e '22m'
alias bold_on = ansi -e '1m'
alias hidden_on = ansi -e '8m'
alias hidden_off = ansi -e '28m'
alias reverse_on = ansi -e '7m'
alias reverse_off = ansi -e '27m'
alias italic_off = ansi -e '23m'
alias italic_on = ansi -e '3m'
alias underline_off = ansi -e '24m'
alias underline_on = ansi -e '4m'
alias strikethrough_off = ansi -e '29m'
alias strikethrough_on = ansi -e '9m'
# this doesn't work right. i need to have an (ansi escape) defined but i don't think there is one
#def format_hyperlink [link text] {echo [(ansi -o '8;;') $link '\' $text (ansi -o '8;;') '\'] | str collect }
alias fg_black = ansi -e '30m'
alias fg_red = ansi -e '31m'
alias fg_green = ansi -e '32m'
alias fg_yellow = ansi -e '33m'
alias fg_blue = ansi -e '34m'
alias fg_magenta = ansi -e '35m'
alias fg_purple = ansi -e '35m'
alias fg_cyan = ansi -e '36m'
alias fg_white = ansi -e '37m'
alias fg_dark_gray = ansi -e '90m'
alias fg_light_black = ansi -e '90m'
alias fg_light_red = ansi -e '91m'
alias fg_light_green = ansi -e '92m'
alias fg_light_yellow = ansi -e '93m'
alias fg_light_blue = ansi -e '94m'
alias fg_light_magenta = ansi -e '95m'
alias fg_light_purple = ansi -e '95m'
alias fg_light_cyan = ansi -e '96m'
alias fg_light_gray = ansi -e '97m'
alias fg_light_white = ansi -e '97m'
# construct a rgb foreground color
def fg_from_rgb [
red:int # red component 0-255
green:int # green component 0-255
blue:int # blue component 0-255
] {
echo [(ansi -e '38;2;') $red ';' $green ';' $blue 'm'] | str join
}
alias bg_black = ansi -e '40m'
alias bg_red = ansi -e '41m'
alias bg_green = ansi -e '42m'
alias bg_yellow = ansi -e '43m'
alias bg_blue = ansi -e '44m'
alias bg_magenta = ansi -e '45m'
alias bg_purple = ansi -e '45m'
alias bg_cyan = ansi -e '46m'
alias bg_white = ansi -e '47m'
alias bg_dark_gray = ansi -e '100m'
alias bg_light_black = ansi -e '100m'
alias bg_light_red = ansi -e '101m'
alias bg_light_green = ansi -e '102m'
alias bg_light_yellow = ansi -e '103m'
alias bg_light_magenta = ansi -e '105m'
alias bg_light_purple = ansi -e '105m'
alias bg_light_blue = ansi -e '104m'
alias bg_light_cyan = ansi -e '106m'
alias bg_light_gray = ansi -e '107m'
alias bg_light_white = ansi -e '107m'
alias bg_expand = ansi -e 'K'
# construct a rgb background color
def bg_from_rgb [
red:int # red component 0-255
green:int # green component 0-255
blue:int # blue component 0-255
] {
$"(ansi -e '48;2;')($red);($green);($blue)m"
}
# Get a foreground color from an index value 0-255
def fg_from_index [
idx:int # index value 0-255
] {
$"(ansi -e '38;5;')($idx)m"
}
# Get a background color from an index value 0-255
def bg_from_index [
idx:int # index value 0-255
] {
$"(ansi -e '48;5;')($idx)m"
}

View file

@ -0,0 +1,82 @@
# A print command that concatenates arguments together with an optional separator
# By default there will be no newline
def print1 [
--separator(-s):any # Optional separator (not yet flagged as optional?)
...rest # All of the parameters
] {
let is_empty = ($separator | is-empty)
let num_of_rest = ($rest | length)
$rest | enumerate | each { |param|
if $is_empty {
$param.item
} else {
if $num_of_rest > ($param.index + 1) {
$"($param.item)($separator)"
} else {
$param.item
}
}
} | into string | str join
}
# > print 1 2 3 "four" -s '--'
# 1--2--3--four
# > print 1 2 3 "four"
# 123four
# An alternate print command that concatenates arguments together with an optional separator.
# This one uses str collect instead of build-string.
# By default there will be no newline
def print2 [
--separator(-s):any # Optional separator (not yet flagged as optional?)
...rest # All of the parameters
] {
let is_empty = ($separator | is-empty)
let num_of_rest = ($rest | length)
if $is_empty {
$rest | into string | str join
} else {
$rest | into string | str join $separator
}
}
# A print command that concatenates arguments together with an optional separator.
# This print command will also concatenate tables like [1 2 3] as well as most other primitives
# since the into string command has been updated with wider support.
def print3 [
--separator(-s):any # Optional separator (not yet flagged as optional?)
--flat(-f) # If tables are found, flatten them
...rest # All of the parameters
] {
let sep_empty = ($separator | is-empty)
let num_of_rest = ($rest | length)
let flat = ($flat | is-empty)
$rest | enumerate | each { |param|
if $sep_empty {
if ((echo $param.item | str length) > 1) and $flat {
let flatter = ($param.item | flatten | into string | str join)
$flatter
} else {
$param.item
}
} else {
if $num_of_rest > ($param.index + 1) {
if ($param.item | length) > 1 and $flat {
let flatter = ($param.item | flatten | into string | str join $separator)
$"($flatter)($separator)"
} else {
$"($param.item)($separator)"
}
} else {
if ($param.item | length) > 1 and $flat {
let flatter = ($param.item | flatten | into string | str join $separator)
$flatter
} else {
$param.item
}
}
}
} | str join
}

View file

@ -0,0 +1,121 @@
# Record module
A module to manipulate nu's records.
## `record list merge`
### Input/output types:
|#|input|output|
|-|-|-|
|1|list\<record>|record|
### Example
```nu
> [{a:1} {b:2} {c:3}] | record list merge
╭───┬───╮
│ a │ 1 │
│ b │ 2 │
│ c │ 3 │
╰───┴───╯
```
## `record filter-name predicate`
Filter a record by validating fields name with a predicate.
### Input/output types:
|#|input|output|
|-|-|-|
|1|record|record|
## Arguments
* `pred`: Predicate closure that checks fields name
### Example
```nu
> $env | record filter-name predicate { $in | str contains VS}
╭───────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ VSCODE_GIT_ASKPASS_EXTRA_ARGS │ --ms-enable-electron-run-as-node │
│ VSCODE_GIT_ASKPASS_MAIN │ /Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass-main.js │
│ VSCODE_GIT_ASKPASS_NODE │ /Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin) │
│ VSCODE_GIT_IPC_HANDLE │ /var/folders/_x/25cgjd3n2sn62x6jfc9ccjjw0000gn/T/vscode-git-56538693f8.sock │
╰───────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
## `record filter-name text`
Filter a record by validating fields name with text.
### Input/output types:
|#|input|output|
|-|-|-|
|1|record|record|
## Arguments
* `filter`: Text to match with
* `--regex(-r)`: Match by regex
### Examples
```nu
> $env | record filter-name text VS
╭───────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ VSCODE_GIT_ASKPASS_EXTRA_ARGS │ --ms-enable-electron-run-as-node │
│ VSCODE_GIT_ASKPASS_MAIN │ /Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass-main.js │
│ VSCODE_GIT_ASKPASS_NODE │ /Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin) │
│ VSCODE_GIT_IPC_HANDLE │ /var/folders/_x/25cgjd3n2sn62x6jfc9ccjjw0000gn/T/vscode-git-56538693f8.sock │
╰───────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> $env | record filter-name text --regex V.*S
╭───────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │ ╭──────┬──────────────────────────────────╮ │
│ ENV_CONVERSIONS │ │ │ ╭─────────────┬────────────────╮ │ │
│ │ │ PATH │ │ from_string │ <Closure 1399> │ │ │
│ │ │ │ │ to_string │ <Closure 1401> │ │ │
│ │ │ │ ╰─────────────┴────────────────╯ │ │
│ │ │ │ ╭─────────────┬────────────────╮ │ │
│ │ │ Path │ │ from_string │ <Closure 1403> │ │ │
│ │ │ │ │ to_string │ <Closure 1405> │ │ │
│ │ │ │ ╰─────────────┴────────────────╯ │ │
│ │ ╰──────┴──────────────────────────────────╯ │
│ LC_TERMINAL_VERSION │ 3.4.22 │
│ NU_VERSION │ 0.87.0 │
│ PROMPT_INDICATOR_VI_INSERT │ <Closure 1395>
│ TERM_PROGRAM_VERSION │ 1.84.2 │
│ VSCODE_GIT_ASKPASS_EXTRA_ARGS │ --ms-enable-electron-run-as-node │
│ VSCODE_GIT_ASKPASS_MAIN │ /Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass-main.js │
│ VSCODE_GIT_ASKPASS_NODE │ /Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin) │
│ VSCODE_GIT_IPC_HANDLE │ /var/folders/_x/25cgjd3n2sn62x6jfc9ccjjw0000gn/T/vscode-git-56538693f8.sock │
╰───────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
## `record filter-value predicate`
Filter a record by validating fields value with predicate.
### Input/output types:
|#|input|output|
|-|-|-|
|1|record|record|
## Arguments
* `pred`: Predicate closure that checks fields value
### Example
```nu
> {a:1 b:2 c:3 d:4} | record filter-value predicate { $in mod 2 == 0 }
╭───┬───╮
│ b │ 2 │
│ d │ 4 │
╰───┴───╯
```

View file

@ -0,0 +1,46 @@
# Merge a list of records
export def "list merge" []: list<record> -> record {
let list = $in
mut result = {}
for $obj in $list {
$result = ($result | merge $obj)
}
$result
}
# Filter fields name by predicate
export def "filter-name predicate" [
pred: closure # Predicate closure that checks fields name
]: record -> record {
let $obj_input = $in
$obj_input
| columns
| where { $in | do $pred }
| each {|input|
{ $input: ($obj_input | get $input) }
}
| list merge
}
# Filter fields name by text checking
export def "filter-name text" [
filter: string # Text to match with
--regex(-r) # Match by regex
]: record -> record {
let obj = $in
$obj | filter-name predicate { not ($in | (if $regex {find -r $filter} else {find $filter}) | is-empty) }
}
# Filter fields value by predicate
export def "filter-value predicate" [
pred: closure # Predicate closure that checks fields value
]: record -> record {
let $obj_input = $in
$obj_input
| columns
| where {|col| $obj_input | get $col | do $pred }
| each {|input|
{ $input: ($obj_input | get $input) }
}
| list merge
}

View file

@ -0,0 +1,61 @@
# helps parsing CLI arguments for Nushell scripts
#
# the following Nushell script does not make sense to be used as an external
# command because there is no such thing as a `list<string>` in Bash for
# instance.
# ```nushell
# def main [x: list<int>] {
# print $x
# }
# ```
#
# one needs to write something less strict at parse-time and thus looses type
# information...
# ```nushell
# def main [
# x: string, # list<int>
# ] {
# print $x
# }
# ```
#
# it's possible to write a much stronger script with `parse-arg`
# ```nushell
# def main [
# x: string, # list<int>
# ] {
# let x = $x | parse-arg (metadata $x).span "list<int>" # the script would crash if either
# # `$x: string` is not valid NUON or if
# # the resulting value is not a `list<int>`
# print $x # here, `$x` is a `list<int>` as intended
# }
# ```
export def parse-arg [
span: record<start: int, end: int>, # the span of the input variable
expected_type: string, # the expected type for the input variable
]: [ string -> any ] {
let val = try {
$in | from nuon
} catch {
error make {
msg: $"(ansi red_bold)invalid NUON(ansi reset)",
label: {
text: "invalid NUON",
span: $span,
},
}
}
if ($val | describe) != $expected_type {
error make {
msg: $"(ansi red_bold)bad type(ansi reset)",
label: {
text: $"type: ($val | describe)",
span: $span,
},
help: $"expected ($expected_type)",
}
}
$val
}

View file

@ -0,0 +1,37 @@
# Gracefully set an environment variable or merge a nested option.
#
# Examples:
# Set $env.NUPM_HOME
# > set-env NUPM_HOME $'($nu.home-path)/.local/share/nupm'
#
# Add to $env.NU_LIB_DIRS
# > set-env --append NU_LIB_DIRS $'($env.NUPM_HOME)/modules'
#
# Set a nested config option
# > set-env config.filesize.metric true
#
# Add a config hook
# > set-env -a config.hooks.pre_prompt 'ellie | print'
export def --env main [
field: cell-path # The environment variable name or nested option cell path
value: any # The value to set or append
--append (-a) # Append to the previous value or wrap in a new list
]: nothing -> nothing {
def 'get or' [default field] {
get --ignore-errors $field | default $default
}
let value = if $append {
$env | get or [] $field | append $value
} else {
$value
}
let field = $field | to text | split row .
let value = match $field {
[_] => $value
[$root, ..$field] => {
let field = $field | into cell-path
$env | get or {} $root | upsert $field $value
}
}
load-env { ($field | first): $value }
}

View file

@ -0,0 +1,93 @@
# Removes common indent from a multi-line string based on the number of spaces on the last line.
#
# A.k.a. Unindent
#
# Example - Two leading spaces are removed from all lines:
#
# > let s = "
# Heading
# Indented Line
# Another Indented Line
#
# Another Heading
# "
# > $a | str dedent
#
# Heading
# Indented Line
# Another Indented Line
#
# Another Heading
export def main []: string -> string {
let string = $in
if ($string | describe) != "string" {
let span = (view files | last)
error make {
msg: 'Requires multi-line string as pipeline input'
label: {
text: "err::pipeline_input"
span: {
start: $span.start
end: $span.end
}
}
}
}
if ($string !~ '^\s*\n') {
return (error make {
msg: 'First line must be empty'
})
}
if ($string !~ '\n\s*$') {
return (error make {
msg: 'Last line must contain only whitespace indicating the dedent'
})
}
# Get number of spaces on the last line
let indent = $string
| str replace -r '(?s).*\n( *)$' '$1'
| str length
# Skip the first and last lines
let lines = (
$string
| str replace -r '(?s)^[^\n]*\n(.*)\n[^\n]*$' '$1'
# Use `split` instead of `lines`, since `lines` will
# drop legitimate trailing empty lines
| split row "\n"
| enumerate
| rename lineNumber text
)
let spaces = ('' | fill -c ' ' -w $indent)
# Has to be done outside the replacement block or the error
# is converted to text. This is probably a Nushell bug, and
# this code can be recombined with the next iterator when
# the Nushell behavior is fixed.
for line in $lines {
if ($line.text !~ '^\s*$') and ($line.text | str index-of --range 0..($indent) $spaces) == -1 {
error make {
msg: $"Line ($line.lineNumber + 1) must be indented by ($indent) or more spaces."
}
}
}
$lines
| each {|line|
# Don't operate on lines containing only whitespace
if ($line.text !~ '^\s*$') {
$line.text | str replace $spaces ''
} else {
$line.text
}
}
| to text
# Remove the trailing newline which indicated
# indent level
| str replace -r '(?s)(.*)\n$' '$1'
}

View file

@ -0,0 +1,2 @@
export use xpend.nu *
export use dedent *

View file

@ -0,0 +1,39 @@
# Append a suffix to an input string or list of strings.
#
# Examples:
# Output 'hello world'
# > 'hello' | str append ' world'
#
# Output file names suffixed with '_world'
# > ls | get name | str append _world
export def append [
suffix: string
]: [string -> string, list<string> -> list<string>] {
let input = $in
let append = { $in + $suffix }
if ($input | describe) == string {
$input | do $append
} else {
$input | each $append
}
}
# Prepend a prefix to an input string or list of strings.
#
# Examples:
# Output 'hello world'
# > 'world' | str prepend 'hello '
#
# Output file names prefixed with 'hello_'
# > ls | get name | str prepend hello_
export def prepend [
prefix: string
]: [string -> string, list<string> -> list<string>] {
let input = $in
let prepend = { $prefix + $in }
if ($input | describe) == string {
$input | do $prepend
} else {
$input | each $prepend
}
}

View file

@ -0,0 +1,12 @@
use std assert
use ../std-rfc bench
export def "test bench-timings" [] {
let $test = bench {1 + 2} --rounds 3 --timings | get times | length
assert equal $test 3
}
export def "test bench-pretty" [] {
let $test = (bench {1 + 2} --rounds 3 --pretty) =~ '\d.* ± \d'
assert equal $test true
}

View file

@ -0,0 +1,98 @@
use std assert
use ../std-rfc 'bulk-rename'
const fixture = [
.gitignore
Cargo.toml
LICENSE
README.md
src
test.nu
]
export def 'test ls' [] {
let expects = [
.gitignore # hidden by ls
_Cargo.toml
_LICENSE
_README.md
_src
_test.nu
]
test $expects {
ls $in | bulk-rename { '_' + $in }
}
}
export def 'test --no-execute' [] {
test $fixture {
ls $in | bulk-rename --no-execute { '_' + $in }
}
}
export def 'test --verbose' [] {
let expects = [
# .gitignore unchanged
_Cargo.toml
_LICENSE
_README.md
_src
_test.nu
]
let renamed = test $fixture {
# Note: Currently failing due to Nushell core #13267
# Remove the 'sort' once it is fixed
# ls $in | bulk-rename --verbose --no-execute { '_' + $in }
ls $in | bulk-rename --verbose --no-execute { '_' + $in } | sort
}
assert equal ($renamed.new | each { path basename }) $expects
}
export def 'test skip-extensions' [] {
let expects = [
.gitignore
Cargo.toml
LICENSE.txt # changed
README.md
src.txt # changed
test.nu
]
test $expects {
ls $in | bulk-rename { |path|
if $path.input.name ends-with $path.stem {
$path.stem + .txt
}
}
}
}
export def 'test glob' [] {
let expects = [
LICENSE # skipped
_.gitignore
_Cargo.toml
_README.md
_test.nu
src # skipped
]
test $expects {
glob ($in | path join *.*) | bulk-rename { '_' + $in }
}
}
def test [expects: list<string> command: closure] {
let test_dir = $nu.temp-path | path join (random uuid)
def actual-files [] {
ls --all --short-names $test_dir | get name | sort
}
# before
mkdir $test_dir
$fixture | each { |name| touch ($test_dir | path join $name) }
assert equal (actual-files) $fixture
# test
let renamed = $test_dir | do $command
assert equal (actual-files) $expects
# after
rm --recursive --force $test_dir
$renamed
}

View file

@ -0,0 +1,29 @@
use std assert
use ../std-rfc/math
#[test]
export def "test significant-digits-decimals" [] {
assert equal (0.0000012346789 | math significant-digits 2) 0.0000012
assert equal (11 / 3 | math significant-digits 6) 3.66666
assert not equal (0.0000012346789 | math significant-digits 2) 0.00000123
assert equal (1.999999 | math significant-digits 1) 1.0
}
#[test]
export def "test significant-digits-duration" [] {
assert equal (2min / 7 | math significant-digits 3) 17100000000ns
assert equal (1sec | math significant-digits 3) 1000000000ns
}
#[test]
export def "test significant-digits-ints" [] {
assert equal (123456 | math significant-digits 2) 120000
}
export def "test significant-digits-0" [] {
assert equal (0 | math significant-digits 2) 0
}
export def "test significant-digits-negative" [] {
assert equal (-1.23456789 | math significant-digits 5) (-1.2346)
}

View file

@ -0,0 +1,9 @@
export module bulk-rename.nu
export module record.nu
export module str_xpend.nu
export module math.nu
export module bench.nu
export module script-parsing.nu
export module str_dedent.nu
export module conversions.nu
export module tables.nu

View file

@ -0,0 +1,20 @@
use std assert
use ../std-rfc record
export def "test list_merge" [] {
assert equal ([{a:1} {b:2} {c:3} {d:4}] | record list merge) {a:1 b:2 c:3 d:4}
}
export def "test filter-name predicate" [] {
assert equal ({aa:1 ab:2 ba:3 bb:4 ca:5 cb:6} | record filter-name predicate {$in | str contains a}) {aa:1 ab:2 ba:3 ca:5}
}
export def "test filter-name text" [] {
assert equal ({aa:1 ab:2 ba:3 bb:4 ca:5 cb:6} | record filter-name text a) {aa:1 ab:2 ba:3 ca:5}
assert equal ({aa:1 ab:2 ba:3 bb:4 ca:5 cb:6} | record filter-name text -r ^a) {aa:1 ab:2}
assert equal ({aa:1 ab:2 ba:3 bb:4 ca:5 cb:6} | record filter-name text -r ^A) {}
}
export def "test filter-value predicate" [] {
assert equal ({aa:1 ab:2 ba:3 bb:4 ca:5 cb:6} | record filter-value predicate { $in mod 2 == 0 }) {ab:2 bb:4 cb:6}
}

View file

@ -0,0 +1,37 @@
use std assert
use ../std-rfc parse-arg
const SPAN = { start: 0, end: 0 }
export def "test parse-arg ok" [] {
const TEST_CASES = [
[ input, type, expected ];
[ "123", "int", 123 ],
[ "[1, 2, 3]", "list<int>", [1, 2, 3] ],
[ "'spam'", "string", "spam" ],
[
"{ a: 1, b: 'egg', c: false }",
"record<a: int, b: string, c: bool>",
{ a: 1, b: 'egg', c: false },
],
]
for t in $TEST_CASES {
assert equal ($t.input | parse-arg $SPAN $t.type) $t.expected
}
}
export def "test parse-arg err" [] {
const TEST_CASES = [
[ input, type ];
[ "{ invalid NUON", "" ],
[ "[1, 2, 3]", "string" ],
]
for t in $TEST_CASES {
let msg = $"test case: input: '($t.input)', type: ($t.type)"
assert error { $t.input | parse-arg $SPAN $t.type } $msg
}
}

View file

@ -0,0 +1,144 @@
use std assert
use ../std-rfc str
export def "test str dedent" [] {
# Test 1:
# Should start with "Heading" in the first character position
# Should not end with a line-break
# The blank line has no extra spaces
assert equal (
do {
let s = "
Heading
one
two
"
$s | str dedent
}
) "Heading\n\n one\n two"
# Test 2:
# Same as #1, but the blank line has leftover whitespace
# indentation (16 spaces) which is left in the result
assert equal (
do {
let s = "
Heading
one
two
"
$s | str dedent
}
) "Heading\n \n one\n two"
# Test 3:
# Same, but with a single tab character on the "blank" line
assert equal (
do {
let s = "
Heading
\t
one
two
"
$s | str dedent
}
) "Heading\n\t\n one\n two"
# Test 4:
# Ends with line-break
assert equal (
do {
let s = "
Heading
one
two
"
$s | str dedent
}
) "Heading\n\n one\n two\n"
# Test 5:
# Identity - Returns the original string sans first and last empty lines
# No other whitespace should be removed
assert equal (
do {
let s = "\n Identity \n"
$s | str dedent
}
) " Identity "
# Test 6:
# Error - Does not contain an empty first line
assert error {||
let s = "Error"
$s | str dedent
}
# Test 6.1:
# Error - Does not contain an empty first line
assert error {||
let s = "Error\n \nTesting\n"
$s | str dedent
}
# Test 7:
# Error - Does not contain an empty last line
assert error {||
let s = "
Error"
$s | str dedent
}
# Test 7.1:
# Error - Does not contain an empty last line
assert error {||
let s = "
Error"
$s | str dedent
}
# Test 8:
# Error - Line 1 does not have enough indentation
assert error {||
let s = "
Line 1
Line 2
"
$s | str dedent
}
# Test 8:
# Error - Line 2 does not have enough indentation
assert error {||
let s = "
Line 1
Line 2
"
$s | str dedent
}
# Test 9:
# Error - Line does not have enough indentation
assert error {||
let s = "
Line
"
$s | str dedent
}
# Test 10:
# "Hidden" whitespace on the first line is allowed
assert equal (
do {
let s = " \t \n Identity \n"
$s | str dedent
}
) " Identity "
}

View file

@ -0,0 +1,12 @@
use std assert
use ../std-rfc str
export def "test append" [] {
assert equal ("foo" | str append "/") "foo/"
assert equal (["foo", "bar", "baz"] | str append "/") ["foo/", "bar/", "baz/"]
}
export def "test prepend" [] {
assert equal ("foo" | str prepend "/") "/foo"
assert equal (["foo", "bar", "baz"] | str prepend "/") ["/foo", "/bar", "/baz"]
}