diff --git a/src/Api/Extensions/StringExtensions.cs b/src/Api/Extensions/StringExtensions.cs new file mode 100644 index 00000000..f7c43667 --- /dev/null +++ b/src/Api/Extensions/StringExtensions.cs @@ -0,0 +1,63 @@ +using System.Text.RegularExpressions; + +namespace Void.Proxy.Api.Extensions; + +public static partial class StringExtensions +{ + private static readonly char[] DefaultDelimiters = [',', ';', '\n']; + + /// + /// Splits a string by multiple delimiters with optional escape character support. + /// + /// The string to split. + /// Custom delimiters to use. If null, uses default delimiters: comma, semicolon, and newline. + /// Optional escape character (e.g., '\') to allow escaped delimiters in the input. + /// Whether to remove empty entries from the result. Defaults to true. + /// An array of strings split by the delimiters. + public static string[] SplitByDelimiters(this string input, char[]? delimiters = null, char? escapeCharacter = null, bool removeEmptyEntries = true) + { + if (string.IsNullOrWhiteSpace(input)) + return []; + + delimiters ??= DefaultDelimiters; + + if (escapeCharacter is not null) + { + return SplitByDelimitersWithEscape(input, delimiters, escapeCharacter.Value, removeEmptyEntries); + } + + var options = removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries : StringSplitOptions.TrimEntries; + return input.Split(delimiters, options); + } + + private static string[] SplitByDelimitersWithEscape(string input, char[] delimiters, char escapeCharacter, bool removeEmptyEntries) + { + var pattern = BuildEscapedDelimiterPattern(delimiters, escapeCharacter); + var regex = new Regex(pattern, RegexOptions.Compiled); + var parts = regex.Split(input); + + var result = parts.Select(part => UnescapeDelimiters(part, delimiters, escapeCharacter).Trim()).ToArray(); + + if (removeEmptyEntries) + result = result.Where(part => !string.IsNullOrWhiteSpace(part)).ToArray(); + + return result; + } + + private static string BuildEscapedDelimiterPattern(char[] delimiters, char escapeCharacter) + { + var escapedChar = Regex.Escape(escapeCharacter.ToString()); + var delimiterPattern = string.Join("|", delimiters.Select(d => Regex.Escape(d.ToString()))); + return $"(? logger, IRunOptions runOptions, IConsoleService console, HttpClient httpClient) : INuGetDependencyResolver, IEventListener +public class NuGetDependencyResolver(ILogger logger, IRunOptions runOptions, IConsoleService console, HttpClient httpClient) : INuGetDependencyResolver, IEventListener { private static readonly Option RepositoryOption = new("--repository", "-r") { @@ -42,7 +42,7 @@ public partial class NuGetDependencyResolver(ILogger lo private readonly NuGet.Common.ILogger _nugetLogger = console.GetOptionValue(EnableNugetLoggingOption) ? new NuGetLogger(logger) : NullLogger.Instance; private readonly HashSet _repositories = []; - private IEnumerable UriRepositories => UnescapedSemicolonRegex().Split(Environment.GetEnvironmentVariable("VOID_NUGET_REPOSITORIES") ?? "").Select(repo => repo.Replace(@"\;", ";")).Concat(_repositories.Concat(console.GetOptionValue(RepositoryOption) ?? [])).Where(uri => !string.IsNullOrWhiteSpace(uri)); + private IEnumerable UriRepositories => (Environment.GetEnvironmentVariable("VOID_NUGET_REPOSITORIES") ?? "").SplitByDelimiters([';'], escapeCharacter: '\\').Concat(_repositories.Concat(console.GetOptionValue(RepositoryOption) ?? [])).Where(uri => !string.IsNullOrWhiteSpace(uri)); private IEnumerable Repositories { get @@ -543,7 +543,4 @@ private async Task ProbeRepositoriesAsync(CancellationToken cancellationToken = foreach (var (url, status) in statuses) logger.LogInformation(" - {Url} [{Status}]", url, status); } - - [GeneratedRegex(@"(?