nushell/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu
2024-09-18 00:48:46 -07:00

267 lines
5.9 KiB
Plaintext
Executable File

#!/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.
const NUSHELL_VERSION = "0.98.0"
const PLUGIN_VERSION = "0.1.1" # bump if you change commands!
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,
description: "Signature test for Nushell plugin in Nushell",
extra_description: "",
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 = {
Value: [{
List: {
vals: (0..9 | each { |x|
{
Record: {
val: (
[one two three] |
zip (0..2 | each { |y|
{
Int: {
val: ($x * $y),
span: $span,
}
}
}) |
into record
),
span: $span
}
}
}),
span: $span
}
}, null]
}
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 {
"Metadata" => {
write_response $id {
Metadata: {
version: $PLUGIN_VERSION
}
}
}
"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
}