## 引言 最近發現自己喜歡用的 Todo 軟體總是差點意思,畢竟每個人的習慣和工作流不太一樣,我就想著自己寫一個小的[Todo 項目]( https://github.com/circler3/TodoTrack ),核心的功能是自動記錄 Todo 執行過程中消耗的時間(尤其面向程式員),按照自己 ...
引言
最近發現自己喜歡用的 Todo 軟體總是差點意思,畢竟每個人的習慣和工作流不太一樣,我就想著自己寫一個小的Todo 項目,核心的功能是自動記錄 Todo 執行過程中消耗的時間(尤其面向程式員),按照自己的想法實現一套 GTD
工作流。
不想寫 Winform
,WPF
也寫膩了,就想著學學 MAUI
、Avalonia
、Uno Platform
、blazor
之類的。由於前端技術選型糾結,遲遲動不了手,想想還是暫時先不弄了。但為了測試,沒有個界面總是不太行,先搞一個 CLI 吧。
更新:由於想讓程式持續執行,所以後面還是替換了 CLI 。
Spectre. Console
Spectre.Console(spectreconsole.net) 是一個美化 Console 輸出的類庫,通過它可以實現豐富多樣的 Console 輸出。核心的特性有這些:
- 格式化輸出文本(支持斜體等)
- 支持對文字著色
- 渲染複雜的組件(表格、結構樹、ASCII 圖片)
- 顯示進度條與狀態
- 強類型輸入驗證
- 對 exception 輸出著色
除此以外,它還提供了一個 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);
}
}
}