最近发现自己喜欢用的 Todo 软件总是差点意思,毕竟每个人的习惯和工作流不太一样,我就想着自己写一个小的Todo 项目,核心的功能是自动记录 Todo 执行过程中消耗的时间(尤其面向程序员),按照自己的想法实现一套 GTD
工作流。
不想写 Winform
,WPF
也写腻了,就想着学学 MAUI
、Avalonia
、Uno Platform
、blazor
之类的。由于前端技术选型纠结,迟迟动不了手,想想还是暂时先不弄了。但为了测试,没有个界面总是不太行,先搞一个 CLI 吧。
更新:由于想让程序持续执行,所以后面还是替换了 CLI 。
Spectre.Console(spectreconsole.net) 是一个美化 Console 输出的类库,通过它可以实现丰富多样的 Console 输出。核心的特性有这些:
除此以外,它还提供了一个 Spectre.Console.Cli
类库,可以帮助我们实现类似 dotnet
、git
之类的 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 package
和 dotnet 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); } } }