/// <summary>
        /// Writes generated content to output files and deletes the old files that were not regenerated.
        /// </summary>
        /// <param name="inputFile">
        /// Full path to the template file.
        /// </param>
        /// <param name="outputFiles">
        /// A collection of <see cref="OutputFile"/> objects produced by the template.
        /// </param>
        void ITransformationContextProvider.UpdateOutputFiles(string inputFile, OutputFile[] outputFiles)
        {
            if (inputFile == null)
            {
                throw new ArgumentNullException("inputFile");
            }

            if (!Path.IsPathRooted(inputFile))
            {
                throw new ArgumentException(Resources.InputFilePathMustBeAbsoluteMessage, "inputFile");
            }

            if (outputFiles == null)
            {
                throw new ArgumentNullException("outputFiles");
            }

            foreach (OutputFile newOutput in outputFiles)
            {
                if (newOutput == null)
                {
                    throw new ArgumentException(Resources.OutputFileIsNullMessage, "outputFiles");
                }
            }

            // Validate the output files immediately. Exceptions will be reported by the templating service.
            var manager = new OutputFileManager(this.serviceProvider, inputFile, outputFiles);
            manager.Validate();

            // Wait for the default output file to be generated
            var watcher = new FileSystemWatcher();
            watcher.Path = Path.GetDirectoryName(inputFile);
            watcher.Filter = Path.GetFileNameWithoutExtension(inputFile) + "*." + this.GetTransformationOutputExtensionFromHost();

            FileSystemEventHandler runManager = (sender, args) =>
            {
                watcher.Dispose();

                // Store the actual output file name
                OutputFile defaultOutput = outputFiles.FirstOrDefault(output => string.IsNullOrEmpty(output.File));
                if (defaultOutput != null)
                {
                    defaultOutput.File = Path.GetFileName(args.FullPath);
                }

                // Finish updating the output files on the UI thread
                ThreadHelper.Generic.BeginInvoke(manager.DoWork);
            };

            watcher.Created += runManager;
            watcher.Changed += runManager;
            watcher.EnableRaisingEvents = true;
        }
Example #2
0
        /// <summary>
        /// Appends generated <paramref name="content"/> to the specified <see cref="OutputInfo.File"/>.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that describes content generated by a template.
        /// </param>
        /// <param name="content">
        /// A <see cref="String"/> that contains content generated by a template.
        /// </param>
        /// <param name="host">
        /// An <see cref="ITextTemplatingEngineHost"/> object hosting the transformation.
        /// </param>
        private void AppendToOutputFile(OutputInfo output, string content, ITextTemplatingEngineHost host)
        {
            // If some content was already generated for this file
            string filePath = GetFullFilePath(output, host);
            OutputFile outputFile = this.outputFiles.FirstOrDefault(o => OutputInfo.SamePath(o.File, filePath));
            if (outputFile != null)
            {
                // Verify that output properties match
                Validate(output, outputFile);
            }
            else
            {
                // Otherwise, create a new output container
                outputFile = new OutputFile();
                outputFile.File = filePath;
                outputFile.Project = GetFullProjectPath(output);
                outputFile.Encoding = output.Encoding;
                outputFile.BuildAction = output.BuildAction;
                outputFile.CustomTool = output.CustomTool;
                outputFile.CustomToolNamespace = output.CustomToolNamespace;
                outputFile.CopyToOutputDirectory = output.CopyToOutputDirectory;
                outputFile.PreserveExistingFile = output.PreserveExistingFile;
                this.outputFiles.Add(outputFile);
            }

            outputFile.Content.Append(content);
            outputFile.AppendBuildProperties(output.BuildProperties);
            outputFile.AppendReferences(output.References);
        }
        /// <summary>
        /// Writes text to the output of this transformation.
        /// </summary>
        /// <param name="output">An <see cref="OutputItem"/> object that specifies how the text must be saved.</param>
        /// <param name="text">A <see cref="String"/> that contains generated content.</param>
        public void Write(OutputItem output, string text)
        {
            if (output == null)
            {
                throw new ArgumentNullException("output");
            }

            output.Validate();

            OutputFile outputFile = this.outputFiles.FirstOrDefault(o => string.Equals(o.Path, output.Path, StringComparison.OrdinalIgnoreCase));

            // If content was previously generated for this file
            if (outputFile != null)
            {
                // Validate new output attributes for consistency
                outputFile.Validate(output);
            }
            else
            {
                // create a new output file
                outputFile = new OutputFile();
                this.outputFiles.Add(outputFile);
            }

            outputFile.CopyPropertiesFrom(output); 
            
            if (string.IsNullOrEmpty(output.File))
            {
                // Append text to transformation output
                this.WriteToTransformation(text);
            }
            else
            {
                // Append text to the output file
                outputFile.Content.Append(text);                
            }
        }
        /// <summary>
        /// Creates a new output file or updates it's contents if it already exists.
        /// </summary>
        /// <param name="output">An <see cref="OutputFile"/> object.</param>
        private static void UpdateOutputFile(OutputFile output)
        {
            if (File.Exists(output.File))
            {
                // Don't do anything unless the output file has changed and needs to be overwritten
                if (output.Content.ToString() == File.ReadAllText(output.File, output.Encoding))
                {
                    return;
                }

                // Don't do anything if the file should be preserved.
                if (output.PreserveExistingFile)
                {
                    return;
                }
            }

            // Check out the file if it is under source control
            SourceControl sourceControl = GetDte().SourceControl;
            if (sourceControl.IsItemUnderSCC(output.File) && !sourceControl.IsItemCheckedOut(output.File))
            {
                sourceControl.CheckOutItem(output.File);
            }

            Directory.CreateDirectory(Path.GetDirectoryName(output.File));
            File.WriteAllText(output.File, output.Content.ToString(), output.Encoding);
        }
        /// <summary>
        /// Sets the known properties for the <see cref="ProjectItem"/> to be added to solution.
        /// </summary>
        /// <param name="projectItem">
        /// A <see cref="ProjectItem"/> that represents the generated item in the solution.
        /// </param>        
        /// <param name="output">
        /// An <see cref="OutputFile"/> that holds metadata about the <see cref="ProjectItem"/> to be added to the solution.
        /// </param>
        private static void SetProjectItemProperties(ProjectItem projectItem, OutputFile output)
        {
            // Set "Build Action" property
            if (!string.IsNullOrEmpty(output.BuildAction))
            {
                ICollection<string> buildActions = GetAvailableBuildActions(projectItem);
                if (!buildActions.Contains(output.BuildAction))
                {

                    throw new TransformationException(
                        string.Format(CultureInfo.CurrentCulture, "Build Action {0} is not supported for {1}", output.BuildAction, projectItem.Name));
                }

                SetPropertyValue(projectItem, "ItemType", output.BuildAction);
            }

            // Set "Copy to Output Directory" property
            if (output.CopyToOutputDirectory != default(CopyToOutputDirectory))
            {
                SetPropertyValue(projectItem, "CopyToOutputDirectory", (int)output.CopyToOutputDirectory);
            }

            // Set "Custom Tool" property
            if (!string.IsNullOrEmpty(output.CustomTool))
            {
                SetPropertyValue(projectItem, "CustomTool", output.CustomTool);
            }

            // Set "Custom Tool Namespace" property
            if (!string.IsNullOrEmpty(output.CustomToolNamespace))
            {
                SetPropertyValue(projectItem, "CustomToolNamespace", output.CustomToolNamespace);
            }
        }
        /// <summary>
        /// Sets the known properties for the <see cref="ProjectItem"/> added to solution.
        /// </summary>
        /// <param name="projectItem">
        /// A <see cref="ProjectItem"/> that represents the generated item in the solution.
        /// </param>        
        /// <param name="output">
        /// An <see cref="OutputFile"/> that holds metadata about the <see cref="ProjectItem"/> to be added to the solution.
        /// </param>
        private static void SetProjectItemBuildProperties(ProjectItem projectItem, OutputFile output)
        {
            if (output.BuildProperties.Count > 0)
            {
                // Get access to the build property storage service
                IServiceProvider serviceProvider = (IServiceProvider)Host;
                IVsSolution solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
                IVsHierarchy hierarchy;
                ErrorHandler.ThrowOnFailure(solution.GetProjectOfUniqueName(projectItem.ContainingProject.UniqueName, out hierarchy));
                IVsBuildPropertyStorage buildPropertyStorage = hierarchy as IVsBuildPropertyStorage;
                if (buildPropertyStorage == null)
                {
                    throw new TransformationException(
                        string.Format(
                            CultureInfo.CurrentCulture,
                            "Project {0} does not support build properties required by {1}",
                            projectItem.ContainingProject.Name,
                            projectItem.Name));
                }

                // Find the target project item in the property storage
                uint projectItemId;
                ErrorHandler.ThrowOnFailure(hierarchy.ParseCanonicalName(projectItem.get_FileNames(1), out projectItemId));

                // Set build projerties for the target project item
                foreach (KeyValuePair<string, string> buildProperty in output.BuildProperties)
                {
                    ErrorHandler.ThrowOnFailure(buildPropertyStorage.SetItemAttribute(projectItemId, buildProperty.Key, buildProperty.Value));
                }
            }
        }
        /// <summary>
        /// Finds project item collection for the output file in Visual Studio solution.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputFile"/> that needs to be added to the solution.
        /// </param>
        /// <param name="projects">
        /// All <see cref="Project"/>s in the current <see cref="Solution"/>.
        /// </param>
        /// <param name="template">
        /// A <see cref="ProjectItem"/> that represents T4 template being transformed.
        /// </param>
        /// <returns>A <see cref="ProjectItems"/> collection where the generated file should be added.</returns>
        private static ProjectItems FindProjectItemCollection(OutputFile output, IEnumerable<Project> projects, ProjectItem template)
        {
            ProjectItems collection; // collection to which output file needs to be added
            string relativePath;     // path from the collection to the file
            string basePath;         // absolute path to the directory to which an item is being added

            if (!string.IsNullOrEmpty(output.Project))
            {
                // If output file needs to be added to another project
                Project project = projects.First(p => OutputInfo.SamePath(GetPath(p), output.Project));
                collection = project.ProjectItems;
                relativePath = GetRelativePath(GetPath(project), output.File);
                basePath = Path.GetDirectoryName(GetPath(project));
            }
            else if (output.PreserveExistingFile || !OutputInfo.SamePath(Path.GetDirectoryName(output.File), Environment.CurrentDirectory))
            {
                // If output file needs to be added to another folder of the current project
                collection = template.ContainingProject.ProjectItems;
                relativePath = GetRelativePath(GetPath(template.ContainingProject), output.File);
                basePath = Path.GetDirectoryName(GetPath(template.ContainingProject));
            }
            else
            {
                // Add the output file to the list of children of the template file
                collection = template.ProjectItems;
                relativePath = GetRelativePath(template.get_FileNames(1), output.File);
                basePath = Path.GetDirectoryName(template.get_FileNames(1));
            }

            // make sure that all folders in the file path exist in the project.
            if (relativePath.StartsWith("." + Path.DirectorySeparatorChar, StringComparison.Ordinal))
            {
                // Remove leading .\ from the path
                relativePath = relativePath.Substring(relativePath.IndexOf(Path.DirectorySeparatorChar) + 1);

                while (relativePath.Contains(Path.DirectorySeparatorChar))
                {
                    string folderName = relativePath.Substring(0, relativePath.IndexOf(Path.DirectorySeparatorChar));
                    ProjectItem folder = AddFolder(collection, folderName, basePath);

                    collection = folder.ProjectItems;
                    relativePath = relativePath.Substring(folderName.Length + 1);
                    basePath = Path.Combine(basePath, folderName);
                }
            }

            return collection;
        }
        /// <summary>
        /// Creates a log file if any files were generated in a different folder or project.
        /// </summary>
        /// <param name="outputFiles">
        /// A collection of <see cref="OutputFile"/> objects.
        /// </param>
        /// <param name="template">
        /// A <see cref="ProjectItem"/> that represents T4 template being transformed.
        /// </param>
        /// <remarks>
        /// A log file is created as an <see cref="OutputFile"/> object and added to 
        /// the collection. The <see cref="UpdateOutputFiles"/> method takes care of 
        /// saving it.
        /// </remarks>
        private static void CreateLogIfNecessary(ICollection<OutputFile> outputFiles, ProjectItem template)
        {
            string templateProject = GetPath(template.ContainingProject);
            string templateDirectory = Environment.CurrentDirectory;

            //Only create a log file when the directories or the projects are different.
            if (outputFiles.Any(output =>
                                !OutputInfo.SamePath(Path.GetDirectoryName(output.File), templateDirectory)
                                || (!string.IsNullOrEmpty(output.Project) && !OutputInfo.SamePath(output.Project, templateProject))))
            {
                OutputFile log = new OutputFile();
                log.File = GetLogFileName();
                log.Project = string.Empty;
                outputFiles.Add(log);

                log.Content.AppendLine("// <autogenerated>");
                log.Content.AppendLine("//   This file contains the list of files generated by " + Path.GetFileName(Host.TemplateFile) + ".");
                log.Content.AppendLine("//   It is used by T4 Toolbox (http://www.codeplex.com/t4toolbox) to automatically delete");
                log.Content.AppendLine("//   generated files that are no longer necessary. Do not modify this file manually. Manual");
                log.Content.AppendLine("//   changes may cause orphaned files and will be lost next time the code is regenerated.");
                log.Content.AppendLine("// </autogenerated>");
                foreach (OutputFile output in outputFiles)
                {
                    // Don't log the generated files that will be preserved
                    if (!output.PreserveExistingFile)
                    {
                        log.Content.AppendLine(GetRelativePath(Host.TemplateFile, output.File));
                    }
                }
            }
        }
        /// <summary>
        /// Adds assembly references required by the project item to the target project.
        /// </summary>
        /// <param name="projectItem">
        /// A <see cref="ProjectItem"/> that represents the generated item in the solution.
        /// </param>        
        /// <param name="output">
        /// An <see cref="OutputFile"/> that holds metadata about the <see cref="ProjectItem"/> to be added to the solution.
        /// </param>
        private static void AddProjectItemReferences(ProjectItem projectItem, OutputFile output)
        {
            if (output.References.Count > 0)
            {
                VSProject project = projectItem.ContainingProject.Object as VSProject;
                if (project == null)
                {
                    throw new TransformationException(
                        string.Format(
                            CultureInfo.CurrentCulture,
                            "Project {0} does not support assembly references required by {1}",
                            projectItem.ContainingProject.Name,
                            projectItem.Name));
                }

                foreach (string reference in output.References)
                {
                    project.References.Add(reference);
                }
            }
        }