diff --git a/T4.Build/.gitignore b/T4.Build/.gitignore new file mode 100644 index 0000000..eeaa096 --- /dev/null +++ b/T4.Build/.gitignore @@ -0,0 +1,2 @@ +# Ignore any immediate `tools` directories, as they are likely someone building and testing locally from another project +/tools/* \ No newline at end of file diff --git a/T4.Build/BuildTemplateGenerator.cs b/T4.Build/BuildTemplateGenerator.cs index a117e62..f867942 100644 --- a/T4.Build/BuildTemplateGenerator.cs +++ b/T4.Build/BuildTemplateGenerator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.CodeDom.Compiler; using System.IO; +using System.Text; using Mono.TextTemplating; namespace T4.Build @@ -11,6 +12,7 @@ class BuildTemplateGenerator : TemplateGenerator List includedFiles = new List(); string lockPath; ParsedTemplate parsedTemplate; + readonly Func preprocessor = null; public BuildTemplateGenerator(string template) { @@ -18,6 +20,12 @@ public BuildTemplateGenerator(string template) Engine.UseInProcessCompiler(); } + public BuildTemplateGenerator(string template, Func preprocessor) + : this(template) + { + this.preprocessor = preprocessor; + } + public string[] IncludedFiles { get @@ -98,24 +106,64 @@ public bool ProcessTemplate(bool skipUpToDate, out bool skipped) { skipped = false; - if (string.IsNullOrEmpty(OutputFile)) + var outputFile = OutputFile; + if (string.IsNullOrWhiteSpace(outputFile)) + { return false; + } if (skipUpToDate) { var templateInfo = new FileInfo(TemplateFile); - var outputInfo = new FileInfo(OutputFile); + var outputInfo = new FileInfo(outputFile); if (outputInfo.Exists - && templateInfo.LastWriteTime.Ticks < outputInfo.LastWriteTime.Ticks -&& includedFiles.TrueForAll(x => (new FileInfo(x)).LastWriteTime.Ticks < outputInfo.LastWriteTime.Ticks) + && templateInfo.LastWriteTime.Ticks < outputInfo.LastWriteTime.Ticks + && includedFiles.TrueForAll(x => (new FileInfo(x)).LastWriteTime.Ticks < outputInfo.LastWriteTime.Ticks) ) { skipped = true; return true; } } - return ProcessTemplate(TemplateFile, OutputFile); + + string content; + try + { + content = File.ReadAllText(TemplateFile); + } + catch (IOException ex) + { + Errors.Clear(); + AddError($"Could not read input file '{TemplateFile}':\n{ex}"); + return false; + } + + if (!(preprocessor is null)) + { + content = preprocessor(content); + } + ProcessTemplate(TemplateFile, content, ref outputFile, out var output); + + try + { + if (!Errors.HasErrors) + { + File.WriteAllText(outputFile, output, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + } + catch (IOException ex) + { + AddError($"Could not write output file '{outputFile}':\n{ex}"); + } + + return !Errors.HasErrors; } + CompilerError AddError(string error) + { + var err = new CompilerError { ErrorText = error }; + Errors.Add(err); + return err; + } } } \ No newline at end of file diff --git a/T4.Build/Program.cs b/T4.Build/Program.cs index 725cbcf..67a42f0 100644 --- a/T4.Build/Program.cs +++ b/T4.Build/Program.cs @@ -2,6 +2,7 @@ using System.CodeDom.Compiler; using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Invocation; using System.Diagnostics; @@ -30,7 +31,12 @@ static int Main(string[] args) transformCommand.Add(skipOption); var parallelOption = new Option(new[] { "--parallel", "-p" }, "Transform templates in parallel"); transformCommand.Add(parallelOption); - transformCommand.Handler = CommandHandler.Create(Transform); + var variableOptions = new Option(new[] { "--variable", "-v" }, "T4ParameterValues variable to replace within the template; syntax is \"Include=Value\"") + { + AllowMultipleArgumentsPerToken = false, + }; + transformCommand.Add(variableOptions); + transformCommand.Handler = CommandHandler.Create(Transform); rootCommand.Add(transformCommand); var cleanCommand = new Command("clean", "List the output files"); @@ -41,7 +47,7 @@ static int Main(string[] args) return rootCommand.InvokeAsync(args).Result; } - static int Transform(FileInfo[] templates, int lockTimeout, bool skipUpToDate, bool parallel) + static int Transform(FileInfo[] templates, int lockTimeout, bool skipUpToDate, bool parallel, string[] variable) { using (var locker = new Lock($"Global\\T4.Build.transform.{ComputeHash(templates)}.lock", lockTimeout)) { @@ -50,12 +56,13 @@ static int Transform(FileInfo[] templates, int lockTimeout, bool skipUpToDate, b var didSomeWork = false; var stopwatch = new Stopwatch(); + var t4ParameterValues = ProcessT4ParameterValues(variable); stopwatch.Start(); Parallel.ForEach(templates, new ParallelOptions { MaxDegreeOfParallelism = parallel ? -1 : 1 }, t => { - var generator = new BuildTemplateGenerator(t.ToString()); + var generator = new BuildTemplateGenerator(t.ToString(), templateContent => PreprocessTemplate(templateContent, t4ParameterValues)); try { bool skipped; @@ -175,6 +182,42 @@ static void LogErrors(CompilerErrorCollection errors) Console.ForegroundColor = oldColor; } + + static IReadOnlyDictionary ProcessT4ParameterValues(string[] variableSets) + { + if (variableSets?.Any() != true) + { + return null; + } + + var t4ParameterValues = new Dictionary(variableSets.Length); + foreach (var variableSet in variableSets) + { + var pair = variableSet.Split('=', 2, StringSplitOptions.None); + t4ParameterValues.Add($"$({pair[0]})", pair[1]); + } + + return t4ParameterValues; + } + + static string PreprocessTemplate(string templateContent, IReadOnlyDictionary t4ParameterValues) + { + if (string.IsNullOrWhiteSpace(templateContent) || t4ParameterValues?.Keys.Any() != true) + { + return templateContent; + } + + var replacementBuilder = (StringBuilder)null; + foreach (var t4ParameterValuesPair in t4ParameterValues) + { + if (replacementBuilder is null && templateContent.IndexOf(t4ParameterValuesPair.Key) > -1) + { + replacementBuilder = new StringBuilder(templateContent); + } + replacementBuilder?.Replace(t4ParameterValuesPair.Key, t4ParameterValuesPair.Value); + } + + return replacementBuilder is null ? templateContent : replacementBuilder.ToString(); + } } } - diff --git a/T4.Build/build/common.targets b/T4.Build/build/common.targets index 7109c5c..c0a0ef1 100644 --- a/T4.Build/build/common.targets +++ b/T4.Build/build/common.targets @@ -22,7 +22,7 @@ - + @@ -31,10 +31,24 @@ - + + + %(T4ParameterValues.Identity) + %(T4ParameterValues.Value) + + + $(T4Build_ParameterValue.Replace('"', '""')) + + + + + + + +