以前制作したCUIアプリケーション開発用フレームワーク『CuiLib』を大規模改修し,リリースしました。
クラス設計を見直した部分が多々あり破壊的変更を多く伴うため,メジャーアップデートとしています。
当記事は,執筆時点での最新バージョン「2.0.1」について触れていきます。
リンク
- GitHub: https://github.com/Funny-Silkie/CuiLib
- NuGet: https://www.nuget.org/packages/CuiLib
- リリースログ: https://github.com/Funny-Silkie/CuiLib/blob/master/docs/RelHistory.md
目玉
対応する.NETバージョンを拡大
これまでは.NET 7.0のみでしたが,以下のランタイムにも対応するようになりました。
.NET Standardに対応したため,.NET Framework 4.6.2-4.8.1・.NET Core 2.0-3.1のプロジェクトから当フレームワークを利用できるようになっております。
- .NET Standard 2.0
- .NET Standard 2.1
- .NET 6.0
- .NET 8.0
- .NET 9.0
複数ランタイム対応について工夫した点など,今後記事にしていけたらいいなと思っています。
テストを拡充し,不具合炙り出し&修正
以前はテストの書き方について詳しくなかったため,最低限の動作しか保証されていない状態でした。
今回メジャーアップデートするにあたって,単体テストを網羅的に実装しました。
これによって多数の不具合が検出されたため,まとめて修正しました。
具体的な修正内容については冒頭のリリースノートのリンクからご確認ください。
ヘルプメッセージの細かいカスタマイズが可能に
これまでは Command.WriteHelp(TextWriter)
メソッドで対象のコマンドのヘルプメッセージを出力していました。
ただ,ヘルプメッセージのカスタマイズを行う際にはこのメソッドをオーバーライドして,一からヘルプメッセージを組み立て直す必要がありました。
当バージョンからは CuiLib.Output.IHelpMessageProvider
インターフェイスと CuiLib.Output.HelpMessageProvider
クラスが新たに実装されて, WriteHelp
メソッドのオーバーロードに WriteHelp(TextWriter, IHelpMessageProvider)
が追加されています。
HelpMessageProvider
クラスは IHelpMessageProvider
インターフェイスのデフォルトの実装で,継承することでヘッダー部分やオプション部分といった部分的なカスタマイズを行うことができます。
サンプルコード
using System;
using System.IO;
using System.Linq;
using CuiLib.Checkers;
using CuiLib.Commands;
using CuiLib.Options;
using CuiLib.Output;
using CuiLib.Parameters;
internal class Program
{
private static void Main(string[] args)
{
var command = new SampleCommand();
// デフォルトのヘルプメッセージ
command.WriteHelp(Console.Out, messageProvider: null);
// カスタマイズしたヘルプメッセージ
command.WriteHelp(Console.Out, new SampleHelpMessageProvider());
}
}
internal class SampleHelpMessageProvider : HelpMessageProvider
{
// オプション部分だけカスタマイズ
public override void WriteOptions(TextWriter writer, Command command)
{
writer.WriteLine("Options:");
foreach (NamedOption option in command.Options.OfType<NamedOption>())
{
writer.Write(" ");
if (option.ShortName is not null)
{
writer.Write($"-{option.ShortName}");
if (option.ValueTypeName is not null) writer.Write($" {option.ValueTypeName.ToUpper()}");
writer.Write(", ");
}
writer.Write($"--{option.FullName}");
if (option.ValueTypeName is not null) writer.Write($" {option.ValueTypeName.ToUpper()}");
writer.WriteLine(':');
writer.WriteLine($" {option.Description}");
writer.WriteLine();
}
}
}
internal class SampleCommand : Command
{
private readonly FlagOption optionHelp;
private readonly FlagOption optionVersion;
private readonly MultipleValueOption<FileInfo> optionInput;
private readonly SingleValueOption<int> optionNum;
private readonly MultipleValueParameter<string> parameters;
public SampleCommand() : base("sample")
{
Description = "Sample command";
optionHelp = new FlagOption('h', "help")
{
Description = "Display help",
};
optionVersion = new FlagOption('v', "version")
{
Description = "Display version",
};
optionInput = new MultipleValueOption<FileInfo>('i', "in")
{
Description = "Input files",
Checker = ValueChecker.ValidSourceFile(),
Required = true,
};
optionNum = new SingleValueOption<int>("number")
{
Description = "Some number",
Required = false,
};
parameters = new MultipleValueParameter<string>("params", 0)
{
Description = "Other parameters",
};
Options.Add(optionHelp);
Options.Add(optionVersion);
Options.Add(optionInput);
Options.Add(optionNum);
Parameters.Add(parameters);
}
}
sample
Description:
Sample command
Usage:
sample [-h] [-v] -i file [--number int] <params ..>
Options:
-h, --help Display help
-v, --version Display version
-i, --in Input files
--number Some number
Parameters:
params Other parameters
sample
Description:
Sample command
Usage:
sample [-h] [-v] -i file [--number int] <params ..>
Options:
-h, --help:
Display help
-v, --version:
Display version
-i FILE, --in FILE:
Input files
--number INT:
Some number
Parameters:
params Other parameters
数値の範囲を表す型 ValueRange
・ ValueRangeCollection
を実装
コマンドラインアプリケーションを開発する際に, 1-3,5
のような数値の範囲を解析したくなる場合が出てきます。
そこで,範囲を表す型として CuiLib.Data.ValueRange
構造体と CuiLib.Data.ValueRangeCollection
クラスを実装しました。
ValueRange
構造体は範囲 1-3,5
のうち, 1-3
や 5
といった「連続した範囲」または「単一の値」を表します。
ValueRangeCollection
クラスは ValueRange
構造体のコレクションを表します。
両型とも IParsable<T>
インターフェイスを実装しており, Parse
メソッドで文字列からの変換をサポートしています。
IValueConverter<TIn, IOut>
による変換もサポートされており, SingleValueOption<T>
や SingleValueParameter<T>
といった引数用のクラスの型引数に ValueRangeCollection
を指定するだけで自動的に変換してくれます。
尚,どちらの型も IEnumerable<T>
を実装しているため,LINQによる処理を行うことも可能です。
サンプルプログラム
using System;
using System.IO;
using CuiLib.Commands;
using CuiLib.Data;
using CuiLib.Options;
internal class Program
{
private static void Main(string[] args)
{
using var writer = new StreamWriter("test.txt", false);
var command = new SampleCommand();
command.Invoke(["-r", "1-3,5,10-30,100"]);
}
}
internal class SampleCommand : Command
{
private readonly SingleValueOption<ValueRangeCollection> optionRange;
public SampleCommand() : base("sample")
{
Description = "Sample command";
optionRange = new SingleValueOption<ValueRangeCollection>('r', "range")
{
Description = "range of value",
Required = true,
};
Options.Add(optionRange);
}
protected override void OnExecution()
{
ValueRangeCollection ranges = optionRange.Value;
Console.WriteLine(ranges);
foreach (ValueRange currentRange in ranges)
{
if (currentRange.Start == currentRange.End) Console.WriteLine($" {currentRange}: represents single value '{currentRange}'");
else Console.WriteLine($" {currentRange}: represents the range {currentRange.Start} to {currentRange.End}");
}
}
}
1-3,5,10-30,100
1-3: represents the range 1 to 3
5: represents single value '5'
10-30: represents the range 10 to 30
100: represents single value '100'
新バージョン移行の際の注意点
今回のアップデートに際して破壊的変更が多数行われているため,バージョン移行の手引きをこちらに示しておきます。
命名の修正
命名の大整理を行ったため,バージョンを変えた直後はコンパイルエラーが多発します。
以下の項を基に参照や名前を更新してください。
名前空間の変更
-
CuiLib.Log
名前空間以下の全型 →CuiLib.Logging
-
IValueChecker<T>
インターフェイスとValueChecker
クラスについて:CuiLib.Options
→CuiLib.Checkers
-
IValueConverter<TIn, TOut>
インターフェイスとValueConverter
クラスについて:CuiLib.Options
→CuiLib.Converters
-
Parameter
クラスとその派生型,ParameterCollection
クラスについて:CuiLib.Options
→CuiLib.Parameters
クラス名の変更
-
IOHelper
→IOHelpers
ValueChecker
クラスのメソッド
-
AlwaysSuccess
->AlwaysValid
-
StartWith
->StartsWith
-
EndWith
->EndsWith
-
Contains
->ContainedIn
-
Larger
->GreaterThan
-
LargerOrEqual
->GreaterThanOrEqualTo
-
Lower
->LessThan
-
LowerOrEqual
->LessThanOrEqualTo
-
Equals
->EqualTo
-
NotEquals
->NotEqualTo
-
IsRegexMatch
->Matches
-
FileExists
->ExistsAsFile
-
DirectoryExists
->ExistsAsDirectory
-
VerifySourceFile
->ValidSourceFile
-
VerifyDestinationFile
->ValidDestinationFile
-
VerifySourceDirectory
->ValidSourceDirectory
Parameter<T>
クラスの設計変更
オプションと取り回しを合わせるため, Parameter<T>
クラスの設計を変更しました。
値を一つだけ取るオプションは SingleValueParameter<T>
クラスに,複数の値をとるオプションは MultipleValueParameter<T>
クラスに分割されています。
そして, Checker
プロパティと Converter
プロパティが Parameter<T>
クラスから削除されて各派生クラスへ移行されています。
そのため Parameter<T>.Checker
と Parameter<T>.Converter
を参照している場合は SingleValueParameter<T>
か MultipleValueParameter<T>
へキャストします。
後者については加えて以下の修正を行います。
-
Values
プロパティ →Value
プロパティ -
Parameter<T>
として代入している場合 →Parameter<T[]>
へ修正
最後に
改修を始めたから半年以上経過しましたが,やっとリリースすることができました。
GitHubのIssueがまだ残っているので,合間を縫ってぼちぼち実装をしていくつもりです。
不具合の報告や機能追加の要望などありましたら,Issueへ遠慮なくお知らせください。