2024-04-19 08:53:30 +02:00
|
|
|
#!/usr/bin/env -S nu --stdin
|
|
|
|
# Example of using a Nushell script as a Nushell plugin
|
|
|
|
#
|
|
|
|
# This is a port of the nu_plugin_python_example plugin to Nushell itself. There is probably not
|
|
|
|
# really any reason to write a Nushell plugin in Nushell, but this is a fun proof of concept, and
|
|
|
|
# it also allows us to test the plugin interface with something manually implemented in a scripting
|
|
|
|
# language without adding any extra dependencies to our tests.
|
|
|
|
|
2024-11-12 21:22:38 +01:00
|
|
|
const NUSHELL_VERSION = "0.100.0"
|
2024-08-02 20:01:20 +02:00
|
|
|
const PLUGIN_VERSION = "0.1.1" # bump if you change commands!
|
2024-04-19 08:53:30 +02:00
|
|
|
|
|
|
|
def main [--stdio] {
|
|
|
|
if ($stdio) {
|
|
|
|
start_plugin
|
|
|
|
} else {
|
|
|
|
print -e "Run me from inside nushell!"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const SIGNATURES = [
|
|
|
|
{
|
|
|
|
sig: {
|
|
|
|
name: nu_plugin_nu_example,
|
2024-08-22 12:02:08 +02:00
|
|
|
description: "Signature test for Nushell plugin in Nushell",
|
|
|
|
extra_description: "",
|
2024-04-19 08:53:30 +02:00
|
|
|
required_positional: [
|
|
|
|
[
|
|
|
|
name,
|
|
|
|
desc,
|
|
|
|
shape
|
|
|
|
];
|
|
|
|
[
|
|
|
|
a,
|
|
|
|
"required integer value",
|
|
|
|
Int
|
|
|
|
],
|
|
|
|
[
|
|
|
|
b,
|
|
|
|
"required string value",
|
|
|
|
String
|
|
|
|
]
|
|
|
|
],
|
|
|
|
optional_positional: [
|
|
|
|
[
|
|
|
|
name,
|
|
|
|
desc,
|
|
|
|
shape
|
|
|
|
];
|
|
|
|
[
|
|
|
|
opt,
|
|
|
|
"Optional number",
|
|
|
|
Int
|
|
|
|
]
|
|
|
|
],
|
|
|
|
rest_positional: {
|
|
|
|
name: rest,
|
|
|
|
desc: "rest value string",
|
|
|
|
shape: String
|
|
|
|
},
|
|
|
|
named: [
|
|
|
|
[
|
|
|
|
long,
|
|
|
|
short,
|
|
|
|
arg,
|
|
|
|
required,
|
|
|
|
desc
|
|
|
|
];
|
|
|
|
[
|
|
|
|
help,
|
|
|
|
h,
|
|
|
|
null,
|
|
|
|
false,
|
|
|
|
"Display the help message for this command"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
flag,
|
|
|
|
f,
|
|
|
|
null,
|
|
|
|
false,
|
|
|
|
"a flag for the signature"
|
|
|
|
],
|
|
|
|
[
|
|
|
|
named,
|
|
|
|
n,
|
|
|
|
String,
|
|
|
|
false,
|
|
|
|
"named string"
|
|
|
|
]
|
|
|
|
],
|
|
|
|
input_output_types: [
|
|
|
|
[Any, Any]
|
|
|
|
],
|
|
|
|
allow_variants_without_examples: true,
|
|
|
|
search_terms: [
|
|
|
|
Example
|
|
|
|
],
|
|
|
|
is_filter: false,
|
|
|
|
creates_scope: false,
|
|
|
|
allows_unknown_args: false,
|
|
|
|
category: Experimental
|
|
|
|
},
|
|
|
|
examples: []
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
def process_call [
|
|
|
|
id: int,
|
|
|
|
plugin_call: record<
|
|
|
|
name: string,
|
|
|
|
call: record<
|
|
|
|
head: record<start: int, end: int>,
|
|
|
|
positional: list,
|
|
|
|
named: list,
|
|
|
|
>,
|
|
|
|
input: any
|
|
|
|
>
|
|
|
|
] {
|
|
|
|
# plugin_call is a dictionary with the information from the call
|
|
|
|
# It should contain:
|
|
|
|
# - The name of the call
|
|
|
|
# - The call data which includes the positional and named values
|
|
|
|
# - The input from the pipeline
|
|
|
|
|
|
|
|
# Use this information to implement your plugin logic
|
|
|
|
|
|
|
|
# Print the call to stderr, in raw nuon and as a table
|
|
|
|
$plugin_call | to nuon --raw | print -e
|
|
|
|
$plugin_call | table -e | print -e
|
|
|
|
|
|
|
|
# Get the span from the call
|
|
|
|
let span = $plugin_call.call.head
|
|
|
|
|
|
|
|
# Create a Value of type List that will be encoded and sent to Nushell
|
|
|
|
let value = {
|
2024-08-02 20:01:20 +02:00
|
|
|
Value: [{
|
2024-04-19 08:53:30 +02:00
|
|
|
List: {
|
|
|
|
vals: (0..9 | each { |x|
|
|
|
|
{
|
|
|
|
Record: {
|
|
|
|
val: (
|
|
|
|
[one two three] |
|
|
|
|
zip (0..2 | each { |y|
|
|
|
|
{
|
|
|
|
Int: {
|
|
|
|
val: ($x * $y),
|
|
|
|
span: $span,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}) |
|
Change behavior of `into record` on lists to be more useful (#13637)
# Description
The previous behaviour of `into record` on lists was to create a new
record with each list index as the key. This was not very useful for
creating meaningful records, though, and most people would end up using
commands like `headers` or `transpose` to turn a list of keys and values
into a record.
This PR changes that instead to do what I think the most ergonomic thing
is, and instead:
- A list of records is merged into one record.
- A list of pairs (two element lists) is folded into a record with the
first element of each pair being the key, and the second being the
value.
The former is just generally more useful than having to use `reduce`
with `merge` for such a common operation, and the latter is useful
because it means that `$a | zip $b | into record` *just works* in the
way that seems most obvious.
Example:
```nushell
[[foo bar] [baz quux]] | into record # => {foo: bar, baz: quux}
[{foo: bar} {baz: quux}] | into record # => {foo: bar, baz: quux}
[foo baz] | zip [bar quux] | into record # => {foo: bar, baz: quux}
```
The support for range input has been removed, as it would no longer
reflect the treatment of an equivalent list.
The following is equivalent to the old behavior, in case that's desired:
```
0.. | zip [a b c] | into record # => {0: a, 1: b, 2: c}
```
# User-Facing Changes
- `into record` changed as described above (breaking)
- `into record` no longer supports range input (breaking)
# Tests + Formatting
Examples changed to match, everything works. Some usage in stdlib and
`nu_plugin_nu_example` had to be changed.
# After Submitting
- [ ] release notes (commands, breaking change)
2024-08-22 11:38:43 +02:00
|
|
|
into record
|
2024-04-19 08:53:30 +02:00
|
|
|
),
|
|
|
|
span: $span
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
span: $span
|
|
|
|
}
|
2024-08-02 20:01:20 +02:00
|
|
|
}, null]
|
2024-04-19 08:53:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
write_response $id { PipelineData: $value }
|
|
|
|
}
|
|
|
|
|
|
|
|
def tell_nushell_encoding [] {
|
|
|
|
print -n "\u{0004}json"
|
|
|
|
}
|
|
|
|
|
|
|
|
def tell_nushell_hello [] {
|
|
|
|
# A `Hello` message is required at startup to inform nushell of the protocol capabilities and
|
|
|
|
# compatibility of the plugin. The version specified should be the version of nushell that this
|
|
|
|
# plugin was tested and developed against.
|
|
|
|
let hello = {
|
|
|
|
Hello: {
|
|
|
|
protocol: "nu-plugin", # always this value
|
|
|
|
version: $NUSHELL_VERSION,
|
|
|
|
features: []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$hello | to json --raw | print
|
|
|
|
}
|
|
|
|
|
|
|
|
def write_response [id: int, response: record] {
|
|
|
|
# Use this format to send a response to a plugin call. The ID of the plugin call is required.
|
|
|
|
let wrapped_response = {
|
|
|
|
CallResponse: [
|
|
|
|
$id,
|
|
|
|
$response,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
$wrapped_response | to json --raw | print
|
|
|
|
}
|
|
|
|
|
|
|
|
def write_error [id: int, text: string, span?: record<start: int, end: int>] {
|
|
|
|
# Use this error format to send errors to nushell in response to a plugin call. The ID of the
|
|
|
|
# plugin call is required.
|
|
|
|
let error = if ($span | is-not-empty) {
|
|
|
|
{
|
|
|
|
Error: {
|
|
|
|
msg: "ERROR from plugin",
|
|
|
|
labels: [
|
|
|
|
{
|
|
|
|
text: $text,
|
|
|
|
span: $span,
|
|
|
|
}
|
|
|
|
],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
{
|
|
|
|
Error: {
|
|
|
|
msg: "ERROR from plugin",
|
|
|
|
help: $text,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
write_response $id $error
|
|
|
|
}
|
|
|
|
|
|
|
|
def handle_input []: any -> nothing {
|
|
|
|
match $in {
|
|
|
|
{ Hello: $hello } => {
|
|
|
|
if ($hello.version != $NUSHELL_VERSION) {
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"Goodbye" => {
|
|
|
|
exit 0
|
|
|
|
}
|
|
|
|
{ Call: [$id, $plugin_call] } => {
|
|
|
|
match $plugin_call {
|
2024-06-21 13:27:09 +02:00
|
|
|
"Metadata" => {
|
|
|
|
write_response $id {
|
|
|
|
Metadata: {
|
|
|
|
version: $PLUGIN_VERSION
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-19 08:53:30 +02:00
|
|
|
"Signature" => {
|
|
|
|
write_response $id { Signature: $SIGNATURES }
|
|
|
|
}
|
|
|
|
{ Run: $call_info } => {
|
|
|
|
process_call $id $call_info
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
write_error $id $"Operation not supported: ($plugin_call | to json --raw)"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$other => {
|
|
|
|
print -e $"Unknown message: ($other | to json --raw)"
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
def start_plugin [] {
|
|
|
|
lines |
|
|
|
|
prepend (do {
|
|
|
|
# This is a hack so that we do this first, but we can also take input as a stream
|
|
|
|
tell_nushell_encoding
|
|
|
|
tell_nushell_hello
|
|
|
|
[]
|
|
|
|
}) |
|
|
|
|
each { from json | handle_input } |
|
|
|
|
ignore
|
2024-10-15 21:01:08 +02:00
|
|
|
}
|