Clasp:一个带插件系统的 .NET 10 命令行工具箱

Clasp:一个带插件系统的 .NET 10 命令行工具箱

本文基于开源项目 Clasp(MIT License)撰写,介绍其架构设计、插件系统与工程实践。

引言

在开发工作中,我们常常需要一堆散落的小工具:查 DNS、看端口、转 Base64、解 JWT、算哈希......每个都去装一个独立的命令行工具,不仅安装繁琐,记忆成本也高。

Clasp 就是为解决这个问题而生的:一个跨平台的 .NET 命令行工具箱 。它把网络、文本处理、系统信息等常用能力打包进单一可执行文件,并且提供了一个精巧的插件系统------任何人都可以通过往 plugins 目录里丢一个 DLL 来扩展新命令。

复制代码
clasp dns --host example.com
clasp b64 --encode "hello world"
clasp jwt --token <JWT>
clasp speed  # 测一下网速

为什么值得关注

  • 单一二进制:一个程序、一处下载,覆盖日常高频命令。
  • 插件优先的架构:内置命令和第三方插件走同一条注册管线,扩展成本极低。
  • 声明式命令定义:命令名、选项都靠 C# 特性声明,几乎零样板代码。
  • 工程完备:30 个内置命令、配套单元测试、GitHub Actions 多平台自动发布。

项目结构

Clasp 采用"主程序 + SDK + 示例 + 测试"的四层结构:

复制代码
Clasp/
├── src/
│   ├── Clasp/            # 主程序:命令注册、参数解析、分发
│   └── Clasp.Plugin/     # 插件 SDK:ClaspCommand 基类、特性、帮助与着色
├── plugins/
│   └── ExamplePlugin/    # 示例插件(hello 命令)
├── tests/
│   └── Clasp.Tests/      # 30 个测试文件,覆盖全部命令
└── Clasp.slnx

flowchart LR A用户输入参数 --> BCommandRegistry\
反射扫描 + 注册
B --> C参数解析器\
长短选项 / 位置参数
C --> DValidateAsync 参数校验 D --> EExecuteAsync 执行命令 B -.扫描插件 DLL.-> Fplugins 目录 E --> GANSI 彩色输出

整个调用链非常短:Program.Main 里只做三件事------确保 plugins 目录存在、扫描注册命令、分发执行:

csharp 复制代码
var pluginsPath = Path.Combine(AppContext.BaseDirectory, "plugins");
Directory.CreateDirectory(pluginsPath);

var registry = CommandRegistry.Scan(Assembly.GetExecutingAssembly(), pluginsPath);
await registry.DispatchAsync(args);

核心架构:反射 + 声明式命令

Clasp 的命令定义完全摆脱了手写 switch-case 分发。一个命令就是一个继承 ClaspCommand 的类,用特性声明元信息:

csharp 复制代码
[ClaspCommand("dns", Description = "查询域名的 A/AAAA 记录")]
internal class DnsLookup : ClaspCommand
{
    [ClaspOption("--host", Description = "要查询的域名")]
    public string Host { get; set; } = string.Empty;

    [ClaspOption("--type", "-t", Description = "记录类型: A/AAAA (默认 A)")]
    public string Type { get; set; } = "A";
}

CommandRegistry 在启动时扫描主程序集与插件目录中的每个类型,把实现了 ClaspCommand 且带 [ClaspCommand] 特性的类收入字典:

csharp 复制代码
if (!typeof(Clasp.Plugin.ClaspCommand).IsAssignableFrom(type) || type.IsAbstract)
    continue;

var attr = type.GetCustomAttribute<Clasp.Plugin.Attributes.ClaspCommandAttribute>();
if (attr is null)
    continue;

两个设计细节值得一提:

  1. 命令别名 :[ClaspCommand("file-download", "fd")] 可以声明多个名字,注册时共用同一个命令类型,方便输入。
  2. 选项注入 :解析器把 --host example.com 这类键值对收集起来,再通过反射注入到命令对象的属性上;bool 类型选项自动按开关处理,位置参数则写入 ClaspCommandArgs。

每个命令需要实现两个生命周期方法:

  • ValidateAsync ------ 参数校验,不合法时抛出异常,主程序捕获后输出错误信息并返回退出码 1;
  • ExecuteAsync ------ 真正执行,返回后主程序返回退出码 0。

这套"校验与执行分离"的流程让所有命令的错误处理路径完全统一,也方便测试。

插件系统:扔一个 DLL 就能扩展

这是 Clasp 最大的亮点。CommandRegistry.Scan 会扫描程序目录下的 plugins 文件夹,Assembly.LoadFrom 加载其中的每个 DLL,然后走与内置命令完全相同的注册流程:

csharp 复制代码
foreach (var dll in Directory.GetFiles(pluginsPath, "*.dll", SearchOption.TopDirectoryOnly))
{
    try
    {
        var pluginAssembly = Assembly.LoadFrom(dll);
        registry.LoadAssembly(pluginAssembly);
    }
    catch
    {
        // ignore unloadable plugin assemblies
    }
}

加载失败(比如依赖缺失)的插件会被静默跳过,不影响主程序运行------这种容错对"用户随便往目录里扔东西"的场景非常关键。

仓库里的 ExamplePlugin 展示了写一个插件有多简单(完整代码只有十几行):

csharp 复制代码
[ClaspCommand("hello", Description = "向指定名称问好")]
internal class HelloCommand : ClaspCommand
{
    [ClaspOption("--name", "-n", Description = "要问好的名称")]
    public string Name { get; set; } = "world";

    public override async Task ValidateAsync(ClaspCommandArgs args, CancellationToken cancellationToken = default)
    {
        await Task.CompletedTask;
    }

    public override async Task ExecuteAsync(ClaspCommandArgs args, CancellationToken cancellationToken = default)
    {
        WriteLine($"Hello, {Name}!");
        await Task.CompletedTask;
    }
}

开发插件只需三步:新建 .NET 10 类库 → 引用 Clasp.Plugin.csproj → 实现上述模板,然后编译出的 DLL 放进 plugins 目录即可,主程序无需任何改动。

内置命令一览

类别 命令
文本处理 b64、cat、echo、count、hash、json、jwt、ts、urlenc、uuid、rand
网络工具 dns、http、ip、port、speed、file-download(fd)、proxy、scan
系统管理 sysinfo、env、ls、procs、kill、date
其他 conv(单位换算)、zip(压缩)、git-tag、help、version

每个命令都支持 --help / -h 查看选项说明,方便零记忆成本使用。

易用性细节

  • 彩色输出 :SDK 提供 ClaspColor,既支持内置枚举色(如 ClaspColorType.Yellow),也支持自定义十六进制色,错误、警告、成功一目了然。
  • 管道支持 :ClaspCommand 内置 ReadStandardInputAsync,当标准输入被重定向时自动读取,cat、b64 这类命令可以自然地参与 shell 管道。
  • 输入编码:接收标准输入时统一按 UTF-8 处理,避免中文乱码。

工程质量

  • 测试 :Clasp.Tests 项目为 30 个命令逐一编写了测试用例,并通过 InternalsVisibleTo 直接测试内部类型;还提供了 ValidateThrowsAsync 等测试辅助方法,让"参数校验抛错"这类断言一行搞定。
  • CI/CD :GitHub Actions 在推送 git 标签后自动触发多平台构建并发布 GitHub Release,用户可直接下载免安装的压缩包使用。

总结与展望

Clasp 展示了一种轻量、务实的 CLI 设计思路:反射扫描代替手写分发、特性声明代替配置、统一生命周期代替各写各的。而"插件 DLL 即命令集合"的设计,让它从一个个人工具箱变成了可生长的平台------团队内部完全可以基于它沉淀自己的运维命令包。

如果你想拥有一个清爽、可扩展的跨平台命令行工具箱,或者想给项目做一套"插件化命令体系",Clasp 的代码值得一看。

相关推荐
huaqianzkh12 小时前
WinForm C#:循环修改控件UI闪烁卡顿?SuspendLayout / ResumeLayout 与 BeginUpdate 优化
开发语言·ui·c#
用户3721574261351 天前
C# 实现 Excel 单元格保护:锁定公式并允许编辑指定区域
c#·ai编程·编程语言
公子小六1 天前
基于.NET的Windows窗体编程之WinForms数据持久化(1)
windows·microsoft·c#·.net·winforms
三雷科技1 天前
第6章第4节:自定义分词器
开发语言·c#
zhanghaha13141 天前
C# 系列教程_12 字符串 string 保姆级系统教程(面向新手,大量示例 + 原理讲解)
开发语言·数据库·c#
Coder_Shenshen2 天前
罗克韦尔ACD工程归档离线变量树解析
算法·c#
寂寞的船2 天前
【Unity知识速递】第10期 Unity GC 零分配实战:从 UnityEngine.Pool 到手动 GC 控制的全链路优化
jvm·unity·c#·游戏引擎·lucene
1-1=02 天前
C# 数据结构和特殊类型
数据结构·c#·.net·.netcore
波力海苔夹心脆6754 天前
C# WPF 资源字典详解:ResourceDictionary、MergedDictionaries 与自定义控件案例
开发语言·c#·wpf·入门教程·resource·资源字典
AI导出鸭PC端4 天前
Perplexity批量导出没有PDF模式?AI导出鸭插件补上最后一块拼图
人工智能·pdf·c#·ai导出鸭