nushell-plugin-builder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Nushell Plugin Builder

Nushell 插件构建器

Overview

概述

This skill helps create Nushell plugins in Rust. Plugins are standalone executables that extend Nushell with custom commands, data transformations, and integrations.
本技能可帮助你在Rust中创建Nushell插件。插件是独立的可执行文件,可为Nushell扩展自定义命令、数据转换功能以及外部集成能力。

Quick Start

快速开始

1. Create Plugin Project

1. 创建插件项目

bash
cargo new nu_plugin_<name>
cd nu_plugin_<name>
cargo add nu-plugin nu-protocol
bash
cargo new nu_plugin_<name>
cd nu_plugin_<name>
cargo add nu-plugin nu-protocol

2. Basic Plugin Structure

2. 基础插件结构

rust
use nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin};
use nu_plugin::{EngineInterface, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{LabeledError, Signature, Type, Value};

struct MyPlugin;

impl Plugin for MyPlugin {
    fn version(&self) -> String {
        env!("CARGO_PKG_VERSION").into()
    }

    fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
        vec![Box::new(MyCommand)]
    }
}

struct MyCommand;

impl SimplePluginCommand for MyCommand {
    type Plugin = MyPlugin;

    fn name(&self) -> &str {
        "my-command"
    }

    fn signature(&self) -> Signature {
        Signature::build("my-command")
            .input_output_type(Type::String, Type::Int)
    }

    fn run(
        &self,
        _plugin: &MyPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        input: &Value,
    ) -> Result<Value, LabeledError> {
        match input {
            Value::String { val, .. } => {
                Ok(Value::int(val.len() as i64, call.head))
            }
            _ => Err(LabeledError::new("Expected string input")
                .with_label("requires string", call.head))
        }
    }
}

fn main() {
    serve_plugin(&MyPlugin, MsgPackSerializer)
}
rust
use nu_plugin::{EvaluatedCall, MsgPackSerializer, serve_plugin};
use nu_plugin::{EngineInterface, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{LabeledError, Signature, Type, Value};

struct MyPlugin;

impl Plugin for MyPlugin {
    fn version(&self) -> String {
        env!("CARGO_PKG_VERSION").into()
    }

    fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
        vec![Box::new(MyCommand)]
    }
}

struct MyCommand;

impl SimplePluginCommand for MyCommand {
    type Plugin = MyPlugin;

    fn name(&self) -> &str {
        "my-command"
    }

    fn signature(&self) -> Signature {
        Signature::build("my-command")
            .input_output_type(Type::String, Type::Int)
    }

    fn run(
        &self,
        _plugin: &MyPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        input: &Value,
    ) -> Result<Value, LabeledError> {
        match input {
            Value::String { val, .. } => {
                Ok(Value::int(val.len() as i64, call.head))
            }
            _ => Err(LabeledError::new("Expected string input")
                .with_label("requires string", call.head))
        }
    }
}

fn main() {
    serve_plugin(&MyPlugin, MsgPackSerializer)
}

3. Build and Install

3. 构建与安装

bash
undefined
bash
undefined

Build

Build

cargo build --release
cargo build --release

Install to cargo bin

Install to cargo bin

cargo install --path . --locked
cargo install --path . --locked

Register with nushell

Register with nushell

plugin add ~/.cargo/bin/nu_plugin_<name> # Add .exe on Windows plugin use <name>
plugin add ~/.cargo/bin/nu_plugin_<name> # Add .exe on Windows plugin use <name>

Test

Test

"hello" | my-command
undefined
"hello" | my-command
undefined

Command Types

命令类型

SimplePluginCommand

SimplePluginCommand

For commands that operate on single values:
  • Input:
    &Value
  • Output:
    Result<Value, LabeledError>
  • Use for: transformations, simple filters, single value operations
适用于处理单个值的命令:
  • 输入:
    &Value
  • 输出:
    Result<Value, LabeledError>
  • 适用场景:转换操作、简单过滤、单值处理

PluginCommand

PluginCommand

For commands that handle streams:
  • Input:
    PipelineData
  • Output:
    Result<PipelineData, LabeledError>
  • Use for: streaming transformations, lazy processing, large datasets
See
references/advanced-features.md
for streaming examples.
适用于处理流数据的命令:
  • 输入:
    PipelineData
  • 输出:
    Result<PipelineData, LabeledError>
  • 适用场景:流式转换、惰性处理、大型数据集
如需流式处理示例,请查看
references/advanced-features.md

Defining Command Signatures

定义命令签名

Input-Output Types

输入输出类型

rust
use nu_protocol::{Signature, Type};

Signature::build("my-command")
    .input_output_type(Type::String, Type::Int)
Common types:
String
,
Int
,
Float
,
Bool
,
List(Box<Type>)
,
Record(...)
,
Any
rust
use nu_protocol::{Signature, Type};

Signature::build("my-command")
    .input_output_type(Type::String, Type::Int)
常见类型:
String
Int
Float
Bool
List(Box<Type>)
Record(...)
Any

Parameters

参数

rust
Signature::build("my-command")
    // Named flags
    .named("output", SyntaxShape::Filepath, "output file", Some('o'))
    .switch("verbose", "enable verbose output", Some('v'))
    // Positional arguments
    .required("input", SyntaxShape::String, "input value")
    .optional("count", SyntaxShape::Int, "repeat count")
    .rest("files", SyntaxShape::Filepath, "files to process")
rust
Signature::build("my-command")
    // 命名标志
    .named("output", SyntaxShape::Filepath, "output file", Some('o'))
    .switch("verbose", "enable verbose output", Some('v'))
    // 位置参数
    .required("input", SyntaxShape::String, "input value")
    .optional("count", SyntaxShape::Int, "repeat count")
    .rest("files", SyntaxShape::Filepath, "files to process")

Accessing Arguments

访问参数

rust
fn run(&self, call: &EvaluatedCall, ...) -> Result<Value, LabeledError> {
    let output: Option<String> = call.get_flag("output")?;
    let verbose: bool = call.has_flag("verbose")?;
    let input: String = call.req(0)?;  // First positional
    let count: Option<i64> = call.opt(1)?;  // Second positional
    let files: Vec<String> = call.rest(2)?;  // Remaining args
}
rust
fn run(&self, call: &EvaluatedCall, ...) -> Result<Value, LabeledError> {
    let output: Option<String> = call.get_flag("output")?;
    let verbose: bool = call.has_flag("verbose")?;
    let input: String = call.req(0)?;  // 第一个位置参数
    let count: Option<i64> = call.opt(1)?;  // 第二个位置参数
    let files: Vec<String> = call.rest(2)?;  // 剩余参数
}

Error Handling

错误处理

Always return
LabeledError
with span information:
rust
Err(LabeledError::new("Error message")
    .with_label("specific issue", call.head))
This shows users exactly where the error occurred in their command.
请始终返回包含范围信息的
LabeledError
rust
Err(LabeledError::new("Error message")
    .with_label("specific issue", call.head))
这样可以向用户准确显示命令中错误发生的位置。

Serialization

序列化

MsgPackSerializer (Recommended)
  • Binary format, much faster
  • Use for production plugins
JsonSerializer
  • Text-based, human-readable
  • Useful for debugging
Choose in
main()
:
rust
serve_plugin(&MyPlugin, MsgPackSerializer)  // Production
// serve_plugin(&MyPlugin, JsonSerializer)  // Debug
MsgPackSerializer(推荐)
  • 二进制格式,速度更快
  • 适用于生产环境插件
JsonSerializer
  • 基于文本的格式,人类可读
  • 适用于调试场景
main()
中选择序列化方式:
rust
serve_plugin(&MyPlugin, MsgPackSerializer)  // 生产环境
// serve_plugin(&MyPlugin, JsonSerializer)  // 调试

Common Patterns

常见模式

String Transformation

字符串转换

rust
Value::String { val, .. } => {
    Ok(Value::string(val.to_uppercase(), call.head))
}
rust
Value::String { val, .. } => {
    Ok(Value::string(val.to_uppercase(), call.head))
}

List Generation

列表生成

rust
let items = vec![
    Value::string("a", call.head),
    Value::string("b", call.head),
];
Ok(Value::list(items, call.head))
rust
let items = vec![
    Value::string("a", call.head),
    Value::string("b", call.head),
];
Ok(Value::list(items, call.head))

Record (Table Row)

记录(表格行)

rust
use nu_protocol::record;

Ok(Value::record(
    record! {
        "name" => Value::string("example", call.head),
        "size" => Value::int(42, call.head),
    },
    call.head,
))
rust
use nu_protocol::record;

Ok(Value::record(
    record! {
        "name" => Value::string("example", call.head),
        "size" => Value::int(42, call.head),
    },
    call.head,
))

Table (List of Records)

表格(记录列表)

rust
let records = vec![
    Value::record(record! { "name" => Value::string("a", span) }, span),
    Value::record(record! { "name" => Value::string("b", span) }, span),
];
Ok(Value::list(records, call.head))
See
references/examples.md
for complete working examples including:
  • Filtering streams
  • HTTP API calls
  • File system operations
  • Multi-command plugins
rust
let records = vec![
    Value::record(record! { "name" => Value::string("a", span) }, span),
    Value::record(record! { "name" => Value::string("b", span) }, span),
];
Ok(Value::list(records, call.head))
完整的可用示例请查看
references/examples.md
,包括:
  • 流数据过滤
  • HTTP API调用
  • 文件系统操作
  • 多命令插件

Development Workflow

开发工作流

Iterative Development

迭代开发

bash
undefined
bash
undefined

Build

Build

cargo build
cargo build

Test (debug build)

Test (debug build)

plugin add target/debug/nu_plugin_<name> plugin use <name> "test" | my-command
plugin add target/debug/nu_plugin_<name> plugin use <name> "test" | my-command

After changes, reload

After changes, reload

plugin rm <name> plugin add target/debug/nu_plugin_<name> plugin use <name>
undefined
plugin rm <name> plugin add target/debug/nu_plugin_<name> plugin use <name>
undefined

Automated Testing

自动化测试

toml
[dev-dependencies]
nu-plugin-test-support = "0.109.1"
rust
#[cfg(test)]
mod tests {
    use nu_plugin_test_support::PluginTest;

    #[test]
    fn test_command() -> Result<(), nu_protocol::ShellError> {
        PluginTest::new("myplugin", MyPlugin.into())?
            .test_examples(&MyCommand)
    }
}
See
references/testing-debugging.md
for debugging techniques and troubleshooting.
toml
[dev-dependencies]
nu-plugin-test-support = "0.109.1"
rust
#[cfg(test)]
mod tests {
    use nu_plugin_test_support::PluginTest;

    #[test]
    fn test_command() -> Result<(), nu_protocol::ShellError> {
        PluginTest::new("myplugin", MyPlugin.into())?
            .test_examples(&MyCommand)
    }
}
调试技巧与故障排除请查看
references/testing-debugging.md

Advanced Features

高级功能

Streaming Data

流式数据处理

For lazy processing of large datasets, use
PipelineData
:
rust
impl PluginCommand for MyCommand {
    fn run(&self, input: PipelineData, ...) -> Result<PipelineData, LabeledError> {
        let filtered = input.into_iter().filter(|v| /* condition */);
        Ok(PipelineData::ListStream(ListStream::new(filtered, span, None), None))
    }
}
如需对大型数据集进行惰性处理,请使用
PipelineData
rust
impl PluginCommand for MyCommand {
    fn run(&self, input: PipelineData, ...) -> Result<PipelineData, LabeledError> {
        let filtered = input.into_iter().filter(|v| /* condition */);
        Ok(PipelineData::ListStream(ListStream::new(filtered, span, None), None))
    }
}

Engine Interaction

引擎交互

rust
// Get environment variables
let home = engine.get_env_var("HOME")?;

// Set environment variables (before response)
engine.add_env_var("MY_VAR", Value::string("value", span))?;

// Get plugin config from $env.config.plugins.<name>
let config = engine.get_plugin_config()?;

// Get current directory for path resolution
let cwd = engine.get_current_dir()?;
rust
// 获取环境变量
let home = engine.get_env_var("HOME")?;

// 设置环境变量(响应前)
engine.add_env_var("MY_VAR", Value::string("value", span))?;

// 从$env.config.plugins.<name>获取插件配置
let config = engine.get_plugin_config()?;

// 获取当前目录用于路径解析
let cwd = engine.get_current_dir()?;

Custom Values

自定义值

Define custom data types that extend beyond Nushell's built-in types. See
references/advanced-features.md
for complete guide.
定义超出Nushell内置类型的自定义数据类型。完整指南请查看
references/advanced-features.md

Important Constraints

重要约束

Stdio Restrictions
  • Plugins cannot use stdin/stdout (reserved for protocol)
  • Check
    engine.is_using_stdio()
    before attempting stdio access
Path Handling
  • Always use paths relative to
    engine.get_current_dir()
  • Never assume current working directory
Version Compatibility
  • Match
    nu-plugin
    and
    nu-protocol
    versions
  • Both should match target Nushell version
标准输入输出限制
  • 插件无法使用标准输入/输出(预留用于协议通信)
  • 在尝试访问标准输入输出前,请检查
    engine.is_using_stdio()
路径处理
  • 请始终使用相对于
    engine.get_current_dir()
    的路径
  • 不要假设当前工作目录
版本兼容性
  • 请保持
    nu-plugin
    nu-protocol
    的版本一致
  • 两者版本需与目标Nushell版本匹配

Reference Documentation

参考文档

  • references/plugin-protocol.md
    - Protocol details, serialization, lifecycle
  • references/advanced-features.md
    - Streaming, EngineInterface, custom values
  • references/examples.md
    - Complete working examples and patterns
  • references/testing-debugging.md
    - Development workflow, debugging, troubleshooting
  • references/plugin-protocol.md
    - 协议细节、序列化、生命周期
  • references/advanced-features.md
    - 流式处理、EngineInterface、自定义值
  • references/examples.md
    - 完整可用示例与模式
  • references/testing-debugging.md
    - 开发工作流、调试、故障排除

External Resources

外部资源

Template Script

模板脚本

Use
scripts/init_plugin.py
to scaffold a new plugin with proper structure:
bash
python3 scripts/init_plugin.py <plugin-name> [--output-dir <path>]
This creates a complete working plugin template ready to customize.
使用
scripts/init_plugin.py
快速搭建结构规范的新插件:
bash
python3 scripts/init_plugin.py <plugin-name> [--output-dir <path>]
该脚本会创建一个可直接自定义的完整可用插件模板。",