| File: Alias\AliasRegistry.cs | Web Access |
| Project: src\sdk\src\Cli\Microsoft.TemplateEngine.Cli\Microsoft.TemplateEngine.Cli.csproj (Microsoft.TemplateEngine.Cli) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.TemplateEngine.Abstractions; using Microsoft.TemplateEngine.Utils; using System.Text.Json; using System.Text.Json.Nodes; namespace Microsoft.TemplateEngine.Cli.Alias { internal class AliasRegistry { private readonly IEngineEnvironmentSettings _environmentSettings; private readonly string _aliasesFilePath; private AliasModel? _aliases; internal AliasRegistry(IEngineEnvironmentSettings environmentSettings) { _environmentSettings = environmentSettings; _aliasesFilePath = Path.Combine(_environmentSettings.Paths.HostVersionSettingsDir, "aliases.json"); } internal IReadOnlyDictionary<string, IReadOnlyList<string>> AllAliases { get { EnsureLoaded(); //ensure loaded sets aliases return new Dictionary<string, IReadOnlyList<string>>(_aliases!.CommandAliases, StringComparer.OrdinalIgnoreCase); } } internal AliasManipulationResult TryCreateOrRemoveAlias(string aliasName, IReadOnlyList<string> aliasTokens) { EnsureLoaded(); if (aliasName == null) { // the input was malformed. Alias flag without alias name return new AliasManipulationResult(AliasManipulationStatus.InvalidInput); } else if (aliasTokens.Count == 0) { // the command was just "--alias <alias name>" // remove the alias //ensure loaded sets aliases if (_aliases!.TryRemoveCommandAlias(aliasName, out IReadOnlyList<string>? removedAliasTokens)) { Save(); return new AliasManipulationResult(AliasManipulationStatus.Removed, aliasName, removedAliasTokens); } else { return new AliasManipulationResult(AliasManipulationStatus.RemoveNonExistentFailed, aliasName, null); } } //ensure loaded sets aliases Dictionary<string, IReadOnlyList<string>> aliasesWithCandidate = new(_aliases!.CommandAliases); aliasesWithCandidate[aliasName] = aliasTokens; if (!TryExpandCommandAliases(aliasesWithCandidate, aliasTokens, out IReadOnlyList<string>? expandedInputTokens)) { return new AliasManipulationResult(AliasManipulationStatus.WouldCreateCycle, aliasName, aliasTokens); } _aliases.AddCommandAlias(aliasName, aliasTokens); Save(); return new AliasManipulationResult(AliasManipulationStatus.Created, aliasName, aliasTokens); } // Attempts to expand aliases on the input string, using the aliases in _aliases internal bool TryExpandCommandAliases(IReadOnlyList<string> inputTokens, out IReadOnlyList<string> expandedInputTokens) { EnsureLoaded(); if (inputTokens.Count == 0) { expandedInputTokens = new List<string>(inputTokens); return true; } //ensure loaded sets aliases if (TryExpandCommandAliases(_aliases!.CommandAliases, inputTokens, out expandedInputTokens!)) { return true; } // TryExpandCommandAliases() returned false because was an expansion error expandedInputTokens = new List<string>(); return false; } private static bool TryExpandCommandAliases(IReadOnlyDictionary<string, IReadOnlyList<string>> aliases, IReadOnlyList<string> inputTokens, out IReadOnlyList<string>? expandedTokens) { bool expansionOccurred = false; HashSet<string> seenAliases = new(); expandedTokens = new List<string>(inputTokens); do { string candidateAliasName = expandedTokens[0]; if (aliases.TryGetValue(candidateAliasName, out IReadOnlyList<string>? aliasExpansion)) { if (!seenAliases.Add(candidateAliasName)) { // a cycle has occurred.... not allowed. expandedTokens = null; return false; } // The expansion is the combination of the aliasExpansion (expands the 0th token of the previously expandedTokens) // and the rest of the previously expandedTokens expandedTokens = new CombinedList<string>(aliasExpansion, expandedTokens.ToList().GetRange(1, expandedTokens.Count - 1)); expansionOccurred = true; } else { expansionOccurred = false; } } while (expansionOccurred); return true; } private void EnsureLoaded() { if (_aliases != null) { return; } if (!_environmentSettings.Host.FileSystem.FileExists(_aliasesFilePath)) { _aliases = new AliasModel(); return; } JsonObject parsed = _environmentSettings.Host.FileSystem.ReadObject(_aliasesFilePath); IReadOnlyDictionary<string, IReadOnlyList<string>> commandAliases = ToStringListDictionary(parsed, StringComparer.OrdinalIgnoreCase, "CommandAliases"); _aliases = new AliasModel(commandAliases); } private void Save() { if (_aliases is AliasModel { CommandAliases: { Count: > 0 } }) { JsonObject root = new(); JsonObject commandAliases = new(); foreach (var kvp in _aliases.CommandAliases) { JsonArray arr = new(); foreach (string item in kvp.Value) { arr.Add((JsonNode)JsonValue.Create(item)!); } commandAliases[kvp.Key] = arr; } root["CommandAliases"] = commandAliases; _environmentSettings.Host.FileSystem.WriteObject(_aliasesFilePath, root); } else { _environmentSettings.Host.FileSystem.FileDelete(_aliasesFilePath); } } // reads a dictionary whose values can either be string literals, or arrays of strings. private IReadOnlyDictionary<string, IReadOnlyList<string>> ToStringListDictionary(JsonObject token, StringComparer? comparer = null, string? propertyName = null) { Dictionary<string, IReadOnlyList<string>> result = new(comparer ?? StringComparer.Ordinal); if (propertyName == null) { return result; } // Case-insensitive property lookup for compatibility with Newtonsoft.Json behavior JsonNode? element = null; foreach (var prop in token) { if (string.Equals(prop.Key, propertyName, StringComparison.OrdinalIgnoreCase)) { element = prop.Value; break; } } if (element is not JsonObject jObj) { return result; } foreach (KeyValuePair<string, JsonNode?> property in jObj) { if (property.Value == null) { continue; } else if (property.Value.GetValueKind() == JsonValueKind.String) { result[property.Key] = new List<string>() { property.Value.GetValue<string>() }; } else if (property.Value is JsonArray arr) { List<string> values = new(); foreach (JsonNode? item in arr) { if (item != null && item.GetValueKind() == JsonValueKind.String) { values.Add(item.GetValue<string>()); } } result[property.Key] = values; } } return result; } } }