Spectre.Console-实现自己的CLI

  • Spectre.Console-实现自己的CLI已关闭评论
  • 241 次浏览
  • A+
所属分类:.NET技术
摘要

最近发现自己喜欢用的 Todo 软件总是差点意思,毕竟每个人的习惯和工作流不太一样,我就想着自己写一个小的Todo 项目,核心的功能是自动记录 Todo 执行过程中消耗的时间(尤其面向程序员),按照自己的想法实现一套 GTD 工作流。


引言

最近发现自己喜欢用的 Todo 软件总是差点意思,毕竟每个人的习惯和工作流不太一样,我就想着自己写一个小的Todo 项目,核心的功能是自动记录 Todo 执行过程中消耗的时间(尤其面向程序员),按照自己的想法实现一套 GTD 工作流。

不想写 WinformWPF 也写腻了,就想着学学 MAUIAvaloniaUno Platformblazor 之类的。由于前端技术选型纠结,迟迟动不了手,想想还是暂时先不弄了。但为了测试,没有个界面总是不太行,先搞一个 CLI 吧。

更新:由于想让程序持续执行,所以后面还是替换了 CLI 。

Spectre. Console

Spectre.Console(spectreconsole.net) 是一个美化 Console 输出的类库,通过它可以实现丰富多样的 Console 输出。核心的特性有这些:

  • 格式化输出文本(支持斜体等)
  • 支持对文字着色
  • 渲染复杂的组件(表格、结构树、ASCII 图片)
  • 显示进度条与状态
  • 强类型输入验证
  • 对 exception 输出着色

除此以外,它还提供了一个 Spectre.Console.Cli 类库,可以帮助我们实现类似 dotnetgit 之类的 CLI(Command Line Interface)。

基本用法

这里使用官方的示例:

var app = new CommandApp<FileSizeCommand>(); return app.Run(args);  internal sealed class FileSizeCommand : Command<FileSizeCommand.Settings> {     public sealed class Settings : CommandSettings     {         [Description("Path to search. Defaults to current directory.")]         [CommandArgument(0, "[searchPath]")]         public string? SearchPath { get; init; }          [CommandOption("-p|--pattern")]         public string? SearchPattern { get; init; }          [CommandOption("--hidden")]         [DefaultValue(true)]         public bool IncludeHidden { get; init; }     }      public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)     {         var searchOptions = new EnumerationOptions         {             AttributesToSkip = settings.IncludeHidden                 ? FileAttributes.Hidden | FileAttributes.System                 : FileAttributes.System         };          var searchPattern = settings.SearchPattern ?? "*.*";         var searchPath = settings.SearchPath ?? Directory.GetCurrentDirectory();         var files = new DirectoryInfo(searchPath)             .GetFiles(searchPattern, searchOptions);          var totalFileSize = files             .Sum(fileInfo => fileInfo.Length);          AnsiConsole.MarkupLine($"Total file size for [green]{searchPattern}[/] files in [green]{searchPath}[/]: [blue]{totalFileSize:N0}[/] bytes");          return 0;     } } 

结构非常简单,标有 [CommandOption("xxx")] 会自动将参数归类,通过下列命令进行调用。

app.exe app.exe c:windows app.exe c:windows --pattern *.dll app.exe c:windows --hidden --pattern *.dll 

多命令

上面这个示例只支持一个默认的命令,但是一般的 CLI 都有很多支持的命令,需要调整一下实现:

var app = new CommandApp(); app.Configure(config => {     config.AddCommand<AddCommand>("add");     config.AddCommand<CommitCommand>("commit");     config.AddCommand<RebaseCommand>("rebase"); }); 

层级命令

更复杂一点的,比如 dotnet add packagedotnet add reference 这种,add 后面还有 package 这个子命令,上面的方法还得继续拓展,首先定义 add 基类和 package 与 reference 继承类。

public class AddSettings : CommandSettings {     [CommandArgument(0, "[PROJECT]")]     public string Project { get; set; } }  public class AddPackageSettings : AddSettings {     [CommandArgument(0, "<PACKAGE_NAME>")]     public string PackageName { get; set; }      [CommandOption("-v|--version <VERSION>")]     public string Version { get; set; } }  public class AddReferenceSettings : AddSettings {     [CommandArgument(0, "<PROJECT_REFERENCE>")]     public string ProjectReference { get; set; } } 

然后对不同的命令,指定不同处理函数。

public class AddPackageCommand : Command<AddPackageSettings> {     public override int Execute(CommandContext context, AddPackageSettings settings)     {         // Omitted         return 0;     } }  public class AddReferenceCommand : Command<AddReferenceSettings> {     public override int Execute(CommandContext context, AddReferenceSettings settings)     {         // Omitted         return 0;     } } 

最后使用 AddBranch 进行组合:

using Spectre.Console.Cli;  namespace MyApp {     public static class Program     {         public static int Main(string[] args)         {             var app = new CommandApp();              app.Configure(config =>             {                 config.AddBranch<AddSettings>("add", add =>                 {                     add.AddCommand<AddPackageCommand>("package");                     add.AddCommand<AddReferenceCommand>("reference");                 });             });              return app.Run(args);         }     } } 

参考