最近發現自己喜歡用的 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);
}
}
}