File: FileRenameGenerator.cs
Web Access
Project: src\sdk\src\TemplateEngine\Microsoft.TemplateEngine.Orchestrator.RunnableProjects\Microsoft.TemplateEngine.Orchestrator.RunnableProjects.csproj (Microsoft.TemplateEngine.Orchestrator.RunnableProjects)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Mount;
using Microsoft.TemplateEngine.Core;
using Microsoft.TemplateEngine.Core.Contracts;
using Microsoft.TemplateEngine.Core.Operations;
using Microsoft.TemplateEngine.Core.Util;
using Microsoft.TemplateEngine.Utils;

namespace Microsoft.TemplateEngine.Orchestrator.RunnableProjects
{
    internal class FileRenameGenerator
    {
        private readonly IProcessor _symbolRenameProcessor;

        internal FileRenameGenerator(
            IEngineEnvironmentSettings environmentSettings,
            string? sourceName,
            object? resolvedNameParamValue,
            IVariableCollection variables,
            IReadOnlyList<IReplacementTokens> symbolBasedFileRenames)
        {
            _symbolRenameProcessor = SetupSymbolBasedRenameProcessor(environmentSettings, sourceName, resolvedNameParamValue, variables, symbolBasedFileRenames);
        }

        /// <summary>
        /// Creates the complete file rename mapping for the template invocation being processed.
        /// Renames are based on:
        ///  - parameters with a FileRename specified
        ///  - the source and target names.
        /// Any input fileRenames will be applied before the parameter symbol renames.
        /// </summary>
        internal static IReadOnlyDictionary<string, string> AugmentFileRenames(
            IEngineEnvironmentSettings environmentSettings,
            string? sourceName,
            IDirectory templateSourceDir,
            string sourceDirectory,
            ref string targetDirectory,
            object? resolvedNameParamValue,
            IVariableCollection variables,
            Dictionary<string, string> fileRenames,
            IReadOnlyList<IReplacementTokens>? symbolBasedFileRenames = null)
        {
            Dictionary<string, string> allRenames = new(StringComparer.Ordinal);

            IProcessor sourceRenameProcessor = SetupSourceBasedRenameProcessor(environmentSettings, fileRenames);
            IProcessor symbolRenameProcessor = SetupSymbolBasedRenameProcessor(environmentSettings, sourceName, resolvedNameParamValue, variables, symbolBasedFileRenames);

            //replace sourceName in target directory, if needed
            if (sourceName is not null && resolvedNameParamValue is not null)
            {
                string targetName = resolvedNameParamValue.ToString().Trim();
                targetDirectory = targetDirectory.Replace(sourceName, targetName);
            }

            IDirectory? sourceBaseDirectoryInfo = templateSourceDir.DirectoryInfo(sourceDirectory.TrimEnd('/'));

            if (sourceBaseDirectoryInfo is null)
            {
                return allRenames;
            }

            foreach (IFileSystemInfo fileSystemEntry in sourceBaseDirectoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
            {
                string sourceTemplateRelativePath = fileSystemEntry.PathRelativeTo(sourceBaseDirectoryInfo);

                // first apply the sources renames, then apply the symbol renames to that result.
                string renameFromSourcesValue = ApplyRenameProcessor(sourceRenameProcessor, sourceTemplateRelativePath);
                string renameFinalTargetValue = ApplyRenameProcessor(symbolRenameProcessor, renameFromSourcesValue);

                if (!string.Equals(sourceTemplateRelativePath, renameFinalTargetValue, StringComparison.Ordinal))
                {
                    allRenames[sourceTemplateRelativePath] = renameFinalTargetValue;
                }
            }

            return allRenames;
        }

        /// <summary>
        /// Applies configured renames to <paramref name="stringToReplace"/>.
        /// </summary>
        internal string ApplyRenameToString(string stringToReplace) => ApplyRenameProcessor(_symbolRenameProcessor, stringToReplace);

        private static string ApplyRenameProcessor(IProcessor processor, string sourceFilename)
        {
            using MemoryStream source = new(Encoding.UTF8.GetBytes(sourceFilename));
            using MemoryStream target = new();

            _ = processor.Run(source, target);
            return Encoding.UTF8.GetString(target.ToArray());
        }

        /// <summary>
        /// Creates and returns the processor used to create the file rename mapping for source based file renames.
        /// </summary>
        private static IProcessor SetupSourceBasedRenameProcessor(IEngineEnvironmentSettings environmentSettings, IReadOnlyDictionary<string, string> substringReplacementMap)
        {
            List<IOperationProvider> operations = new();
            foreach (KeyValuePair<string, string> replacement in substringReplacementMap)
            {
                IOperationProvider replacementOperation = new Replacement(replacement.Key.TokenConfig(), replacement.Value, null, true);
                operations.Add(replacementOperation);
            }
            return SetupProcessor(environmentSettings, operations);
        }

        /// <summary>
        /// Creates and returns the processor used to create the file rename mapping based on the symbols with fileRename defined.
        /// Also sets up rename for the target directory.
        /// </summary>
        private static IProcessor SetupSymbolBasedRenameProcessor(
            IEngineEnvironmentSettings environmentSettings,
            string? sourceName,
            object? resolvedNameParamValue,
            IVariableCollection variables,
            IReadOnlyList<IReplacementTokens>? symbolBasedFileRenames)
        {
            List<IOperationProvider> operations = new();
            if (resolvedNameParamValue != null && sourceName != null)
            {
                SetupRenameForTargetDirectory(sourceName, resolvedNameParamValue, operations);
            }

            if (symbolBasedFileRenames != null)
            {
                foreach (IReplacementTokens fileRenameToken in symbolBasedFileRenames)
                {
                    if (variables.TryGetValue(fileRenameToken.VariableName, out object? newValueObject))
                    {
                        string newValue = newValueObject?.ToString() ?? string.Empty;
                        operations.Add(new Replacement(fileRenameToken.OriginalValue, newValue, null, true));
                    }
                }
            }
            return SetupProcessor(environmentSettings, operations);
        }

        /// <summary>
        /// Sets up a rename based on the "name" parameter.
        /// </summary>
        /// <param name="sourceName"></param>
        /// <param name="resolvedNameParamValue"></param>
        /// <param name="operations"></param>
        private static void SetupRenameForTargetDirectory(
            string sourceName,
            object resolvedNameParamValue,
            List<IOperationProvider> operations)
        {
            string targetName = ((string)resolvedNameParamValue).Trim();
            operations.Add(new Replacement(sourceName.TokenConfig(), targetName, null, true));
        }

        private static IProcessor SetupProcessor(IEngineEnvironmentSettings environmentSettings, IReadOnlyList<IOperationProvider> operations)
        {
            IVariableCollection variables = new VariableCollection();
            EngineConfig config = new(environmentSettings.Host.Logger, variables);
            IProcessor processor = Processor.Create(config, operations);
            return processor;
        }
    }
}