using System; using System.Collections.Generic; using System.Linq; using FluentResults; namespace MfGames.ToolBuilder.Extensions { public static class StringCliExtensions { /// /// Parses a string and converts it into an enumeration while ignoring /// case and allow only prefix values. If the value cannot be /// determined, it will report it and throw an exception. /// /// The enumeration to parse. /// The input value to parse. /// The logger to report issues. /// The name of the variable or label. /// public static TEnum GetEnumFuzzy( this string input, string label) where TEnum : struct { if (input.TryParseEnumFuzzy(out TEnum value)) { return value; } throw new InvalidOperationException( string.Format( "Cannot parse unknown value {0} for {1}: {2}", input, label, Enum.GetNames(typeof(TEnum)))); } /// /// Searches for possible list of values for a given one, handling /// case-insensitive searching and substring searches. /// /// The value to search for. /// A collection of all possible values. /// A tuple with the selected value or an error message. public static Result GetFuzzy( this string value, ICollection possible) { // Look for a direct match. var found = possible .Where( x => string.Equals( x, value, StringComparison.InvariantCultureIgnoreCase)) .ToList(); if (found.Count == 1) { return Result.Ok(found[0]); } // Look for substrings in the values. found = possible .Where( x => x.Contains( value, StringComparison.InvariantCultureIgnoreCase)) .ToList(); return found.Count switch { 1 => Result.Ok(found[0]), 0 => Result.Fail( string.Format( "Cannot find \"{0}\" from possible options: {1}.", value, string.Join(", ", possible))), _ => Result.Fail( string.Format( "Found multiple matches for \"{0}\" from possible options: {1}.", value, string.Join(", ", found))), }; } /// /// Parses a string and converts it into an enumeration while ignoring /// case and allowing only prefix /// values. /// /// The enumeration to parse. /// The input value to parse. /// /// The resulting value if parsed or default if not. /// /// The resulting enum. public static bool TryParseEnumFuzzy( this string input, out TEnum value) where TEnum : struct { // If we have a blank, then we don't know what to do. if (string.IsNullOrWhiteSpace(input)) { value = default; return false; } // See if we have an exact match first. if (Enum.TryParse(input, true, out value)) { return true; } // Attempt a fuzzy search. var possible = Enum.GetNames(typeof(TEnum)) .Select(e => e.ToLower()) .Where(e => e.StartsWith(input.ToLower())) .ToList(); if (possible.Count == 1) { value = (TEnum)Enum.Parse(typeof(TEnum), possible[0], true); return true; } // Fall back to not allowing the value. value = default; return false; } } }