using System.Globalization; using System.Text.RegularExpressions; namespace AIStudio.Assistants.ModelComparison; /// /// Keeps the names of presets and of comparison series unique, so that the user can tell them apart in their lists. /// /// /// Two names count as the same regardless of case and of spaces around them, as the user reads them. /// public static partial class ModelComparisonNames { private static string TA(string fallbackEN) => Tools.PluginSystem.I18N.I.TA(fallbackEN, typeof(ModelComparisonNames).Namespace, nameof(ModelComparisonNames)); /// /// How long the name of a comparison series may be, so that it fits the list of the series. /// public const int MAX_SERIES_NAME_LENGTH = 60; /// /// Checks the name of a comparison series, for a new series as well as for renaming one: it is needed, it fits, /// and no other series has it. /// /// The name. /// The names of the other series, without the one being renamed. /// Why the name does not work, or null when it does. public static string? SeriesNameIssue(string? name, IEnumerable takenNames) { if (string.IsNullOrWhiteSpace(name)) return TA("Please give the series a name, so that you find it again."); if (name.Trim().Length > MAX_SERIES_NAME_LENGTH) return string.Format(CultureInfo.InvariantCulture, TA("The name of your series may have at most {0} characters."), MAX_SERIES_NAME_LENGTH); if (IsTaken(name, takenNames)) return TA("A series with this name already exists. Please choose a different name."); return null; } /// /// Tells whether a name is taken already. /// /// The name. /// The names taken by the others, without the one the name belongs to. /// True when another one has the name already. public static bool IsTaken(string name, IEnumerable takenNames) { var trimmedName = name.Trim(); return takenNames.Any(takenName => string.Equals(takenName.Trim(), trimmedName, StringComparison.CurrentCultureIgnoreCase)); } /// /// Gets a name which is not taken yet, based on a given one: the name itself when it is free, otherwise the name /// with the next free number, e.g., "Weekly reports (2)". /// /// The name, e.g., of the series whose setup a new one takes over. /// The names taken already. /// How long the name may be; the base name is shortened for the number when needed. /// The free name. public static string FreeNameFrom(string name, IReadOnlyCollection takenNames, int maxLength) { var trimmedName = name.Trim(); if (!IsTaken(trimmedName, takenNames) && trimmedName.Length <= maxLength) return trimmedName; // A name with a number already counts on from its base, so that a copy of "Reports (2)" becomes "Reports (3)": var match = NumberedName().Match(trimmedName); var baseName = match.Success ? match.Groups["base"].Value : trimmedName; for (var number = 2;; number++) { var suffix = string.Format(CultureInfo.InvariantCulture, " ({0})", number); var shortenedBase = baseName.Length + suffix.Length > maxLength ? baseName[..Math.Max(0, maxLength - suffix.Length)].TrimEnd() : baseName; var candidate = shortenedBase + suffix; if (!IsTaken(candidate, takenNames)) return candidate; } } [GeneratedRegex(@"^(?.+?) \((?\d+)\)$")] private static partial Regex NumberedName(); }