Person
- int Id
- string Name
- DateTime Birthday
- Uri
- System.Text.Encoding Encoding
empty
number
- int n
- double? scale
- DayOfWeek
Text Template Transformation Toolkit (T4) はテンプレート エンジンの一つで、主に Visual Studio で使われているものです。
これを使うと、ソースコードやデータの集合などのファイルを自動生成できます。
今回は例として、次のような Markdown を記述したら、
それに対応するクラスやプロパティを C# のソースコードとして生成することを考えます。
Person
empty
number
この Markdown の仕様を次のように定めます。
以下では、このような .md ファイルを入力として、
プロパティおよびコンストラクターを持つ部分クラスを .cs ファイルに出力するように T4 で実装していきます。
まず、プロジェクトに上記の .md ファイルを追加しておきます。
そしてプロジェクトに「テキスト テンプレート (.tt)」 を追加します。
追加された .tt ファイルを、仕様に従って次のように実装します。
<#@ template debug="false" hostspecific="true" language="C#" #> | |
<#@ assembly name="System.Core" #> | |
<#@ import namespace="System.Collections.Generic" #> | |
<#@ import namespace="System.IO" #> | |
<#@ import namespace="System.Linq" #> | |
<#@ output extension=".cs" #> | |
<# | |
var types = GetTypeDefs(Host.ResolvePath("RecordTypes.md")); | |
#> | |
using System; | |
namespace RecordGenConsole | |
{ | |
<# foreach (var type in types) { #> | |
public partial class <#= type.Key #> | |
{ | |
<# foreach (var prop in type.Value) { #> | |
public <#= prop.Value #> <#= prop.Key #> { get; } | |
<# } #> | |
public <#= type.Key #>(<#= string.Join(", ", type.Value.Select(p => $"{p.Value} {ToCamel(p.Key)}")) #>) | |
{ | |
<# foreach (var prop in type.Value) { #> | |
<#= prop.Key #> = <#= ToCamel(prop.Key) #>; | |
<# } #> | |
} | |
} | |
<# } #> | |
} | |
<#+ | |
Dictionary<string, Dictionary<string, string>> GetTypeDefs(string filePath) | |
{ | |
var lines = File.ReadLines(filePath) | |
.Where(l => !string.IsNullOrWhiteSpace(l)) | |
.ToArray(); | |
var typeLines = lines | |
.Select((l, i) => new { l, i }) | |
.Where(_ => !_.l.StartsWith("-")) | |
.ToArray(); | |
return typeLines | |
.ToDictionary(_ => ToPascal(_.l.Trim()), _ => GetProps(_.i)); | |
Dictionary<string, string> GetProps(int i) => lines | |
.Skip(i + 1) | |
.TakeWhile(l => l.StartsWith("-")) | |
.Select(l => l.Trim('-', ' ').Split()) | |
.ToDictionary(p => ToPascal(GetPropName(p)), p => p[0]); | |
} | |
string GetPropName(string[] prop) => prop[prop.Length == 1 ? 0 : 1]; | |
string ToPascal(string s) => char.ToUpperInvariant(s[0]) + s.Substring(1); | |
string ToCamel(string s) => char.ToLowerInvariant(s[0]) + s.Substring(1); | |
#> |
注意点は以下の通りです。
hostspecific="true"
を指定して Host.ResolvePath
メソッドを使う <#= #>
:テキストの出力 <# #>
:コードを書ける、変数を使える <#+ #>
:メソッド、クラスなどを定義できる .tt ファイルを保存したときに処理が実行されます。
または、.tt ファイルを右クリックして [カスタム ツールの実行] を選択すれば実行されます。
これで、以下のように RecordTypes.cs が生成されます。
using System; | |
using System.Text; | |
namespace RecordGenConsole | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var person = new Person(4, "Shiro", DateTime.UtcNow, null, Encoding.UTF8); | |
var n = new Number(3, 2.5, DayOfWeek.Sunday); | |
Console.WriteLine(n.Scaled); // 7.5 | |
} | |
} | |
public partial class Number | |
{ | |
public double Scaled => (Scale ?? 1) * N; | |
} | |
} |
using System; | |
namespace RecordGenConsole | |
{ | |
public partial class Person | |
{ | |
public int Id { get; } | |
public string Name { get; } | |
public DateTime Birthday { get; } | |
public Uri Uri { get; } | |
public System.Text.Encoding Encoding { get; } | |
public Person(int id, string name, DateTime birthday, Uri uri, System.Text.Encoding encoding) | |
{ | |
Id = id; | |
Name = name; | |
Birthday = birthday; | |
Uri = uri; | |
Encoding = encoding; | |
} | |
} | |
public partial class Empty | |
{ | |
public Empty() | |
{ | |
} | |
} | |
public partial class Number | |
{ | |
public int N { get; } | |
public double? Scale { get; } | |
public DayOfWeek DayOfWeek { get; } | |
public Number(int n, double? scale, DayOfWeek dayOfWeek) | |
{ | |
N = n; | |
Scale = scale; | |
DayOfWeek = dayOfWeek; | |
} | |
} | |
} |
作成したサンプル
テストしたバージョン
参照
ビルド用 PowerShell スクリプトの Build Release (GitHub) を .NET Framework プロジェクト形式向けに提供していましたが、
今回は .NET Core プロジェクト形式向けのビルドツールを追加しました。
ツールの内容:
アセンブリのバージョン (x.y.z の z の部分) を 1 だけ増加させます。
.NET Core プロジェクト形式では、プロジェクト ファイル (.csproj) でバージョンを書き換えます。
プロジェクトを Release でビルドして、ZIP ファイルを作成します。
ビルド前にアセンブリのバージョンを増加させます。
プロジェクトを Release でビルドして、NuGet パッケージを作成します。
ビルド前にアセンブリのバージョンを増加させます。
これらのツール (PowerShell スクリプト) を使う方法としては、Visual Studio の「外部ツール」に登録するのが便利だと思います。
前回にビルド用のスクリプトを Visual Studio の外部ツールに登録する方法について書きましたが、
.NET Core 版の手順も改めて以下に書いておきます。
Build-Release/Downloads (GitHub) からツールの最新版をダウンロードして任意のフォルダーに展開します。
Visual Studio のメニューで [ツール] – [外部ツール] を選択して各スクリプトを追加していきます。
powershell.exe
-ExecutionPolicy Unrestricted "C:\scripts_folder\KTools.xxx.ps1"
$(ProjectDir)
$(SolutionDir)
でもよい
.NET Core 向けのプロジェクト テンプレートを選択してプロジェクトを作成します。
.NET Framework プロジェクトではバージョン番号などを AssemblyInfo.cs に記述しますが、
.NET Core プロジェクトではプロジェクト ファイル (.csproj) に記述します。
初期状態ではバージョンが設定されていない (その場合は 1.0.0 と判定される) ため、
プロジェクトのプロパティで [パッケージ バージョン] の値を設定しておきます。
上記の設定をして保存すると、.csproj ファイルの <Version> に反映されます。
なお、.NET Core のプロジェクト形式でも、
<TargetFramework>net45</TargetFramework>
のようにすれば .NET Framework をターゲットにすることができます。
詳細は .NET Core と .NET Standard を参照してください。
ツールの実行:
対象のプロジェクト内のファイルを開いた状態で、メニューからスクリプトを選択すると実行されます。
実行すると、ログが Visual Studio に出力されます。
同様に、メニューから Zip Release を実行します。
zip フォルダーに ZIP ファイルが作成されます。
クラス ライブラリ プロジェクトを対象に NuGet Packup を実行します。
pkg フォルダーに NuGet パッケージが作成されます。
注意点
前回: ビルド用のスクリプトを Visual Studio の外部ツールに登録する
テスト済バージョン
Visual Studio 2017
参照
Build Release (GitHub)
外部ツールの管理
.nuspec File Reference for NuGet