将 T4 模板输出复制到新文件

发布于 2024-11-08 21:38:23 字数 1013 浏览 0 评论 0原文

我正在尝试使用 T4 模板来为我们的系统生成迁移稍微容易一些。我不太明白的一件事(这让我想知道我是否使用 T4 模板做错了事情)是如何将渲染的输出复制到新文件。我可以手动创建一个文件并复制生成的文件的内容,但这违背了我的整个“让事情变得更容易”的精神。

这是我的模板。渲染后,理想情况下它会被复制到同一目录中的“62-CreateWidgetsTable.cs”。目标是拥有一个我现在可以编辑的文件(我正在生成一个模板,换句话说,不是生成完整的文件。)如果我可以在 VS 中重命名生成的文件(然后让 t4 生成一个新模板就坐在那里直到有人出现并使用它),这就足够了。

  <#@ template debug="false" hostspecific="false" language="C#" #>
  <#@ output extension=".cs" #>
  <#
    var migrationNumber = "62";
    var migrationName = "CreateWidgetsTable";
  #>
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using Migrator.Framework;

  namespace WidgetsIntl.Console.Migrations
  {
    [Migration(<#= DateTime.UtcNow.ToString("yyyyMMddhhmmss") #>)]
    public class _<#= migrationNumber  #>_<#= migrationName #> : Migration
    {
      public override void Up()
      {

      }

      public override void Down()
      {

      }
    }
  }

I'm trying to use T4 templates to make generating migrations for our system slightly easier. The one thing that I can't quite figure out (and this makes me wonder if I'm using T4 templates for the wrong thing) is how to copy the rendered output to a new file. I can manually create a file and copy the contents of the generated file, but that kind of goes against my whole "make things easier" ethos here.

Here's the template I have. Upon rendering, it would ideally get copied to "62-CreateWidgetsTable.cs" in the same directory. The goal is to have a file that I can now edit (I am generating a template, in other words, not generating the complete file.) If I could rename the generated file in VS (and then have the t4 generate a new template that would just sit there until someone came along and used it), that would be good enough.

  <#@ template debug="false" hostspecific="false" language="C#" #>
  <#@ output extension=".cs" #>
  <#
    var migrationNumber = "62";
    var migrationName = "CreateWidgetsTable";
  #>
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using Migrator.Framework;

  namespace WidgetsIntl.Console.Migrations
  {
    [Migration(<#= DateTime.UtcNow.ToString("yyyyMMddhhmmss") #>)]
    public class _<#= migrationNumber  #>_<#= migrationName #> : Migration
    {
      public override void Up()
      {

      }

      public override void Down()
      {

      }
    }
  }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

孤城病女 2024-11-15 21:38:23

好吧,我想出了几种方法来做到这一点。最简单的方法(我在按照我要向您展示的方式进行操作之后才发现)如下:t4 将输出附加到现有文件。关键信息是 GenerationEnvironment 是一个 StringBuilder,其中包含运行模板的结果,因此您可以将该结果写入您想要的任何旧文件!

另一种方法是使用 T4 Toolbox。快去下载吧!

然后,您可以创建一个模板,该模板创建一个扩展 Template (由 T4 工具箱定义)的类,该类覆盖一些默认行为:

<#@ template language="C#" hostspecific="True" debug="True" #>
<#@ include file="T4Toolbox.tt" #>
<#
    // make an instance of the class we define below, set some variables, and render it
    var tpl = new MyT4();
    tpl.MyVar = "Do those things you do";
    tpl.Render();
#>
<#+
public class MyT4 : Template 
{
    public MyVar = "some stuff";

    public override string TransformText()
    {
        Output.PreserveExistingFile = true; // tells T4 that you want to manually edit this file afterward (for scaffoling, which was my use case)
        Output.File = MyVar + ".cs"; // output will go in "some stuff.cs"

        /******************
        Template is defined here!
        *******************/
    #>
    public class <#=myVar.Replace(" ", "_") #> 
    { 
        public void Method()
        {
            return "Hi, I am <#= myvar #>";
        }
    }
    <#+
        /*************************
        now finishing up the TransformText() method
        *************************/

        return GenerationEnvironment.ToString();
    }
}
#>

Alright, I figured out a couple ways to do this. The simplest way to do it (which I found only after doing it the way I'm about to show you) is here: t4 append output to existing file. The key information is GenerationEnvironment is a StringBuilder which contains the result of running the template, so you can just write that result to any old file you want!

The other way to do it is to use T4 Toolbox. Go download it!

Then, you can make a template that create a class that extends Template (defined by T4 toolbox) that overrides some default behavior:

<#@ template language="C#" hostspecific="True" debug="True" #>
<#@ include file="T4Toolbox.tt" #>
<#
    // make an instance of the class we define below, set some variables, and render it
    var tpl = new MyT4();
    tpl.MyVar = "Do those things you do";
    tpl.Render();
#>
<#+
public class MyT4 : Template 
{
    public MyVar = "some stuff";

    public override string TransformText()
    {
        Output.PreserveExistingFile = true; // tells T4 that you want to manually edit this file afterward (for scaffoling, which was my use case)
        Output.File = MyVar + ".cs"; // output will go in "some stuff.cs"

        /******************
        Template is defined here!
        *******************/
    #>
    public class <#=myVar.Replace(" ", "_") #> 
    { 
        public void Method()
        {
            return "Hi, I am <#= myvar #>";
        }
    }
    <#+
        /*************************
        now finishing up the TransformText() method
        *************************/

        return GenerationEnvironment.ToString();
    }
}
#>
债姬 2024-11-15 21:38:23

在某些项目中,我使用了下面的 FileManager 类。它是基于此博客文章的自定义实现: http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited

<#@ assembly name="System.Core"
#><#@ assembly name="System.Data.Linq"
#><#@ assembly name="EnvDTE"
#><#@ assembly name="System.Xml"
#><#@ assembly name="System.Xml.Linq"
#><#@ import namespace="System"
#><#@ import namespace="System.CodeDom"
#><#@ import namespace="System.CodeDom.Compiler"
#><#@ import namespace="System.Collections.Generic"
#><#@ import namespace="System.Data.Linq"
#><#@ import namespace="System.Data.Linq.Mapping"
#><#@ import namespace="System.IO"
#><#@ import namespace="System.Linq"
#><#@ import namespace="System.Reflection"
#><#@ import namespace="System.Text"
#><#@ import namespace="System.Xml.Linq"
#><#@ import namespace="Microsoft.VisualStudio.TextTemplating"
#><#+

// Manager class records the various blocks so it can split them up
protected abstract class FileManager {

    protected FileManager(ITextTemplatingEngineHost host, StringBuilder template)
    {
        this.host = host;
        this.template = template;
    }

    protected abstract void CreateFile(String fileName, String content);
    public abstract String GetCustomToolNamespace(String fileName);
    public abstract String DefaultProjectNamespace { get; }
    public abstract void Process();

    public static FileManager Create(ITextTemplatingEngineHost host, StringBuilder template) 
    {
        return new VSManager(host, template);
    }

    protected class Block
    {
        public String Name;
        public int Start, Length;
    }

    protected Block currentBlock;
    protected List<Block> files = new List<Block>();
    protected Block footer = new Block();
    protected Block header = new Block();
    protected ITextTemplatingEngineHost host;
    protected StringBuilder template;

    public void StartNewFile(String name) 
    {
        if (name == null)
            throw new ArgumentNullException("name");

        CurrentBlock = new Block { Name = name };
    }

    public void StartFooter() {
        CurrentBlock = footer;
    }

    public void StartHeader() {
        CurrentBlock = header;
    }

    public void EndBlock() {
        if (CurrentBlock == null)
            return;
        CurrentBlock.Length = template.Length - CurrentBlock.Start;
        if (CurrentBlock != header && CurrentBlock != footer)
            files.Add(CurrentBlock);

        currentBlock = null;
    }

    protected bool IsFileContentDifferent(String fileName, String newContent) 
    {
        return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
    }

    protected Block CurrentBlock 
    {
        get { return currentBlock; }
        set {
            if (CurrentBlock != null)
                EndBlock();
            if (value != null)
                value.Start = template.Length;
            currentBlock = value;
        }
    }

    // VS Manager
    private class VSManager: FileManager 
    {
        private EnvDTE.ProjectItem templateProjectItem;
        private EnvDTE.DTE dte;
        private List<string> generatedFileNames = new List<string>();

        public override String DefaultProjectNamespace 
        {
            get 
            {
                return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString();
            }
        }

        public override String GetCustomToolNamespace(string fileName) 
        {
            return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString();
        }

        public override void Process() 
        {           
            EndBlock();
            String headerText = template.ToString(header.Start, header.Length);
            String footerText = template.ToString(footer.Start, footer.Length);

            Directory.SetCurrentDirectory(Path.GetDirectoryName(host.TemplateFile));

            files.Reverse();
            foreach(Block block in files) 
            {
                String fileName = Path.GetFullPath(block.Name);
                String content = headerText + template.ToString(block.Start, block.Length) + footerText;
                generatedFileNames.Add(fileName);
                CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            }

            this.ProjectSync(generatedFileNames);
            this.files = new List<Block>();
            this.footer = new Block();
            this.header = new Block();
            this.generatedFileNames = new List<string>();
        }

        protected override void CreateFile(String fileName, String content)
        {
            if (IsFileContentDifferent(fileName, content)) 
            {
                CheckoutFileIfRequired(fileName);
                File.WriteAllText(fileName, content);
            }
        }

        internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) : base(host, template) 
        {
            var hostServiceProvider = host as IServiceProvider;
            if (hostServiceProvider == null)
            {
                throw new ArgumentNullException("Could not obtain IServiceProvider");
            }

            this.dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
            if (this.dte == null)
            {
                throw new ArgumentNullException("Could not obtain DTE from host");
            }
        }

        private void ProjectSync(IEnumerable<string> keepFileNames) {
            var projectFiles = new Dictionary<string, EnvDTE.ProjectItem>();

            foreach (string keepFileName in keepFileNames)
            {
                var item = this.dte.Solution.FindProjectItem(keepFileName);
                if (item != null)
                {
                    projectFiles.Add(keepFileName, item);
                }
            }

            // Remove unused items from the project 
            /* foreach(var pair in projectFiles) // NEW
            {
                if (keepFileNames.Contains(pair.Key))
                {
                    pair.Value.Delete();
                }
            } */

            // Add missing files to the project
            foreach(string fileName in keepFileNames)
            {
                if (!projectFiles.ContainsKey(fileName))
                {       
                    EnvDTE.Project targetProj = null;
                    foreach (EnvDTE.Project proj in this.dte.Solution.Projects)
                    {
                        if (string.IsNullOrEmpty(proj.FullName))
                        {
                            continue;
                        }

                        if (fileName.Contains(Path.GetDirectoryName(proj.FullName) + @"\"))
                        {
                            targetProj = proj;
                            break;
                        }
                    }       

                    var targetDir = NavigateTo(targetProj, fileName);       
                    if (targetDir == null)
                    {
                        targetProj.ProjectItems.AddFromFile(fileName);
                        continue;
                    }

                    targetDir.ProjectItems.AddFromFile(fileName);
                }
            }
        }

        private void CheckoutFileIfRequired(String fileName) 
        {
            var sc = dte.SourceControl;
            if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName))
            {
                dte.SourceControl.CheckOutItem(fileName);
            }
        }

        public EnvDTE.ProjectItem NavigateTo(EnvDTE.Project project, string path)
        {
            if (string.IsNullOrEmpty(project.FullName))
            {
                return null;
            }

            var projBase = Path.GetDirectoryName(project.FullName);
            var fileBase = Path.GetDirectoryName(path);
            var naviBase = fileBase.Replace(projBase + @"\", "");

            if (string.IsNullOrEmpty(fileBase.Replace(projBase, "")))
            {
                return null;
            }

            var naviPoints = naviBase.Split('\\');
            EnvDTE.ProjectItem item = null;
            EnvDTE.ProjectItems items = project.ProjectItems;

            foreach (var folder in naviPoints)
            {
                item = items.Item(folder);
                items = item.ProjectItems;
            }

            return item;
        }
    }
} #>

In some Projects I use allready the FileManager class below. Its a custominated implementation based on this blog post: http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited

<#@ assembly name="System.Core"
#><#@ assembly name="System.Data.Linq"
#><#@ assembly name="EnvDTE"
#><#@ assembly name="System.Xml"
#><#@ assembly name="System.Xml.Linq"
#><#@ import namespace="System"
#><#@ import namespace="System.CodeDom"
#><#@ import namespace="System.CodeDom.Compiler"
#><#@ import namespace="System.Collections.Generic"
#><#@ import namespace="System.Data.Linq"
#><#@ import namespace="System.Data.Linq.Mapping"
#><#@ import namespace="System.IO"
#><#@ import namespace="System.Linq"
#><#@ import namespace="System.Reflection"
#><#@ import namespace="System.Text"
#><#@ import namespace="System.Xml.Linq"
#><#@ import namespace="Microsoft.VisualStudio.TextTemplating"
#><#+

// Manager class records the various blocks so it can split them up
protected abstract class FileManager {

    protected FileManager(ITextTemplatingEngineHost host, StringBuilder template)
    {
        this.host = host;
        this.template = template;
    }

    protected abstract void CreateFile(String fileName, String content);
    public abstract String GetCustomToolNamespace(String fileName);
    public abstract String DefaultProjectNamespace { get; }
    public abstract void Process();

    public static FileManager Create(ITextTemplatingEngineHost host, StringBuilder template) 
    {
        return new VSManager(host, template);
    }

    protected class Block
    {
        public String Name;
        public int Start, Length;
    }

    protected Block currentBlock;
    protected List<Block> files = new List<Block>();
    protected Block footer = new Block();
    protected Block header = new Block();
    protected ITextTemplatingEngineHost host;
    protected StringBuilder template;

    public void StartNewFile(String name) 
    {
        if (name == null)
            throw new ArgumentNullException("name");

        CurrentBlock = new Block { Name = name };
    }

    public void StartFooter() {
        CurrentBlock = footer;
    }

    public void StartHeader() {
        CurrentBlock = header;
    }

    public void EndBlock() {
        if (CurrentBlock == null)
            return;
        CurrentBlock.Length = template.Length - CurrentBlock.Start;
        if (CurrentBlock != header && CurrentBlock != footer)
            files.Add(CurrentBlock);

        currentBlock = null;
    }

    protected bool IsFileContentDifferent(String fileName, String newContent) 
    {
        return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
    }

    protected Block CurrentBlock 
    {
        get { return currentBlock; }
        set {
            if (CurrentBlock != null)
                EndBlock();
            if (value != null)
                value.Start = template.Length;
            currentBlock = value;
        }
    }

    // VS Manager
    private class VSManager: FileManager 
    {
        private EnvDTE.ProjectItem templateProjectItem;
        private EnvDTE.DTE dte;
        private List<string> generatedFileNames = new List<string>();

        public override String DefaultProjectNamespace 
        {
            get 
            {
                return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString();
            }
        }

        public override String GetCustomToolNamespace(string fileName) 
        {
            return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString();
        }

        public override void Process() 
        {           
            EndBlock();
            String headerText = template.ToString(header.Start, header.Length);
            String footerText = template.ToString(footer.Start, footer.Length);

            Directory.SetCurrentDirectory(Path.GetDirectoryName(host.TemplateFile));

            files.Reverse();
            foreach(Block block in files) 
            {
                String fileName = Path.GetFullPath(block.Name);
                String content = headerText + template.ToString(block.Start, block.Length) + footerText;
                generatedFileNames.Add(fileName);
                CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            }

            this.ProjectSync(generatedFileNames);
            this.files = new List<Block>();
            this.footer = new Block();
            this.header = new Block();
            this.generatedFileNames = new List<string>();
        }

        protected override void CreateFile(String fileName, String content)
        {
            if (IsFileContentDifferent(fileName, content)) 
            {
                CheckoutFileIfRequired(fileName);
                File.WriteAllText(fileName, content);
            }
        }

        internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) : base(host, template) 
        {
            var hostServiceProvider = host as IServiceProvider;
            if (hostServiceProvider == null)
            {
                throw new ArgumentNullException("Could not obtain IServiceProvider");
            }

            this.dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
            if (this.dte == null)
            {
                throw new ArgumentNullException("Could not obtain DTE from host");
            }
        }

        private void ProjectSync(IEnumerable<string> keepFileNames) {
            var projectFiles = new Dictionary<string, EnvDTE.ProjectItem>();

            foreach (string keepFileName in keepFileNames)
            {
                var item = this.dte.Solution.FindProjectItem(keepFileName);
                if (item != null)
                {
                    projectFiles.Add(keepFileName, item);
                }
            }

            // Remove unused items from the project 
            /* foreach(var pair in projectFiles) // NEW
            {
                if (keepFileNames.Contains(pair.Key))
                {
                    pair.Value.Delete();
                }
            } */

            // Add missing files to the project
            foreach(string fileName in keepFileNames)
            {
                if (!projectFiles.ContainsKey(fileName))
                {       
                    EnvDTE.Project targetProj = null;
                    foreach (EnvDTE.Project proj in this.dte.Solution.Projects)
                    {
                        if (string.IsNullOrEmpty(proj.FullName))
                        {
                            continue;
                        }

                        if (fileName.Contains(Path.GetDirectoryName(proj.FullName) + @"\"))
                        {
                            targetProj = proj;
                            break;
                        }
                    }       

                    var targetDir = NavigateTo(targetProj, fileName);       
                    if (targetDir == null)
                    {
                        targetProj.ProjectItems.AddFromFile(fileName);
                        continue;
                    }

                    targetDir.ProjectItems.AddFromFile(fileName);
                }
            }
        }

        private void CheckoutFileIfRequired(String fileName) 
        {
            var sc = dte.SourceControl;
            if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName))
            {
                dte.SourceControl.CheckOutItem(fileName);
            }
        }

        public EnvDTE.ProjectItem NavigateTo(EnvDTE.Project project, string path)
        {
            if (string.IsNullOrEmpty(project.FullName))
            {
                return null;
            }

            var projBase = Path.GetDirectoryName(project.FullName);
            var fileBase = Path.GetDirectoryName(path);
            var naviBase = fileBase.Replace(projBase + @"\", "");

            if (string.IsNullOrEmpty(fileBase.Replace(projBase, "")))
            {
                return null;
            }

            var naviPoints = naviBase.Split('\\');
            EnvDTE.ProjectItem item = null;
            EnvDTE.ProjectItems items = project.ProjectItems;

            foreach (var folder in naviPoints)
            {
                item = items.Item(folder);
                items = item.ProjectItems;
            }

            return item;
        }
    }
} #>
薄荷港 2024-11-15 21:38:23

我发现在没有插件的情况下执行此操作的最简单方法是右键单击目标项目并转到“添加”->“现有项目并选择您生成的文件。这会在项目的根目录下为您创建一个文件副本,然后您可以根据需要移动该副本。这使您可以轻松地将生成的文件传输到生成它们的项目之外的项目中。

我还没有测试当 .tt 文件本身位于项目的根目录中时会发生什么,但只要 .tt 文件存在,这绝对有效。位于子文件夹中(无论如何,这可能是一个很好的做法)。

The easiest way I have found to do this without plugins is to right-click your target project and go to Add -> Existing Item and select your generated file. This makes a copy of the file for you at the root of the project, which you can then move as needed. This allows you to easily transfer generated files to projects besides the one that they were generated in.

I haven't tested what happens when the .tt file is itself in the root of a project, but this definitely works so long as the .tt is in a subfolder (which is probably good practice anyhow).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文