nushell/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu

268 lines
5.9 KiB
Plaintext
Raw Normal View History

#!/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.95.1"
Allow plugins to report their own version and store it in the registry (#12883) # Description This allows plugins to report their version (and potentially other metadata in the future). The version is shown in `plugin list` and in `version`. The metadata is stored in the registry file, and reflects whatever was retrieved on `plugin add`, not necessarily the running binary. This can help you to diagnose if there's some kind of mismatch with what you expect. We could potentially use this functionality to show a warning or error if a plugin being run does not have the same version as what was in the cache file, suggesting `plugin add` be run again, but I haven't done that at this point. It is optional, and it requires the plugin author to make some code changes if they want to provide it, since I can't automatically determine the version of the calling crate or anything tricky like that to do it. Example: ``` > plugin list | select name version is_running pid ╭───┬────────────────┬─────────┬────────────┬─────╮ │ # │ name │ version │ is_running │ pid │ ├───┼────────────────┼─────────┼────────────┼─────┤ │ 0 │ example │ 0.93.1 │ false │ │ │ 1 │ gstat │ 0.93.1 │ false │ │ │ 2 │ inc │ 0.93.1 │ false │ │ │ 3 │ python_example │ 0.1.0 │ false │ │ ╰───┴────────────────┴─────────┴────────────┴─────╯ ``` cc @maxim-uvarov (he asked for it) # User-Facing Changes - `plugin list` gets a `version` column - `version` shows plugin versions when available - plugin authors *should* add `fn metadata()` to their `impl Plugin`, but don't have to # Tests + Formatting Tested the low level stuff and also the `plugin list` column. # After Submitting - [ ] update plugin guide docs - [ ] update plugin protocol docs (`Metadata` call & response) - [ ] update plugin template (`fn metadata()` should be easy) - [ ] release notes
2024-06-21 13:27:09 +02:00
const PLUGIN_VERSION = "0.1.0" # 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,
usage: "Signature test for Nushell plugin in Nushell",
extra_usage: "",
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,
}
}
}) |
each { into record } |
transpose --as-record --header-row
),
span: $span
}
}
}),
span: $span
}
}
}
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 {
Allow plugins to report their own version and store it in the registry (#12883) # Description This allows plugins to report their version (and potentially other metadata in the future). The version is shown in `plugin list` and in `version`. The metadata is stored in the registry file, and reflects whatever was retrieved on `plugin add`, not necessarily the running binary. This can help you to diagnose if there's some kind of mismatch with what you expect. We could potentially use this functionality to show a warning or error if a plugin being run does not have the same version as what was in the cache file, suggesting `plugin add` be run again, but I haven't done that at this point. It is optional, and it requires the plugin author to make some code changes if they want to provide it, since I can't automatically determine the version of the calling crate or anything tricky like that to do it. Example: ``` > plugin list | select name version is_running pid ╭───┬────────────────┬─────────┬────────────┬─────╮ │ # │ name │ version │ is_running │ pid │ ├───┼────────────────┼─────────┼────────────┼─────┤ │ 0 │ example │ 0.93.1 │ false │ │ │ 1 │ gstat │ 0.93.1 │ false │ │ │ 2 │ inc │ 0.93.1 │ false │ │ │ 3 │ python_example │ 0.1.0 │ false │ │ ╰───┴────────────────┴─────────┴────────────┴─────╯ ``` cc @maxim-uvarov (he asked for it) # User-Facing Changes - `plugin list` gets a `version` column - `version` shows plugin versions when available - plugin authors *should* add `fn metadata()` to their `impl Plugin`, but don't have to # Tests + Formatting Tested the low level stuff and also the `plugin list` column. # After Submitting - [ ] update plugin guide docs - [ ] update plugin protocol docs (`Metadata` call & response) - [ ] update plugin template (`fn metadata()` should be easy) - [ ] release notes
2024-06-21 13:27:09 +02:00
"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
Bump to 0.95.0 (#13221) <!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # 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` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` 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. -->
2024-06-25 20:29:47 +02:00
}