2023-04-09 19:00:20 +02:00
|
|
|
# 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.
|
2023-11-03 16:09:33 +01:00
|
|
|
# 4. Closure. Predicate accepting entry. Selects all entries among nodes selected by previous path for which predicate returns true.
|
2023-04-09 19:00:20 +02:00
|
|
|
] {
|
|
|
|
let input = $in
|
|
|
|
if ($path | is-empty) {
|
|
|
|
let path_span = (metadata $path).span
|
2023-11-03 16:09:33 +01:00
|
|
|
error make {
|
|
|
|
msg: 'Empty path provided'
|
|
|
|
label: {
|
|
|
|
text: 'Use a non-empty list of path steps'
|
|
|
|
span: $path_span
|
|
|
|
}
|
|
|
|
}
|
2023-04-09 19:00:20 +02:00
|
|
|
}
|
|
|
|
# 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
|
Improve type hovers (#9515)
# Description
This PR does a few things to help improve type hovers and, in the
process, fixes a few outstanding issues in the type system. Here's a
list of the changes:
* `for` now will try to infer the type of the iteration variable based
on the expression it's given. This fixes things like `for x in [1, 2, 3]
{ }` where `x` now properly gets the int type.
* Removed old input/output type fields from the signature, focuses on
the vec of signatures. Updated a bunch of dataframe commands that hadn't
moved over. This helps tie things together a bit better
* Fixed inference of types from subexpressions to use the last
expression in the block
* Fixed handling of explicit types in `let` and `mut` calls, so we now
respect that as the authoritative type
I also tried to add `def` input/output type inference, but unfortunately
we only know the predecl types universally, which means we won't have
enough information to properly know what the types of the custom
commands are.
# User-Facing Changes
Script typechecking will get tighter in some cases
Hovers should be more accurate in some cases that previously resorted to
any.
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the
standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
---------
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2023-06-28 19:19:48 +02:00
|
|
|
mut values: any = ()
|
2023-04-09 19:00:20 +02:00
|
|
|
$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
|
2023-11-03 16:09:33 +01:00
|
|
|
error make {
|
|
|
|
msg: $'Incorrect path step type ($type)'
|
|
|
|
label: {
|
|
|
|
text: 'Use a string or int as a step'
|
|
|
|
span: $step_span
|
|
|
|
}
|
|
|
|
}
|
2023-04-09 19:00:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
Improve type hovers (#9515)
# Description
This PR does a few things to help improve type hovers and, in the
process, fixes a few outstanding issues in the type system. Here's a
list of the changes:
* `for` now will try to infer the type of the iteration variable based
on the expression it's given. This fixes things like `for x in [1, 2, 3]
{ }` where `x` now properly gets the int type.
* Removed old input/output type fields from the signature, focuses on
the vec of signatures. Updated a bunch of dataframe commands that hadn't
moved over. This helps tie things together a bit better
* Fixed inference of types from subexpressions to use the last
expression in the block
* Fixed handling of explicit types in `let` and `mut` calls, so we now
respect that as the authoritative type
I also tried to add `def` input/output type inference, but unfortunately
we only know the predecl types universally, which means we won't have
enough information to properly know what the types of the custom
commands are.
# User-Facing Changes
Script typechecking will get tighter in some cases
Hovers should be more accurate in some cases that previously resorted to
any.
# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the
standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->
# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
---------
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2023-06-28 19:19:48 +02:00
|
|
|
mut reenumerated_new_values: any = ($to_update.index | zip $new_values | each {|x| {index: $x.0 item: $x.1}})
|
2023-04-09 19:00:20 +02:00
|
|
|
|
|
|
|
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
|
2023-11-03 16:09:33 +01:00
|
|
|
error make {
|
|
|
|
msg: $'Incorrect path step type ($type)'
|
|
|
|
label: {
|
|
|
|
text: 'Use a string or int as a step'
|
|
|
|
span: $step_span
|
|
|
|
}
|
|
|
|
}
|
2023-04-09 19:00:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# 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.
|
2023-11-03 16:09:33 +01:00
|
|
|
# 4. Closure. Predicate accepting entry. Selects all entries among nodes selected by previous path for which predicate returns true.
|
2023-04-09 19:00:20 +02:00
|
|
|
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
|
2023-11-03 16:09:33 +01:00
|
|
|
if (($input | describe) == 'string' or
|
2023-04-09 19:00:20 +02:00
|
|
|
($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.
|
2023-11-03 16:09:33 +01:00
|
|
|
# 4. Closure. Predicate accepting entry. Selects all entries among nodes selected by previous path for which predicate returns true.
|
2023-04-09 19:00:20 +02:00
|
|
|
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
|
2023-11-03 16:09:33 +01:00
|
|
|
# position is greater than number of elements) in content of all entries of input matched by
|
2023-04-09 19:00:20 +02:00
|
|
|
# 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
|
|
|
|
}
|
|
|
|
|
2023-11-03 16:09:33 +01:00
|
|
|
|
2023-04-09 19:00:20 +02:00
|
|
|
{tag: $entry.tag attributes: $entry.attributes content: $new_content}
|
|
|
|
},
|
|
|
|
_ => (error make {msg: 'Can insert entry only into content of a tag node'})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|