| File: PackageSourceMapping\SearchTree.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Configuration\NuGet.Configuration.csproj (NuGet.Configuration) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using System.Text; namespace NuGet.Configuration { internal class SearchTree { private readonly SearchNode _root; internal SearchTree(PackageSourceMapping configuration) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } _root = new SearchNode(); foreach (KeyValuePair<string, IReadOnlyList<string>> patternsPerSource in configuration.Patterns) { foreach (string namespaceId in patternsPerSource.Value) { Add(patternsPerSource.Key, namespaceId); } } } private void Add(string packageSourceKey, string packagePattern) { SearchNode currentNode = _root; if (string.IsNullOrWhiteSpace(packagePattern)) { throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Empty_Or_WhiteSpaceOnly, nameof(packagePattern)); } // To prevent from unwanted behaviour. if (packagePattern.Length > PackageSourceMapping.PackageIdMaxLength) { throw new ArgumentOutOfRangeException(nameof(packagePattern)); } packageSourceKey = packageSourceKey.Trim(); packagePattern = packagePattern.ToLower(CultureInfo.CurrentCulture).Trim(); for (int i = 0; i < packagePattern.Length; i++) { char c = packagePattern[i]; if (c == '*') { // break here since seeing * means end of expression. currentNode.IsGlobbing = true; break; } if (!currentNode.Children.ContainsKey(c)) { currentNode.Children[c] = new SearchNode(); } currentNode = currentNode.Children[c]; } if (currentNode.PackageSources == null) { currentNode.PackageSources = new List<string>(); } currentNode.PackageSources.Add(packageSourceKey); } /// <summary> /// Finds the pattern in <see cref="PackageSourceMapping.Patterns"/> which satisfies the <paramref name="term"/>. /// Patterns are all lowercase and include glob (`*`) characters. /// </summary> /// <param name="term">Term to search for in <see cref="PackageSourceMapping.Patterns"/>.</param> /// <returns>Found <see cref="PackageSourceMapping.Patterns"/> which satisfies the <paramref name="term"/>, or `null`.</returns> public string? SearchForPattern(string term) { return SearchPatternByTerm(term); } /// <summary> /// Get package source names with matching prefix "term" from package source mapping section. /// </summary> /// <param name="term">Search term. Cannot be null, empty, or whitespace only. </param> /// <returns>Package source names with matching prefix "term" from package source mapping section.</returns> /// <exception cref="ArgumentException"> if <paramref name="term"/> is null, empty, or whitespace only.</exception> public IReadOnlyList<string> GetConfiguredPackageSources(string term) { SearchNode? searchNodeResult = SearchNodeByTerm(term); if (searchNodeResult is null || searchNodeResult.PackageSources is null) { return Array.Empty<string>(); } return searchNodeResult.PackageSources; } private SearchNode? SearchNodeByTerm(string term) { if (string.IsNullOrWhiteSpace(term)) { throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Empty_Or_WhiteSpaceOnly, nameof(term)); } term = term.ToLower(CultureInfo.CurrentCulture).Trim(); SearchNode currentNode = _root; SearchNode? longestMatchingPrefixNode = null; if (currentNode.IsGlobbing) { longestMatchingPrefixNode = currentNode; } int i = 0; for (; i < term.Length; i++) { char c = term[i]; if (!currentNode.Children.TryGetValue(c, out SearchNode? child)) { break; } currentNode = child; if (currentNode.IsGlobbing) { longestMatchingPrefixNode = currentNode; } } if (i == term.Length && currentNode.PackageSources != null) { return currentNode; } return longestMatchingPrefixNode; } private string? SearchPatternByTerm(string term) { if (string.IsNullOrWhiteSpace(term)) { throw new ArgumentException(Resources.Argument_Cannot_Be_Null_Empty_Or_WhiteSpaceOnly, nameof(term)); } term = term.ToLower(CultureInfo.CurrentCulture).Trim(); StringBuilder sb = new StringBuilder(); SearchNode currentNode = _root; if (currentNode.IsGlobbing) { return "*"; } int i = 0; for (; i < term.Length; i++) { char c = term[i]; if (!currentNode.Children.TryGetValue(c, out SearchNode? child)) { if (!currentNode.IsGlobbing) { return null; } break; } currentNode = child; sb.Append(c); } if (currentNode.IsGlobbing) { sb.Append('*'); } return sb.ToString(); } } }