Exemplo n.º 1
0
        /// <summary>
        /// Creates new or appends to existing output 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 <paramref name="transformation"/>.
        /// </param>
        /// <param name="transformation">
        /// <see cref="TextTransformation"/> object generated by T4 based on top-level .tt file.
        /// </param>
        /// <remarks>
        /// Multiple outputs can be combined in a single output file during code 
        /// generation. This allows user to customize a composite code generator 
        /// to generate output with required granularity without having to modify
        /// the generator itself.
        /// </remarks>
        public void Append(OutputInfo output, string content, ITextTemplatingEngineHost host, TextTransformation transformation)
        {
            if (output == null)
            {
                throw new ArgumentNullException("output");
            }

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

            string previousDirectory = Environment.CurrentDirectory;
            Environment.CurrentDirectory = Path.GetDirectoryName(host.TemplateFile);
            try
            {
                Validate(output);
                if (string.IsNullOrEmpty(output.File))
                {
                    this.AppendToStandardOutput(output, content, host, transformation);
                }
                else
                {
                    this.AppendToOutputFile(output, content, host);
                }
            }
            finally
            {
                Environment.CurrentDirectory = previousDirectory;
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Determines absolute path to the output <see cref="OutputInfo.File"/>.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that describes content generated by a template.
        /// </param>
        /// <param name="host">
        /// An <see cref="ITextTemplatingEngineHost"/> object hosting the <see cref="TextTransformation"/>.
        /// </param>
        /// <returns>
        /// If <see cref="OutputInfo.File"/> contains absolute path, returns its value. Otherwise,
        /// if <see cref="OutputInfo.Project"/> is specified, resolves relative <see cref="OutputInfo.File"/>
        /// path based on the location of the project, otherwise resolves the path based on the location
        /// of the <see cref="ITextTemplatingEngineHost.TemplateFile"/>.
        /// </returns>
        private static string GetFullFilePath(OutputInfo output, ITextTemplatingEngineHost host)
        {
            // Does file contain absolute path already?
            if (Path.IsPathRooted(output.File))
            {
                return(output.File);
            }

            // Resolve relative path
            string baseFile      = string.IsNullOrEmpty(output.Project) ? host.TemplateFile : output.Project;
            string baseDirectory = Path.GetDirectoryName(baseFile);
            string fullPath      = Path.Combine(baseDirectory, output.File);

            return(Path.GetFullPath(fullPath));
        }
        /// <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);
        }
Exemplo n.º 4
0
        /// <summary>
        /// Validates that <paramref name="output"/> properties are consistent
        /// with properties of the <paramref name="previousOutput"/>.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that represents the generated output content.
        /// </param>
        /// <param name="previousOutput">
        /// An <see cref="OutputInfo"/> object that represents previously generated output content.
        /// </param>
        private static void Validate(OutputInfo output, OutputInfo previousOutput)
        {
            if (!OutputInfo.SamePath(previousOutput.Project, GetFullProjectPath(output)))
            {
                throw new TransformationException("Project name doesn't match previously generated value");
            }

            if (previousOutput.Encoding != output.Encoding)
            {
                throw new TransformationException("Encoding doesn't match previously generated value");
            }

            if (previousOutput.BuildAction != output.BuildAction)
            {
                throw new TransformationException("Build action doesn't match previously generated value");
            }

            if (previousOutput.CopyToOutputDirectory != output.CopyToOutputDirectory)
            {
                throw new TransformationException("CopyToOutputDirectory doesn't match previously generated value");
            }

            if (previousOutput.CustomTool != output.CustomTool)
            {
                throw new TransformationException("CustomTool doesn't match previously generated value");
            }

            if (previousOutput.CustomToolNamespace != output.CustomToolNamespace)
            {
                throw new TransformationException("CustomToolNamespace doesn't match previously generated value");
            }

            foreach (KeyValuePair <string, string> buildProperty in previousOutput.BuildProperties)
            {
                string previousValue;
                bool   propertyExists = output.BuildProperties.TryGetValue(buildProperty.Key, out previousValue);
                if (propertyExists && buildProperty.Value != previousValue)
                {
                    throw new TransformationException("Build property doesn't match previously generated value");
                }
            }

            if (previousOutput.PreserveExistingFile != output.PreserveExistingFile)
            {
                throw new TransformationException("PreserveExistingFile doesn't match previously generated value");
            }
        }
        /// <summary>
        /// This method is a part of T4 Toolbox infrastructure. Don't call it in your code.
        /// </summary>
        /// <param name="content">
        /// Generated content.
        /// </param>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that specifies how the content must be saved.
        /// </param>
        /// <param name="errors">
        /// A collection of <see cref="CompilerError"/> objects that represent errors and warnings
        /// that occurred while generating this content.
        /// </param>
        internal static void Render(string content, OutputInfo output, CompilerErrorCollection errors)
        {
            //if (output.PreserveExistingFile == true)
            //{
            //    output.PreserveExistingFile = false;
            //    CompilerError error = new CompilerError();
            //    error.IsWarning = true;
            //    // PreserveExistingFile depends on where it is put (ie before Render) - not atomic. Too risky.
            //    // this.PreserveExistingFile = true in a Template would be safer.
            //    // But I prefer explict method, as it is quite clear.
            //    error.ErrorText = "PreserveExistingFile is deprecated. Always use before Render() in Generator, or in template itself, or use RenderToFileIfNotExists(filename) instead.";
            //    error.FileName = Host.TemplateFile;
            //    errors.Add(error);
            //}

            TransformationContext.ReportErrors(errors);
            TransformationContext.outputManager.Append(output, content, Host, Transformation);
        }
        /// <summary>
        /// Performs validation tasks that require accessing Visual Studio automation model.
        /// </summary>
        /// <param name="outputFiles">
        /// <see cref="OutputFile"/>s that need to be added to the solution.
        /// </param>
        /// <param name="projects">
        /// All <see cref="Project"/>s in the current solution.
        /// </param>
        /// <remarks>
        /// Most of the output validation is done on the fly, by the <see cref="OutputManager.Append"/>
        /// method. This method performs the remaining validations that access Visual
        /// Studio automation model and cannot cross <see cref="bAppDomain"/> boundries.
        /// </remarks>
        private static void Validate(IEnumerable <OutputFile> outputFiles, IEnumerable <Project> projects)
        {
            foreach (OutputFile outputFile in outputFiles)
            {
                if (string.IsNullOrEmpty(outputFile.Project))
                {
                    continue;
                }

                // Make sure that project is included in the solution
                bool projectInSolution = projects.Any(p => OutputInfo.SamePath(GetPath(p), outputFile.Project));
                if (!projectInSolution)
                {
                    throw new TransformationException(
                              string.Format(CultureInfo.CurrentCulture, "Target project {0} does not belong to the solution", outputFile.Project));
                }
            }
        }
Exemplo n.º 7
0
        /// <summary>
        /// Appends generated <paramref name="content"/> to standard output of the <paramref name="transformation"/>.
        /// </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 <paramref name="transformation"/>.
        /// </param>
        /// <param name="transformation">
        /// <see cref="TextTransformation"/> object generated by T4 based on top-level .tt file.
        /// </param>
        private void AppendToStandardOutput(OutputInfo output, string content, ITextTemplatingEngineHost host, TextTransformation transformation)
        {
            // If some content was already written to the standard output
            if (this.standardOutput != null)
            {
                Validate(output, this.standardOutput);
            }

            transformation.Write(content);
            host.SetOutputEncoding(output.Encoding, false);

            if (this.standardOutput == null)
            {
                this.standardOutput          = new OutputInfo();
                this.standardOutput.Project  = GetFullProjectPath(output);
                this.standardOutput.Encoding = output.Encoding;
            }

            this.standardOutput.AppendReferences(output.References);
        }
        /// <summary>
        /// This method is a part of T4 Toolbox infrastructure. Don't call it in your code.
        /// </summary>
        /// <param name="content">
        /// Generated content.
        /// </param>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that specifies how the content must be saved.
        /// </param>
        /// <param name="errors">
        /// A collection of <see cref="CompilerError"/> objects that represent errors and warnings
        /// that occurred while generating this content.
        /// </param>
        internal static void Render(string content, OutputInfo output, CompilerErrorCollection errors)
        {
            //if (output.PreserveExistingFile == true)
            //{
            //    output.PreserveExistingFile = false;
            //    CompilerError error = new CompilerError();
            //    error.IsWarning = true;
            //    // PreserveExistingFile depends on where it is put (ie before Render) - not atomic. Too risky.
            //    // this.PreserveExistingFile = true in a Template would be safer.
            //    // But I prefer explict method, as it is quite clear.
            //    error.ErrorText = "PreserveExistingFile is deprecated. Always use before Render() in Generator, or in template itself, or use RenderToFileIfNotExists(filename) instead.";
            //    error.FileName = Host.TemplateFile;
            //    errors.Add(error);
            //}

            TransformationContext.ReportErrors(errors);
            TransformationContext.outputManager.Append(output, content, Host, Transformation);
        }
Exemplo n.º 9
0
        /// <summary>
        /// Appends generated <paramref name="content"/> to standard output of the <paramref name="transformation"/>.
        /// </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 <paramref name="transformation"/>.
        /// </param>
        /// <param name="transformation">
        /// <see cref="TextTransformation"/> object generated by T4 based on top-level .tt file.
        /// </param>
        private void AppendToStandardOutput(OutputInfo output, string content, ITextTemplatingEngineHost host, TextTransformation transformation)
        {
            // If some content was already written to the standard output
            if (this.standardOutput != null)
            {
                Validate(output, this.standardOutput);
            }

            transformation.Write(content);
            host.SetOutputEncoding(output.Encoding, false);

            if (this.standardOutput == null)
            {
                this.standardOutput = new OutputInfo();
                this.standardOutput.Project = GetFullProjectPath(output);
                this.standardOutput.Encoding = output.Encoding;
            }

            this.standardOutput.AppendReferences(output.References);
        }
Exemplo n.º 10
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);
        }
Exemplo n.º 11
0
        /// <summary>
        /// Validates that <paramref name="output"/> properties are consistent 
        /// with properties of the <paramref name="previousOutput"/>.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that represents the generated output content.
        /// </param>
        /// <param name="previousOutput">
        /// An <see cref="OutputInfo"/> object that represents previously generated output content.
        /// </param>
        private static void Validate(OutputInfo output, OutputInfo previousOutput)
        {
            if (!OutputInfo.SamePath(previousOutput.Project, GetFullProjectPath(output)))
            {
                throw new TransformationException("Project name doesn't match previously generated value");
            }

            if (previousOutput.Encoding != output.Encoding)
            {
                throw new TransformationException("Encoding doesn't match previously generated value");
            }

            if (previousOutput.BuildAction != output.BuildAction)
            {
                throw new TransformationException("Build action doesn't match previously generated value");
            }

            if (previousOutput.CopyToOutputDirectory != output.CopyToOutputDirectory)
            {
                throw new TransformationException("CopyToOutputDirectory doesn't match previously generated value");
            }

            if (previousOutput.CustomTool != output.CustomTool)
            {
                throw new TransformationException("CustomTool doesn't match previously generated value");
            }

            if (previousOutput.CustomToolNamespace != output.CustomToolNamespace)
            {
                throw new TransformationException("CustomToolNamespace doesn't match previously generated value");
            }

            foreach (KeyValuePair<string, string> buildProperty in previousOutput.BuildProperties)
            {
                string previousValue;
                bool propertyExists = output.BuildProperties.TryGetValue(buildProperty.Key, out previousValue);
                if (propertyExists && buildProperty.Value != previousValue)
                {
                    throw new TransformationException("Build property doesn't match previously generated value");
                }
            }

            if (previousOutput.PreserveExistingFile != output.PreserveExistingFile)
            {
                throw new TransformationException("PreserveExistingFile doesn't match previously generated value");
            }
        }
Exemplo n.º 12
0
        /// <summary>
        /// Determines absolute path to the <see cref="OutputInfo.Project"/> file.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that describes content generated by a template.
        /// </param>
        /// <returns>
        /// An absolute path to the <see cref="OutputInfo.Project"/>, if specified. Otherwise, returns 
        /// an empty string.
        /// </returns>
        private static string GetFullProjectPath(OutputInfo output)
        {
            if (string.IsNullOrEmpty(output.Project))
            {
                return string.Empty;
            }

            return Path.GetFullPath(output.Project);
        }
Exemplo n.º 13
0
        /// <summary>
        /// Determines absolute path to the output <see cref="OutputInfo.File"/>.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that describes content generated by a template.
        /// </param>
        /// <param name="host">
        /// An <see cref="ITextTemplatingEngineHost"/> object hosting the <see cref="TextTransformation"/>.
        /// </param>
        /// <returns>
        /// If <see cref="OutputInfo.File"/> contains absolute path, returns its value. Otherwise,
        /// if <see cref="OutputInfo.Project"/> is specified, resolves relative <see cref="OutputInfo.File"/> 
        /// path based on the location of the project, otherwise resolves the path based on the location
        /// of the <see cref="ITextTemplatingEngineHost.TemplateFile"/>.
        /// </returns>
        private static string GetFullFilePath(OutputInfo output, ITextTemplatingEngineHost host)
        {
            // Does file contain absolute path already?
            if (Path.IsPathRooted(output.File))
            {
                return output.File;
            }

            // Resolve relative path
            string baseFile = string.IsNullOrEmpty(output.Project) ? host.TemplateFile : output.Project;
            string baseDirectory = Path.GetDirectoryName(baseFile);
            string fullPath = Path.Combine(baseDirectory, output.File);
            return Path.GetFullPath(fullPath);
        }
Exemplo n.º 14
0
 /// <summary>
 /// This method is a part of T4 Toolbox infrastructure. Don't call it in your code.
 /// </summary>
 /// <param name="content">
 /// Generated content.
 /// </param>
 /// <param name="output">
 /// An <see cref="OutputInfo"/> object that specifies how the content must be saved.
 /// </param>
 /// <param name="errors">
 /// A collection of <see cref="CompilerError"/> objects that represent errors and warnings
 /// that occurred while generating this content.
 /// </param>
 internal static void Render(string content, OutputInfo output, CompilerErrorCollection errors)
 {
     TransformationContext.ReportErrors(errors);
     TransformationContext.outputManager.Append(output, content, Host, Transformation);
 }
Exemplo n.º 15
0
        /// <summary>
        /// Deletes output files that were not generated by the current session.
        /// </summary>
        /// <param name="outputFiles">
        /// A read-only collection of <see cref="OutputFile"/> objects.
        /// </param>
        /// <param name="solution">
        /// Current Visual Studio <see cref="Solution"/>.
        /// </param>
        /// <param name="template">
        /// A <see cref="ProjectItem"/> that represents T4 template being transformed.
        /// </param>
        private static void DeleteOldOutputs(IEnumerable <OutputFile> outputFiles, Solution solution, ProjectItem template)
        {
            // If previous transformation produced a log of generated ouptut files
            string logFile = GetLogFileName();

            if (File.Exists(logFile))
            {
                // Delete all files recorded in the log that were not regenerated
                foreach (string line in File.ReadAllLines(logFile))
                {
                    string relativePath = line.Trim();

                    // Skip blank lines
                    if (relativePath.Length == 0)
                    {
                        continue;
                    }

                    // Skip comments
                    if (relativePath.StartsWith("//", StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    string absolutePath = Path.GetFullPath(relativePath);

                    // Skip the file if it was regenerated during current transformation
                    if (outputFiles.Any(output => OutputInfo.SamePath(output.File, absolutePath)))
                    {
                        continue;
                    }

                    // The file wasn't regenerated, delete it from the solution, source control and file storage
                    ProjectItem projectItem = solution.FindProjectItem(absolutePath);
                    if (projectItem != null)
                    {
                        DeleteProjectItem(projectItem);
                    }
                }
            }

            // Also delete all project items nested under template if they weren't regenerated
            string templateFileName = Path.GetFileNameWithoutExtension(Host.TemplateFile);

            foreach (ProjectItem childProjectItem in template.ProjectItems)
            {
                // Skip the file if it has the same name as the template file. This will prevent constant
                // deletion and adding of the main output file to the project, which is slow and may require
                // the user to check the file out unnecessarily.
                if (templateFileName == Path.GetFileNameWithoutExtension(childProjectItem.Name))
                {
                    continue;
                }

                // If the file wan't regenerated, delete it from the the solution, source control and file storage
                if (!outputFiles.Any(o => OutputInfo.SamePath(o.File, childProjectItem.get_FileNames(1))))
                {
                    childProjectItem.Delete();
                }
            }
        }
Exemplo n.º 16
0
        /// <summary>
        /// Validates <paramref name="output"/> properties.
        /// </summary>
        /// <param name="output">
        /// An <see cref="OutputInfo"/> object that represents the generated output content.
        /// </param>
        private static void Validate(OutputInfo output)
        {
            // If content needs to be written to the standard output of the transformation
            if (string.IsNullOrEmpty(output.File))
            {
                if (!string.IsNullOrEmpty(output.Project))
                {
                    throw new TransformationException("Cannot move standard transformation output file");
                }

                if (!string.IsNullOrEmpty(output.BuildAction))
                {
                    throw new TransformationException("Cannot specify BuildAction for standard template output.");
                }

                if (output.CopyToOutputDirectory != CopyToOutputDirectory.DoNotCopy)
                {
                    throw new TransformationException("Cannot specify CopyToOutputDirectory for standard template output.");
                }

                if (output.CustomTool != null)
                {
                    throw new TransformationException("Cannot specify CustomTool for standard template output.");
                }

                if (output.CustomToolNamespace != null)
                {
                    throw new TransformationException("Cannot specify CustomToolNamespace for standard template output.");
                }

                if (output.BuildProperties.Count > 0)
                {
                    throw new TransformationException("Cannot specify BuildProperties for standard template output.");
                }

                if (output.PreserveExistingFile == true)
                {
                    throw new TransformationException("PreserveExistingFile cannot be set for standard template output");
                }
            }

            // If content needs to be written to another project
            if (!string.IsNullOrEmpty(output.Project))
            {
                // Make sure the project file exists
                string projectPath = GetFullProjectPath(output);
                if (!File.Exists(projectPath))
                {
                    throw new TransformationException(
                        string.Format(CultureInfo.CurrentCulture, "Target project {0} does not exist", projectPath));
                }
            }
        }
Exemplo n.º 17
0
 /// <summary>
 /// This method is a part of T4 Toolbox infrastructure. Don't call it in your code.
 /// </summary>
 /// <param name="content">
 /// Generated content.
 /// </param>
 /// <param name="output">
 /// An <see cref="OutputInfo"/> object that specifies how the content must be saved.
 /// </param>
 /// <param name="errors">
 /// A collection of <see cref="CompilerError"/> objects that represent errors and warnings
 /// that occurred while generating this content.
 /// </param>
 internal static void Render(string content, OutputInfo output, CompilerErrorCollection errors)
 {
     TransformationContext.ReportErrors(errors);
     TransformationContext.outputManager.Append(output, content, Host, Transformation);
 }