From 4d66af7a89d00d26e9bff874b0af3ceda7cdedde Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Tue, 12 Nov 2024 05:22:37 +0000 Subject: [PATCH 1/5] Improvement: Added new 'V2' ModuleIssue System --- .../Issues/ModuleIssueV2.cs | 317 +++++++++++ .../Misc/Polyfills.cs | 7 + .../Bannerlord.ModuleManager.Source.csproj | 6 +- .../ModuleUtilities.cs | 492 ++++++++++-------- 4 files changed, 617 insertions(+), 205 deletions(-) create mode 100644 src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs create mode 100644 src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs diff --git a/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs new file mode 100644 index 0000000..b254b28 --- /dev/null +++ b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs @@ -0,0 +1,317 @@ +using LegacyModuleIssue = Bannerlord.ModuleManager.ModuleIssue; + +namespace Bannerlord.ModuleManager.Models.Issues; + +/// +/// Base record type for all module-related issues that can occur during validation. +/// This record serves as the abstract base class for all specific issue variants, +/// providing a common structure and conversion capability to legacy formats. +/// +/// The module in which the issue was detected. This is always required. +public abstract record ModuleIssueV2(ModuleInfoExtended Module) +{ + /// + /// Converts this issue instance to the legacy ModuleIssue format for backwards compatibility + /// + /// A representation of this issue + public abstract LegacyModuleIssue ToLegacy(); +} + +/// +/// Represents an issue where a required module is missing from the module list. +/// This typically occurs when a module declares a dependency on another module +/// that is not present in the current module collection. +/// +/// The module that was found to be missing +/// The version range in which the module should exist +/// +/// This issue occurs when a module is completely missing from the game's module list. +/// +/// Example scenario: +/// Your SubModule.xml references a required module, but that module is not installed: +/// ```xml +/// +/// +/// +/// ``` +/// +public sealed record ModuleMissingIssue( + ModuleInfoExtended Module, + ApplicationVersionRange SourceVersion +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.Missing, $"Missing '{Module.Id}' {Module.Version} in modules list", SourceVersion); +} + +/// +/// Represents an issue where a module is missing one or more of its required dependencies. +/// This can occur when a module explicitly declares a dependency that cannot be found, +/// either because it's not installed or not loaded. +/// +/// The module with missing dependencies +/// The ID of the missing dependency module +/// Optional version range specifying which versions of the dependency are acceptable +public sealed record ModuleMissingDependenciesIssue( + ModuleInfoExtended Module, + string DependencyId, + ApplicationVersionRange? SourceVersion = null +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() + { + var reason = SourceVersion switch + { + null => $"Missing '{DependencyId}'", + _ => $"Missing '{DependencyId}' {SourceVersion}" + }; + return new(Module, DependencyId, ModuleIssueType.MissingDependencies, reason, SourceVersion ?? ApplicationVersionRange.Empty); + } +} + +/// +/// Represents an issue where a module's dependency is itself missing required dependencies. +/// This is a cascading issue where a module's dependency has unresolved dependencies, +/// indicating a deeper problem in the dependency chain. +/// +/// The module whose dependency has missing dependencies +/// The ID of the dependency module that is missing its own dependencies +public sealed record ModuleDependencyMissingDependenciesIssue( + ModuleInfoExtended Module, + string DependencyId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyMissingDependencies, $"'{DependencyId}' is missing its dependencies!", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module's dependency fails validation checks. +/// This indicates that while the dependency exists, it has its own validation +/// issues that need to be resolved. +/// +/// The module with the dependency that failed validation +/// The ID of the dependency module that failed validation +public sealed record ModuleDependencyValidationIssue( + ModuleInfoExtended Module, + string DependencyId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyValidationError, $"'{DependencyId}' has unresolved issues!", ApplicationVersionRange.Empty); +} + +/// +/// Base record type for version mismatch issues between modules and their dependencies. +/// This serves as an abstract base for both specific version and version range issues. +/// +/// The module with the version mismatch +/// The ID of the dependency module with mismatched version +public abstract record ModuleVersionMismatchIssue( + ModuleInfoExtended Module, + string DependencyId +) : ModuleIssueV2(Module); + +/// +/// Base record type for version mismatch issues involving specific versions. +/// Used when comparing against exact version numbers rather than ranges. +/// +/// The module with the version mismatch +/// The ID of the dependency module with mismatched version +/// The specific version being compared against +public abstract record ModuleVersionMismatchSpecificIssue( + ModuleInfoExtended Module, + string DependencyId, + ApplicationVersion Version +) : ModuleVersionMismatchIssue(Module, DependencyId); + +/// +/// Base record type for version mismatch issues involving version ranges. +/// Used when comparing against version ranges rather than specific versions. +/// +/// The module with the version mismatch +/// The ID of the dependency module with mismatched version +/// The version range being compared against +public abstract record ModuleVersionMismatchRangeIssue( + ModuleInfoExtended Module, + string DependencyId, + ApplicationVersionRange VersionRange +) : ModuleVersionMismatchIssue(Module, DependencyId); + +/// +/// Represents an issue where a dependency's version is higher than the maximum allowed specific version. +/// This occurs when a dependency module's version exceeds an exact version requirement. +/// +/// The module with the version constraint +/// The ID of the dependency module that exceeds the version requirement +/// The specific version that should not be exceeded +public sealed record ModuleVersionMismatchLessThanOrEqualSpecificIssue( + ModuleInfoExtended Module, + string DependencyId, + ApplicationVersion Version +) : ModuleVersionMismatchSpecificIssue(Module, DependencyId, Version) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThanOrEqual, $"'{DependencyId}' wrong version <= {Version}", new(Version, Version)); +} + +/// +/// Represents an issue where a dependency's version is less than the minimum required version range. +/// This occurs when a dependency module's version is below the minimum version specified in a range. +/// +/// The module with the version requirement +/// The ID of the dependency module that doesn't meet the minimum version +/// The version range containing the minimum version requirement +public sealed record ModuleVersionMismatchLessThanRangeIssue( + ModuleInfoExtended Module, + string DependencyId, + ApplicationVersionRange VersionRange +) : ModuleVersionMismatchRangeIssue(Module, DependencyId, VersionRange) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThan, $"'{DependencyId}' wrong version < [{VersionRange}]", VersionRange); +} + +/// +/// Represents an issue where a dependency's version exceeds the maximum allowed version range. +/// This occurs when a dependency module's version is above the maximum version specified in a range. +/// +/// The module with the version constraint +/// The ID of the dependency module that exceeds the version limit +/// The version range containing the maximum version requirement +public sealed record ModuleVersionMismatchGreaterThanRangeIssue( + ModuleInfoExtended Module, + string DependencyId, + ApplicationVersionRange VersionRange +) : ModuleVersionMismatchRangeIssue(Module, DependencyId, VersionRange) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchGreaterThan, $"'{DependencyId}' wrong version > [{VersionRange}]", VersionRange); +} + +/// +/// Represents an issue where two modules are incompatible with each other. +/// This occurs when one module explicitly declares it cannot work with another module. +/// +/// The module that has declared an incompatibility +/// The ID of the module that is incompatible with the target +public sealed record ModuleIncompatibleIssue( + ModuleInfoExtended Module, + string IncompatibleModuleId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, IncompatibleModuleId, ModuleIssueType.Incompatible, $"'{IncompatibleModuleId}' is incompatible with this module", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module is both depended upon and marked as incompatible. +/// This indicates a contradictory configuration where a module is required but also +/// marked as incompatible. +/// +/// The module with the conflicting dependency declaration +/// The ID of the module that is both depended upon and marked incompatible +public sealed record ModuleDependencyConflictDependentAndIncompatibleIssue( + ModuleInfoExtended Module, + string ConflictingModuleId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentAndIncompatible, $"Module '{ConflictingModuleId}' is both depended upon and marked as incompatible", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module is declared to load both before and after another module. +/// This indicates a contradictory load order configuration that cannot be satisfied. +/// +/// The module with the conflicting load order declaration +/// The ID of the module that has conflicting load order requirements +public sealed record ModuleDependencyConflictLoadBeforeAndAfterIssue( + ModuleInfoExtended Module, + string ConflictingModuleId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter, $"Module '{ConflictingModuleId}' is both depended upon as LoadBefore and LoadAfter", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where modules have circular dependencies on each other. +/// This occurs when two or more modules form a dependency cycle that cannot be resolved. +/// +/// One of the modules in the circular dependency chain +/// The ID of another module in the circular dependency chain +public sealed record ModuleDependencyConflictCircularIssue( + ModuleInfoExtended Module, + string CircularDependencyId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, CircularDependencyId, ModuleIssueType.DependencyConflictCircular, $"Circular dependencies. '{Module.Id}' and '{CircularDependencyId}' depend on each other", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module that should be loaded before the target module is loaded after it. +/// This indicates a violation of the specified load order requirements. +/// +/// The module with the load order requirement +/// The ID of the module that should be loaded before the target +public sealed record ModuleDependencyNotLoadedBeforeIssue( + ModuleInfoExtended Module, + string DependencyId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyNotLoadedBeforeThis, $"'{DependencyId}' should be loaded before '{Module.Id}'", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module that should be loaded after the target module is loaded before it. +/// This indicates a violation of the specified load order requirements. +/// +/// The module with the load order requirement +/// The ID of the module that should be loaded after the target +public sealed record ModuleDependencyNotLoadedAfterIssue( + ModuleInfoExtended Module, + string DependencyId +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyNotLoadedAfterThis, $"'{DependencyId}' should be loaded after '{Module.Id}'", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module is missing its required module ID. +/// This is required by every mod. +/// +/// The module missing its ID +public sealed record ModuleMissingIdIssue( + ModuleInfoExtended Module +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.MissingModuleId, $"Module Id is missing for '{Module.Name}'", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module is missing its required name. +/// This is a required field. +/// +/// The module missing its name +public sealed record ModuleMissingNameIssue( + ModuleInfoExtended Module +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.MissingModuleName, $"Module Name is missing in '{Module.Id}'", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module has a null/empty dependency reference. +/// This indicates an invalid dependency configuration. +/// +/// The module with the null dependency +public sealed record ModuleDependencyNullIssue( + ModuleInfoExtended Module +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyIsNull, $"Found a null dependency in '{Module.Id}'", ApplicationVersionRange.Empty); +} + +/// +/// Represents an issue where a module's dependency is missing its required module ID. +/// This indicates an invalid or incomplete dependency configuration. +/// +/// The module with a dependency missing its ID +public sealed record ModuleDependencyMissingIdIssue( + ModuleInfoExtended Module +) : ModuleIssueV2(Module) +{ + public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyMissingModuleId, $"Module Id is missing for one of the dependencies of '{Module.Id}'", ApplicationVersionRange.Empty); +} \ No newline at end of file diff --git a/src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs b/src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs new file mode 100644 index 0000000..d2f12cc --- /dev/null +++ b/src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs @@ -0,0 +1,7 @@ +// Polyfill for NS2.0 +// ReSharper disable once UnusedType.Global +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit {} +} \ No newline at end of file diff --git a/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj b/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj index de9b701..19ca099 100644 --- a/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj +++ b/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -40,6 +40,8 @@ + + @@ -50,6 +52,8 @@ + + diff --git a/src/Bannerlord.ModuleManager/ModuleUtilities.cs b/src/Bannerlord.ModuleManager/ModuleUtilities.cs index 5821ac2..e6f516f 100644 --- a/src/Bannerlord.ModuleManager/ModuleUtilities.cs +++ b/src/Bannerlord.ModuleManager/ModuleUtilities.cs @@ -46,6 +46,7 @@ namespace Bannerlord.ModuleManager using global::System; using global::System.Collections.Generic; using global::System.Linq; + using Bannerlord.ModuleManager.Models.Issues; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal @@ -142,139 +143,250 @@ private static IEnumerable GetDependenciesInternal(IReadOnly } } +#region Polyfills /// /// Validates a module /// - /// All available modules - /// Any error that were detected during inspection - public static IEnumerable ValidateModule(IReadOnlyList modules, ModuleInfoExtended targetModule, Func isSelected) + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is selected (is enabled) + /// Any errors that were detected during inspection + public static IEnumerable ValidateModule( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected) { var visited = new HashSet(); - foreach (var issue in ValidateModule(modules, targetModule, visited, isSelected, x => ValidateModule(modules, x, isSelected).Count() == 0)) - yield return issue; + return ValidateModuleEx(modules, targetModule, visited, isSelected, + x => ValidateModuleEx(modules, x, isSelected).Count() == 0) + .Select(x => x.ToLegacy()); } + /// /// Validates a module /// - /// All available modules - /// Any error that were detected during inspection - public static IEnumerable ValidateModule(IReadOnlyList modules, ModuleInfoExtended targetModule, Func isSelected, Func isValid) + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is selected (is enabled) + /// Function that determines if a module is valid + /// Any errors that were detected during inspection + public static IEnumerable ValidateModule( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected, + Func isValid) { var visited = new HashSet(); - foreach (var issue in ValidateModule(modules, targetModule, visited, isSelected, isValid)) - yield return issue; + return ValidateModuleEx(modules, targetModule, visited, isSelected, isValid) + .Select(x => x.ToLegacy()); } + /// - /// Validates a module + /// Validates a module's common data /// - /// All available modules - /// Any error that were detected during inspection - public static IEnumerable ValidateModule(IReadOnlyList modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func isSelected, Func isValid) + /// The module to validate + /// Any errors that were detected during inspection of common data + public static IEnumerable ValidateModuleCommonData(ModuleInfoExtended module) => + ValidateModuleCommonDataEx(module).Select(x => x.ToLegacy()); + + /// + /// Validates a module's dependency declarations + /// + /// All available modules (in any order) + /// The module whose dependencies are being validated + /// Any errors that were detected during inspection of dependencies + public static IEnumerable ValidateModuleDependenciesDeclarations( + IReadOnlyList modules, + ModuleInfoExtended targetModule) => + ValidateModuleDependenciesDeclarationsEx(modules, targetModule).Select(x => x.ToLegacy()); + + /// + /// Validates module dependencies + /// + /// All available modules (in any order) + /// The module whose dependencies are being validated + /// Set of modules already validated to prevent cycles + /// Function that determines if a module is selected + /// Function that determines if a module is valid + /// Any errors that were detected during inspection of dependencies + public static IEnumerable ValidateModuleDependencies( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules, + Func isSelected, + Func isValid) => + ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid) + .Select(x => x.ToLegacy()); + + /// + /// Validates whether the load order is correctly sorted + /// + /// Assumed that only valid and selected to launch modules are in the list + /// Assumed that it's present in + /// Any errors that were detected during inspection + public static IEnumerable ValidateLoadOrder( + IReadOnlyList modules, + ModuleInfoExtended targetModule) { - // Validate common data foreach (var issue in ValidateModuleCommonData(targetModule)) yield return issue; + + var visited = new HashSet(); + foreach (var issue in ValidateLoadOrderEx(modules, targetModule, visited) + .Select(x => x.ToLegacy())) + yield return issue; + } + + /// + /// Validates whether the load order is correctly sorted + /// + /// Assumed that only valid and selected to launch modules are in the list + /// Assumed that it's present in + /// Used to track that we traverse each module only once + /// Any errors that were detected during inspection + public static IEnumerable ValidateLoadOrder( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules) => + ValidateLoadOrderEx(modules, targetModule, visitedModules).Select(x => x.ToLegacy()); +#endregion + + /// + /// Validates a module using the new variant-based issue system + /// + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is enabled + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected) + { + var visited = new HashSet(); + foreach (var issue in ValidateModuleEx(modules, targetModule, visited, isSelected, x => ValidateModuleEx(modules, x, isSelected).Count() == 0)) + { + yield return issue; + } + } + + /// + /// Validates a module using the new variant-based issue system + /// + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is enabled + /// Function that determines if a module is valid + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected, + Func isValid) + { + var visited = new HashSet(); + foreach (var issue in ValidateModuleEx(modules, targetModule, visited, isSelected, isValid)) + { + yield return issue; + } + } + + /// + /// Internal validation method that handles the core validation logic + /// + /// All available modules (in any order) + /// The module to validate + /// Set of modules already validated to prevent cycles + /// Function that determines if a module is enabled + /// Function that determines if a module is valid + /// Any errors that were detected during inspection + private static IEnumerable ValidateModuleEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules, + Func isSelected, + Func isValid) + { + foreach (var issue in ValidateModuleCommonDataEx(targetModule)) + yield return issue; - // Validate dependency declaration - foreach (var issue in ValidateModuleDependenciesDeclarations(modules, targetModule)) + foreach (var issue in ValidateModuleDependenciesDeclarationsEx(modules, targetModule)) yield return issue; - // Check that all dependencies are present - foreach (var issue in ValidateModuleDependencies(modules, targetModule, visitedModules, isSelected, isValid)) + foreach (var issue in ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid)) yield return issue; } /// - /// Validates a module's common data + /// Validates a module's common data using the new variant-based issue system /// - public static IEnumerable ValidateModuleCommonData(ModuleInfoExtended module) + /// The module whose common data is being validated + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleCommonDataEx(ModuleInfoExtended module) { if (string.IsNullOrWhiteSpace(module.Id)) - { - yield return new ModuleIssue(module, "UNKNOWN", ModuleIssueType.MissingModuleId) - { - Reason = $"Module Id is missing for '{module.Name}'" - }; - } + yield return new ModuleMissingIdIssue(module); + if (string.IsNullOrWhiteSpace(module.Name)) - { - yield return new ModuleIssue(module, module.Id, ModuleIssueType.MissingModuleName) - { - Reason = $"Module Name is missing in '{module.Id}'" - }; - } + yield return new ModuleMissingNameIssue(module); + foreach (var dependentModule in module.DependentModules.Where(x => x is not null)) { if (dependentModule is null) { - yield return new ModuleIssue(module, "UNKNOWN", ModuleIssueType.DependencyIsNull) - { - Reason = $"Found a null dependency in '{module.Id}'", - }; + yield return new ModuleDependencyNullIssue(module); break; } if (string.IsNullOrWhiteSpace(dependentModule.Id)) - { - yield return new ModuleIssue(module, "UNKNOWN", ModuleIssueType.DependencyMissingModuleId) - { - Reason = $"Module Id is missing for one if the dependencies of '{module.Id}'", - }; - } + yield return new ModuleDependencyMissingIdIssue(module); } + foreach (var dependentModuleMetadata in module.DependentModuleMetadatas.Where(x => x is not null)) { if (dependentModuleMetadata is null) { - yield return new ModuleIssue(module, "UNKNOWN", ModuleIssueType.DependencyIsNull) - { - Reason = $"Found a null dependency in '{module.Id}'", - }; + yield return new ModuleDependencyNullIssue(module); break; } if (string.IsNullOrWhiteSpace(dependentModuleMetadata.Id)) - { - yield return new ModuleIssue(module, "UNKNOWN", ModuleIssueType.DependencyMissingModuleId) - { - Reason = $"Module Id is missing for one if the dependencies of '{module.Id}'", - }; - } + yield return new ModuleDependencyMissingIdIssue(module); } } /// - /// Validates a module metadata + /// Validates module dependencies declarations using the new variant-based issue system /// - /// All available modules - /// Any error that were detected during inspection - public static IEnumerable ValidateModuleDependenciesDeclarations(IReadOnlyList modules, ModuleInfoExtended targetModule) + /// All available modules (in any order) + /// The module whose dependency declarations are being validated + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleDependenciesDeclarationsEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule) { // Any Incompatible module is depended on // TODO: Will a null Id break things? - foreach (var moduleId in targetModule.DependenciesToLoadDistinct().Select(x => x.Id).Intersect(targetModule.DependenciesIncompatiblesDistinct().Select(x => x.Id))) + foreach (var moduleId in targetModule.DependenciesToLoadDistinct() + .Select(x => x.Id) + .Intersect(targetModule.DependenciesIncompatiblesDistinct().Select(x => x.Id))) { - yield return new ModuleIssue(targetModule, moduleId, ModuleIssueType.DependencyConflictDependentAndIncompatible) - { - Reason = $"Module '{moduleId}' is both depended upon and marked as incompatible" - }; + yield return new ModuleDependencyConflictDependentAndIncompatibleIssue(targetModule, moduleId); } + // Check raw metadata too - foreach (var dependency in targetModule.DependentModuleMetadatas.Where(x => x is not null).Where(x => x.IsIncompatible && x.LoadType != LoadType.None)) + foreach (var dependency in targetModule.DependentModuleMetadatas + .Where(x => x is not null) + .Where(x => x.IsIncompatible && x.LoadType != LoadType.None)) { - yield return new ModuleIssue(targetModule, dependency.Id, ModuleIssueType.DependencyConflictDependentAndIncompatible) - { - Reason = $"Module '{dependency.Id}' is both depended upon and marked as incompatible" - }; + yield return new ModuleDependencyConflictDependentAndIncompatibleIssue(targetModule, dependency.Id); } // LoadBeforeThis conflicts with LoadAfterThis foreach (var module in targetModule.DependenciesLoadBeforeThisDistinct()) { - if (targetModule.DependenciesLoadAfterThisDistinct().FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)) is { } metadata) + if (targetModule.DependenciesLoadAfterThisDistinct() + .FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)) is { } metadata) { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter) - { - Reason = $"Module '{metadata.Id}' is both depended upon as LoadBefore and LoadAfter" - }; + yield return new ModuleDependencyConflictLoadBeforeAndAfterIssue(targetModule, metadata.Id); } } @@ -282,25 +394,31 @@ public static IEnumerable ValidateModuleDependenciesDeclarations(IR foreach (var module in targetModule.DependenciesToLoadDistinct().Where(x => x.LoadType != LoadType.None)) { var moduleInfo = modules.FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)); - if (moduleInfo?.DependenciesToLoadDistinct().Where(x => x.LoadType != LoadType.None).FirstOrDefault(x => string.Equals(x.Id, targetModule.Id, StringComparison.Ordinal)) is { } metadata) + if (moduleInfo?.DependenciesToLoadDistinct() + .Where(x => x.LoadType != LoadType.None) + .FirstOrDefault(x => string.Equals(x.Id, targetModule.Id, StringComparison.Ordinal)) is { } metadata) { if (metadata.LoadType == module.LoadType) - { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.DependencyConflictCircular) - { - Reason = $"Circular dependencies. '{targetModule.Id}' and '{moduleInfo.Id}' depend on each other" - }; - } + yield return new ModuleDependencyConflictCircularIssue(targetModule, metadata.Id); } } } + /// - /// Validates a module relative to other modules + /// Validates module dependencies using the new variant-based issue system /// - /// All available modules - /// Whether another module is valid. Can be checked by this function - /// Any error that were detected during inspection - public static IEnumerable ValidateModuleDependencies(IReadOnlyList modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func isSelected, Func isValid) + /// All available modules (in any order) + /// The module whose dependencies are being validated + /// Set of modules already validated to prevent cycles + /// Function that determines if a module is enabled + /// Function that determines if a module is valid + /// Any errors that were detected during inspection + private static IEnumerable ValidateModuleDependenciesEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules, + Func isSelected, + Func isValid) { // Check that all dependencies are present foreach (var metadata in targetModule.DependenciesToLoadDistinct()) @@ -312,135 +430,114 @@ public static IEnumerable ValidateModuleDependencies(IReadOnlyList< { if (metadata.Version != ApplicationVersion.Empty) { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.MissingDependencies) - { - Reason = $"Missing '{metadata.Id}' {metadata.Version}", - SourceVersion = new(metadata.Version, metadata.Version) - }; + yield return new ModuleMissingDependenciesIssue( + targetModule, + metadata.Id, + new ApplicationVersionRange(metadata.Version, metadata.Version)); } else if (metadata.VersionRange != ApplicationVersionRange.Empty) { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.MissingDependencies) - { - Reason = $"Missing '{metadata.Id}' {metadata.VersionRange}", - SourceVersion = metadata.VersionRange - }; + yield return new ModuleMissingDependenciesIssue( + targetModule, + metadata.Id, + metadata.VersionRange); } else { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.MissingDependencies) - { - Reason = $"Missing '{metadata.Id}'" - }; + yield return new ModuleMissingDependenciesIssue(targetModule, metadata.Id); } yield break; } } - + // Check that the dependencies themselves have all dependencies present var opts = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; var dependencies = GetDependencies(modules, targetModule, visitedModules, opts).ToArray(); + foreach (var dependency in dependencies) { - if (targetModule.DependenciesAllDistinct().FirstOrDefault(x => string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is { } metadata) + if (targetModule.DependenciesAllDistinct().FirstOrDefault(x => + string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is { } metadata) { // Not found, should not be possible if (metadata is null) continue; - // Handle only direct dependencies if (metadata.LoadType != LoadType.LoadBeforeThis) continue; - // Ignore the check for Optional if (metadata.IsOptional) continue; - // Ignore the check for Incompatible if (metadata.IsIncompatible) continue; - + // Check missing dependency dependencies - if (modules.FirstOrDefault(x => string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is not { } depencencyModuleInfo) + if (modules.FirstOrDefault(x => string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is not { } dependencyModuleInfo) { - yield return new ModuleIssue(targetModule, dependency.Id, ModuleIssueType.DependencyMissingDependencies) - { - Reason = $"'{dependency.Id}' is missing it's dependencies!" - }; + yield return new ModuleDependencyMissingDependenciesIssue(targetModule, dependency.Id); continue; } // Check depencency correctness - if (!isValid(depencencyModuleInfo)) - { - yield return new ModuleIssue(targetModule, dependency.Id, ModuleIssueType.DependencyValidationError) - { - Reason = $"'{dependency.Id}' has unresolved issues!" - }; - } + if (!isValid(dependencyModuleInfo)) + yield return new ModuleDependencyValidationIssue(targetModule, dependency.Id); } } - // Check that the dependencies have the minimum required version set by DependedModuleMetadatas +, // Check that the dependencies have the minimum required version set by DependedModuleMetadatas foreach (var metadata in targetModule.DependenciesToLoadDistinct()) { // Ignore the check for empty versions - if (metadata.Version == ApplicationVersion.Empty && metadata.VersionRange == ApplicationVersionRange.Empty) continue; + if (metadata.Version == ApplicationVersion.Empty && metadata.VersionRange == ApplicationVersionRange.Empty) + continue; // Dependency is loaded - if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) continue; + if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) + continue; if (metadata.Version != ApplicationVersion.Empty) { // dependedModuleMetadata.Version > dependedModule.Version if (!metadata.IsOptional && (ApplicationVersionComparer.CompareStandard(metadata.Version, metadataModule.Version) > 0)) { - yield return new ModuleIssue(targetModule, metadataModule.Id, ModuleIssueType.VersionMismatchLessThanOrEqual) - { - Reason = $"'{metadataModule.Id}' wrong version <= {metadata.Version}", - SourceVersion = new(metadata.Version, metadata.Version) - }; + yield return new ModuleVersionMismatchLessThanOrEqualSpecificIssue( + targetModule, + metadataModule.Id, + metadata.Version); continue; } } - if (metadata.VersionRange != ApplicationVersionRange.Empty) + + if (metadata.VersionRange != ApplicationVersionRange.Empty && !metadata.IsOptional) { // dependedModuleMetadata.Version > dependedModule.VersionRange.Min // dependedModuleMetadata.Version < dependedModule.VersionRange.Max - if (!metadata.IsOptional) + if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Min, metadataModule.Version) > 0) { - if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Min, metadataModule.Version) > 0) - { - yield return new ModuleIssue(targetModule, metadataModule.Id, ModuleIssueType.VersionMismatchLessThan) - { - Reason = $"'{metadataModule?.Id}' wrong version < [{metadata.VersionRange}]", - SourceVersion = metadata.VersionRange - }; - continue; - } - if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Max, metadataModule.Version) < 0) - { - yield return new ModuleIssue(targetModule, metadataModule.Id, ModuleIssueType.VersionMismatchGreaterThan) - { - Reason = $"'{metadataModule.Id}' wrong version > [{metadata.VersionRange}]", - SourceVersion = metadata.VersionRange - }; - continue; - } + yield return new ModuleVersionMismatchLessThanRangeIssue( + targetModule, + metadataModule.Id, + metadata.VersionRange); + continue; + } + if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Max, metadataModule.Version) < 0) + { + yield return new ModuleVersionMismatchGreaterThanRangeIssue( + targetModule, + metadataModule.Id, + metadata.VersionRange); + continue; } } } - + // Do not load this mod if an incompatible mod is selected foreach (var metadata in targetModule.DependenciesIncompatiblesDistinct()) { // Dependency is loaded - if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule || !isSelected(metadataModule)) continue; + if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule || + !isSelected(metadataModule)) continue; // If the incompatible mod is selected, this mod should be disabled if (isSelected(metadataModule)) - { - yield return new ModuleIssue(targetModule, metadataModule.Id, ModuleIssueType.Incompatible) - { - Reason = $"'{metadataModule.Id}' is incompatible with this module" - }; - } + yield return new ModuleIncompatibleIssue(targetModule, metadataModule.Id); } // If another mod declared incompatibility and is selected, disable this @@ -448,91 +545,84 @@ public static IEnumerable ValidateModuleDependencies(IReadOnlyList< { // Ignore self if (string.Equals(module.Id, targetModule.Id, StringComparison.Ordinal)) continue; - if (!isSelected(module)) continue; - + foreach (var metadata in module.DependenciesIncompatiblesDistinct()) { if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; - + // If the incompatible mod is selected, this mod is disabled if (isSelected(module)) - { - yield return new ModuleIssue(targetModule, module.Id, ModuleIssueType.Incompatible) - { - Reason = $"'{module.Id}' is incompatible with this module" - }; - } + yield return new ModuleIncompatibleIssue(targetModule, module.Id); } } } /// - /// Validates whether the load order is correctly sorted + /// Validates whether the load order is correctly sorted using the new variant-based issue system /// - /// Assumed that only valid and selected to launch modules are in the list - /// Assumed that it's present in - /// Any error that were detected during inspection - public static IEnumerable ValidateLoadOrder(IReadOnlyList modules, ModuleInfoExtended targetModule) + /// All available modules + /// The module whose load order is being validated + /// Any errors that were detected during inspection + public static IEnumerable ValidateLoadOrderEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule) { - // Validate common data - foreach (var issue in ValidateModuleCommonData(targetModule)) + foreach (var issue in ValidateModuleCommonDataEx(targetModule)) yield return issue; var visited = new HashSet(); - foreach (var issue in ValidateLoadOrder(modules, targetModule, visited)) + foreach (var issue in ValidateLoadOrderEx(modules, targetModule, visited)) yield return issue; } + /// - /// Validates whether the load order is correctly sorted + /// Validates whether the load order is correctly sorted using the new variant-based issue system /// - /// Assumed that only valid and selected to launch modules are in the list - /// Assumed that it's present in - /// Used to track that we traverse each module only once - /// Any error that were detected during inspection - public static IEnumerable ValidateLoadOrder(IReadOnlyList modules, ModuleInfoExtended targetModule, HashSet visitedModules) + /// All available modules + /// The module whose load order is being validated + /// Set of modules already validated to prevent cycles + /// Any errors that were detected during inspection + public static IEnumerable ValidateLoadOrderEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules) { var targetModuleIdx = CollectionsExtensions.IndexOf(modules, targetModule); if (targetModuleIdx == -1) { - yield return new ModuleIssue(targetModule, targetModule.Id, ModuleIssueType.Missing) - { - Reason = $"Missing '{targetModule.Id}' {targetModule.Version} in modules list", - SourceVersion = new(targetModule.Version, targetModule.Version) - }; + yield return new ModuleMissingIssue( + targetModule, + new ApplicationVersionRange(targetModule.Version, targetModule.Version)); yield break; } - // Check that all dependencies are present foreach (var metadata in targetModule.DependenciesToLoad().DistinctBy(x => x.Id)) { - var metadataIdx = CollectionsExtensions.IndexOf(modules, x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)); + var metadataIdx = CollectionsExtensions.IndexOf(modules, x => + string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)); + if (metadataIdx == -1) { if (!metadata.IsOptional) { if (metadata.Version != ApplicationVersion.Empty) { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.MissingDependencies) - { - Reason = $"Missing '{metadata.Id}' {metadata.Version}", - SourceVersion = new(metadata.Version, metadata.Version) - }; + yield return new ModuleMissingDependenciesIssue( + targetModule, + metadata.Id, + new ApplicationVersionRange(metadata.Version, metadata.Version)); } else if (metadata.VersionRange != ApplicationVersionRange.Empty) { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.MissingDependencies) - { - Reason = $"Missing '{metadata.Id}' {metadata.VersionRange}", - SourceVersion = metadata.VersionRange - }; + yield return new ModuleMissingDependenciesIssue( + targetModule, + metadata.Id, + metadata.VersionRange); } else { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.MissingDependencies) - { - Reason = $"Missing '{metadata.Id}'" - }; + yield return new ModuleMissingDependenciesIssue(targetModule, metadata.Id); } } continue; @@ -540,18 +630,12 @@ public static IEnumerable ValidateLoadOrder(IReadOnlyList targetModuleIdx) { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.DependencyNotLoadedBeforeThis) - { - Reason = $"'{metadata.Id}' should be loaded before '{targetModule.Id}'" - }; + yield return new ModuleDependencyNotLoadedBeforeIssue(targetModule, metadata.Id); } if (metadata.LoadType == LoadType.LoadAfterThis && metadataIdx < targetModuleIdx) { - yield return new ModuleIssue(targetModule, metadata.Id, ModuleIssueType.DependencyNotLoadedAfterThis) - { - Reason = $"'{metadata.Id}' should be loaded after '{targetModule.Id}'" - }; + yield return new ModuleDependencyNotLoadedAfterIssue(targetModule, metadata.Id); } } } From ee6c354837cadf18ab0179993c86180e91869a12 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Tue, 12 Nov 2024 12:13:26 +0000 Subject: [PATCH 2/5] Expanded: V2 Module Issue Full Descriptions --- .../Issues/ModuleIssueV2.cs | 605 ++++++++++++++---- .../ModuleUtilities.cs | 36 +- 2 files changed, 510 insertions(+), 131 deletions(-) diff --git a/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs index b254b28..7936b05 100644 --- a/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs +++ b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs @@ -1,107 +1,221 @@ using LegacyModuleIssue = Bannerlord.ModuleManager.ModuleIssue; +// ReSharper disable MemberCanBePrivate.Global namespace Bannerlord.ModuleManager.Models.Issues; -/// -/// Base record type for all module-related issues that can occur during validation. -/// This record serves as the abstract base class for all specific issue variants, -/// providing a common structure and conversion capability to legacy formats. -/// +/// +/// Base record type for all module-related issues that can occur during validation. +/// This record serves as the abstract base class for all specific issue variants, +/// providing a common structure and conversion capability to legacy formats. +/// /// The module in which the issue was detected. This is always required. public abstract record ModuleIssueV2(ModuleInfoExtended Module) { /// - /// Converts this issue instance to the legacy ModuleIssue format for backwards compatibility + /// Converts this issue instance to the legacy ModuleIssue format for backwards compatibility /// - /// A representation of this issue + /// A representation of this issue public abstract LegacyModuleIssue ToLegacy(); } -/// +/// /// Represents an issue where a required module is missing from the module list. -/// This typically occurs when a module declares a dependency on another module -/// that is not present in the current module collection. -/// +/// This indicates some sort of error with the API usage, you called a method with a +/// module list, but the module you provided in another parameter was not in that list. +/// /// The module that was found to be missing /// The version range in which the module should exist +public sealed record ModuleMissingIssue( + ModuleInfoExtended Module, + ApplicationVersionRange SourceVersion +) : ModuleIssueV2(Module) +{ + public override string ToString() => $"Module '{Module.Id}' {Module.Version} is missing from modules list"; + public override ModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.Missing, ToString(), SourceVersion); +} + +/// +/// Represents an issue where a required dependency module is missing and no version was specified +/// +/// The module with the missing dependency +/// The ID of the missing dependency module /// -/// This issue occurs when a module is completely missing from the game's module list. +/// This issue occurs when a required unversioned dependency is missing. /// /// Example scenario: -/// Your SubModule.xml references a required module, but that module is not installed: /// ```xml -/// -/// -/// +/// +/// +/// +/// +/// +/// +/// +/// /// ``` +/// If `TournamentOverhaul` is not installed at all, this issue will be raised if `SimpleTournaments` is enabled. +/// Note that it's recommended to use `DependedModuleMetadatas` with version specifications instead. /// -public sealed record ModuleMissingIssue( +public sealed record ModuleMissingUnversionedDependencyIssue( ModuleInfoExtended Module, - ApplicationVersionRange SourceVersion + string DependencyId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.Missing, $"Missing '{Module.Id}' {Module.Version} in modules list", SourceVersion); + public override string ToString() => $"Missing '{DependencyId}' module"; + public override ModuleIssue ToLegacy() => new( + Module, + DependencyId, + ModuleIssueType.MissingDependencies, + ToString(), + ApplicationVersionRange.Empty); } +/// +/// Represents an issue where a required dependency module is missing AND an exact version was specified +/// +/// The module with the missing dependency +/// The ID of the missing dependency module +/// The specific version that was required /// -/// Represents an issue where a module is missing one or more of its required dependencies. -/// This can occur when a module explicitly declares a dependency that cannot be found, -/// either because it's not installed or not loaded. +/// This issue occurs when a required dependency with an exact version requirement is missing. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If `Bannerlord.UIExtenderEx` is not installed at all (any version), this issue will be raised. +/// This is different from version mismatch issues where the module is present but with wrong version. /// -/// The module with missing dependencies -/// The ID of the missing dependency module -/// Optional version range specifying which versions of the dependency are acceptable -public sealed record ModuleMissingDependenciesIssue( +public sealed record ModuleMissingExactVersionDependencyIssue( ModuleInfoExtended Module, string DependencyId, - ApplicationVersionRange? SourceVersion = null + ApplicationVersion RequiredVersion ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() - { - var reason = SourceVersion switch - { - null => $"Missing '{DependencyId}'", - _ => $"Missing '{DependencyId}' {SourceVersion}" - }; - return new(Module, DependencyId, ModuleIssueType.MissingDependencies, reason, SourceVersion ?? ApplicationVersionRange.Empty); - } + public override string ToString() => $"Missing '{DependencyId}' with required version {RequiredVersion}"; + public override ModuleIssue ToLegacy() => new( + Module, + DependencyId, + ModuleIssueType.MissingDependencies, + ToString(), + new ApplicationVersionRange(RequiredVersion, RequiredVersion)); } +/// +/// Represents an issue where a required dependency module is missing and a version range was specified +/// +/// The module with the missing dependency +/// The ID of the missing dependency module +/// The version range that was required /// -/// Represents an issue where a module's dependency is itself missing required dependencies. -/// This is a cascading issue where a module's dependency has unresolved dependencies, -/// indicating a deeper problem in the dependency chain. +/// This issue occurs when a required dependency with a version range requirement is missing. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If `Bannerlord.UIExtenderEx` module is not installed at all (any version), this issue will be raised. +/// This is different from version mismatch issues where the module is present but with wrong version. /// +public sealed record ModuleMissingVersionRangeDependencyIssue( + ModuleInfoExtended Module, + string DependencyId, + ApplicationVersionRange RequiredVersionRange +) : ModuleIssueV2(Module) +{ + public override string ToString() => $"Missing '{DependencyId}' with required version range [{RequiredVersionRange}]"; + public override ModuleIssue ToLegacy() => new(Module, + DependencyId, + ModuleIssueType.MissingDependencies, + ToString(), + RequiredVersionRange); +} + +/// +/// Represents an issue where a module's dependency is itself missing required dependencies. +/// This is a cascading issue where a module's dependency has unresolved dependencies, +/// indicating a deeper problem in the dependency chain. +/// /// The module whose dependency has missing dependencies /// The ID of the dependency module that is missing its own dependencies +/// +/// This issue occurs when a dependency has missing dependencies of its own. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If `BetterTime` requires `Harmony` but `Harmony` is not installed, this issue will be raised for `RealisticWeather` +/// because its dependency (`BetterTime`) has missing dependencies. +/// public sealed record ModuleDependencyMissingDependenciesIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyMissingDependencies, $"'{DependencyId}' is missing its dependencies!", ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}': Required dependency '{DependencyId}' is missing its own dependencies"; + public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyMissingDependencies, ToString(), ApplicationVersionRange.Empty); } -/// -/// Represents an issue where a module's dependency fails validation checks. -/// This indicates that while the dependency exists, it has its own validation -/// issues that need to be resolved. -/// +/// +/// Represents an issue where a module's dependency fails validation checks. +/// This indicates that while the dependency exists, it has its own validation +/// issues that need to be resolved. +/// /// The module with the dependency that failed validation /// The ID of the dependency module that failed validation +/// +/// This issue occurs when a dependency has validation issues of its own. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If `CustomSpawnsFramework` has its own issues (like missing dependencies or invalid configuration), +/// this issue will be raised for `CustomSpawns` since its dependency needs to be fixed first. +/// public sealed record ModuleDependencyValidationIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyValidationError, $"'{DependencyId}' has unresolved issues!", ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' has unresolved validation issues"; + public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyValidationError, ToString(), ApplicationVersionRange.Empty); } -/// -/// Base record type for version mismatch issues between modules and their dependencies. -/// This serves as an abstract base for both specific version and version range issues. -/// +/// +/// Base record type for version mismatch issues between modules and their dependencies. +/// This serves as an abstract base for both specific version and version range issues. +/// /// The module with the version mismatch /// The ID of the dependency module with mismatched version public abstract record ModuleVersionMismatchIssue( @@ -109,10 +223,10 @@ public abstract record ModuleVersionMismatchIssue( string DependencyId ) : ModuleIssueV2(Module); -/// -/// Base record type for version mismatch issues involving specific versions. -/// Used when comparing against exact version numbers rather than ranges. -/// +/// +/// Base record type for version mismatch issues involving specific versions. +/// Used when comparing against exact version numbers rather than ranges. +/// /// The module with the version mismatch /// The ID of the dependency module with mismatched version /// The specific version being compared against @@ -122,10 +236,10 @@ public abstract record ModuleVersionMismatchSpecificIssue( ApplicationVersion Version ) : ModuleVersionMismatchIssue(Module, DependencyId); -/// -/// Base record type for version mismatch issues involving version ranges. -/// Used when comparing against version ranges rather than specific versions. -/// +/// +/// Base record type for version mismatch issues involving version ranges. +/// Used when comparing against version ranges rather than specific versions. +/// /// The module with the version mismatch /// The ID of the dependency module with mismatched version /// The version range being compared against @@ -135,183 +249,440 @@ public abstract record ModuleVersionMismatchRangeIssue( ApplicationVersionRange VersionRange ) : ModuleVersionMismatchIssue(Module, DependencyId); -/// -/// Represents an issue where a dependency's version is higher than the maximum allowed specific version. -/// This occurs when a dependency module's version exceeds an exact version requirement. -/// +/// +/// Represents an issue where a dependency's version is higher than the maximum allowed specific version. +/// This occurs when a dependency module's version exceeds an exact version requirement. +/// /// The module with the version constraint /// The ID of the dependency module that exceeds the version requirement /// The specific version that should not be exceeded +/// +/// This issue occurs when a module specifies incompatible versions. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// +/// If a higher version of Harmony (e.g., `v2.3.0`) is installed than allowed, this issue will be raised. +/// public sealed record ModuleVersionMismatchLessThanOrEqualSpecificIssue( ModuleInfoExtended Module, string DependencyId, ApplicationVersion Version ) : ModuleVersionMismatchSpecificIssue(Module, DependencyId, Version) { - public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThanOrEqual, $"'{DependencyId}' wrong version <= {Version}", new(Version, Version)); + public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' has wrong version (required <= {Version})"; + public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThanOrEqual, ToString(), new ApplicationVersionRange(Version, Version)); } -/// -/// Represents an issue where a dependency's version is less than the minimum required version range. -/// This occurs when a dependency module's version is below the minimum version specified in a range. -/// +/// +/// Represents an issue where a dependency's version is less than the minimum required version range. +/// This occurs when a dependency module's version is below the minimum version specified in a range. +/// /// The module with the version requirement /// The ID of the dependency module that doesn't meet the minimum version /// The version range containing the minimum version requirement +/// +/// This issue occurs when a dependency's version is below the minimum required version range. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If an older version of ButterLib (e.g., v2.8.14) is installed, this issue will be raised. +/// public sealed record ModuleVersionMismatchLessThanRangeIssue( ModuleInfoExtended Module, string DependencyId, ApplicationVersionRange VersionRange ) : ModuleVersionMismatchRangeIssue(Module, DependencyId, VersionRange) { - public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThan, $"'{DependencyId}' wrong version < [{VersionRange}]", VersionRange); + public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' version is below minimum required range ($version < [{VersionRange}])"; + public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThan, ToString(), VersionRange); } -/// -/// Represents an issue where a dependency's version exceeds the maximum allowed version range. -/// This occurs when a dependency module's version is above the maximum version specified in a range. -/// +/// +/// Represents an issue where a dependency's version exceeds the maximum allowed version range. +/// This occurs when a dependency module's version is above the maximum version specified in a range. +/// /// The module with the version constraint /// The ID of the dependency module that exceeds the version limit /// The version range containing the maximum version requirement +/// +/// This issue occurs when a dependency's version exceeds the maximum allowed version range. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If a version of `ModB` that falls within the range is installed, this issue will be raised. +/// public sealed record ModuleVersionMismatchGreaterThanRangeIssue( ModuleInfoExtended Module, string DependencyId, ApplicationVersionRange VersionRange ) : ModuleVersionMismatchRangeIssue(Module, DependencyId, VersionRange) { - public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchGreaterThan, $"'{DependencyId}' wrong version > [{VersionRange}]", VersionRange); + public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' version exceeds maximum allowed range ($version > [{VersionRange}])"; + public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchGreaterThan, ToString(), VersionRange); } -/// -/// Represents an issue where two modules are incompatible with each other. -/// This occurs when one module explicitly declares it cannot work with another module. -/// +/// +/// Represents an issue where two modules are incompatible with each other. +/// This occurs when one module explicitly declares it cannot work with another module. +/// /// The module that has declared an incompatibility /// The ID of the module that is incompatible with the target +/// +/// This issue occurs when a module explicitly marks another module as incompatible. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If both `AlternativeArmorSystem` is enabled when `RealisticBattleArmor` is already enabled, this issue will be raised. +/// public sealed record ModuleIncompatibleIssue( ModuleInfoExtended Module, string IncompatibleModuleId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, IncompatibleModuleId, ModuleIssueType.Incompatible, $"'{IncompatibleModuleId}' is incompatible with this module", ApplicationVersionRange.Empty); + public override string ToString() => $"'{IncompatibleModuleId}' is incompatible with this module"; + public override LegacyModuleIssue ToLegacy() => new(Module, IncompatibleModuleId, ModuleIssueType.Incompatible, ToString(), ApplicationVersionRange.Empty); } -/// -/// Represents an issue where a module is both depended upon and marked as incompatible. -/// This indicates a contradictory configuration where a module is required but also -/// marked as incompatible. -/// +/// +/// Represents an issue where a module is both depended upon and marked as incompatible. +/// This indicates a contradictory configuration where a module is required but also +/// marked as incompatible. +/// /// The module with the conflicting dependency declaration /// The ID of the module that is both depended upon and marked incompatible +/// +/// This issue occurs when a module has conflicting configurations for dependencies. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// This is a configuration error as `TroopOverhaul` cannot be both required and incompatible. +/// public sealed record ModuleDependencyConflictDependentAndIncompatibleIssue( ModuleInfoExtended Module, string ConflictingModuleId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentAndIncompatible, $"Module '{ConflictingModuleId}' is both depended upon and marked as incompatible", ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}' has conflicting configuration: '{ConflictingModuleId}' is marked as both required and incompatible"; + public override ModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentAndIncompatible, ToString(), ApplicationVersionRange.Empty); } -/// -/// Represents an issue where a module is declared to load both before and after another module. -/// This indicates a contradictory load order configuration that cannot be satisfied. -/// +/// +/// Represents an issue where a module is declared to load both before and after another module. +/// This indicates a contradictory load order configuration that cannot be satisfied. +/// /// The module with the conflicting load order declaration /// The ID of the module that has conflicting load order requirements +/// +/// This issue occurs when a module has conflicting load order requirements. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// This creates an impossible load order requirement as `ArenaOverhaul` cannot load both before and after `ImprovedTournaments`. +/// public sealed record ModuleDependencyConflictLoadBeforeAndAfterIssue( ModuleInfoExtended Module, string ConflictingModuleId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter, $"Module '{ConflictingModuleId}' is both depended upon as LoadBefore and LoadAfter", ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}' has conflicting load order requirements with '{ConflictingModuleId}' (both LoadBefore and LoadAfter)"; + public override ModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter, ToString(), ApplicationVersionRange.Empty); } -/// -/// Represents an issue where modules have circular dependencies on each other. -/// This occurs when two or more modules form a dependency cycle that cannot be resolved. -/// +/// +/// Represents an issue where modules have circular dependencies on each other. +/// This occurs when two or more modules form a dependency cycle that cannot be resolved. +/// /// One of the modules in the circular dependency chain /// The ID of another module in the circular dependency chain +/// +/// This issue occurs when modules create a circular dependency chain. +/// +/// Example scenario: +/// Two modules depend on each other in a way that creates an unresolvable cycle: +/// +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// +/// ❌ This creates an impossible situation where each module must load before the other. +/// public sealed record ModuleDependencyConflictCircularIssue( ModuleInfoExtended Module, string CircularDependencyId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, CircularDependencyId, ModuleIssueType.DependencyConflictCircular, $"Circular dependencies. '{Module.Id}' and '{CircularDependencyId}' depend on each other", ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}' and '{CircularDependencyId}' have circular dependencies"; + public override ModuleIssue ToLegacy() => new(Module, CircularDependencyId, ModuleIssueType.DependencyConflictCircular, ToString(), ApplicationVersionRange.Empty); } -/// -/// Represents an issue where a module that should be loaded before the target module is loaded after it. -/// This indicates a violation of the specified load order requirements. -/// +/// +/// Represents an issue where a module that should be loaded before the target module is loaded after it. +/// This indicates a violation of the specified load order requirements. +/// /// The module with the load order requirement /// The ID of the module that should be loaded before the target +/// +/// This issue occurs when a required "load before" dependency loads after the specified module. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If `Harmony` is loading after `BetterBattles` in the actual load order, this issue will be raised. +/// public sealed record ModuleDependencyNotLoadedBeforeIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyNotLoadedBeforeThis, $"'{DependencyId}' should be loaded before '{Module.Id}'", ApplicationVersionRange.Empty); + public override string ToString() => $"'{DependencyId}' should be loaded before '{Module.Id}'"; + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyNotLoadedBeforeThis, ToString(), ApplicationVersionRange.Empty); } -/// -/// Represents an issue where a module that should be loaded after the target module is loaded before it. -/// This indicates a violation of the specified load order requirements. -/// +/// +/// Represents an issue where a module that should be loaded after the target module is loaded before it. +/// This indicates a violation of the specified load order requirements. +/// /// The module with the load order requirement /// The ID of the module that should be loaded after the target +/// +/// This issue occurs when a required "load after" dependency loads before the specified module. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// If `ImprovedGarrisons` is loading before `BetterSettlements`, this issue will be raised. +/// public sealed record ModuleDependencyNotLoadedAfterIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyNotLoadedAfterThis, $"'{DependencyId}' should be loaded after '{Module.Id}'", ApplicationVersionRange.Empty); + public override string ToString() => $"'{DependencyId}' should be loaded after '{Module.Id}'"; + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyNotLoadedAfterThis, ToString(), ApplicationVersionRange.Empty); } +/// +/// Represents an issue where a module is missing its required module ID. +/// This is required by every mod. +/// +/// The module missing its ID /// -/// Represents an issue where a module is missing its required module ID. -/// This is required by every mod. +/// This issue occurs when a module is missing its required Id field. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// ``` +/// The Id field is required for module identification and dependency management. /// -/// The module missing its ID public sealed record ModuleMissingIdIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.MissingModuleId, $"Module Id is missing for '{Module.Name}'", ApplicationVersionRange.Empty); + public override string ToString() => $"Module with Name '{Module.Name}' is missing its Id field"; + public override ModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.MissingModuleId, ToString(), ApplicationVersionRange.Empty); } /// -/// Represents an issue where a module is missing its required name. -/// This is a required field. +/// Represents an issue where a module is missing its required name. +/// This is a required field. /// /// The module missing its name +/// +/// This issue occurs when a module has a malformed or missing Name field. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// ``` +/// The Name field is required for module identification and display purposes. +/// public sealed record ModuleMissingNameIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.MissingModuleName, $"Module Name is missing in '{Module.Id}'", ApplicationVersionRange.Empty); + public override string ToString() => $"Module with Id '{Module.Id}' is missing its Name field"; + public override ModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.MissingModuleName, ToString(), ApplicationVersionRange.Empty); } /// -/// Represents an issue where a module has a null/empty dependency reference. -/// This indicates an invalid dependency configuration. +/// Represents an issue where a module has a null/empty dependency reference. +/// This indicates an invalid dependency configuration. /// /// The module with the null dependency +/// +/// This issue occurs when a dependency entry is malformed or null. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// All dependency entries must be properly formed with required attributes. +/// public sealed record ModuleDependencyNullIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyIsNull, $"Found a null dependency in '{Module.Id}'", ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}' has a null dependency entry"; + public override ModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyIsNull, ToString(), ApplicationVersionRange.Empty); } /// -/// Represents an issue where a module's dependency is missing its required module ID. -/// This indicates an invalid or incomplete dependency configuration. +/// Represents an issue where a module's dependency is missing its required module ID. +/// This indicates an invalid or incomplete dependency configuration. /// /// The module with a dependency missing its ID +/// +/// This issue occurs when a dependency entry is missing its required Id field. +/// +/// Example scenario: +/// ```xml +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// ``` +/// All dependency entries must include an Id to identify the required module. +/// public sealed record ModuleDependencyMissingIdIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { - public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyMissingModuleId, $"Module Id is missing for one of the dependencies of '{Module.Id}'", ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}' has a dependency entry missing its Id field"; + public override ModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyMissingModuleId, ToString(), ApplicationVersionRange.Empty); } \ No newline at end of file diff --git a/src/Bannerlord.ModuleManager/ModuleUtilities.cs b/src/Bannerlord.ModuleManager/ModuleUtilities.cs index e6f516f..cb696c5 100644 --- a/src/Bannerlord.ModuleManager/ModuleUtilities.cs +++ b/src/Bannerlord.ModuleManager/ModuleUtilities.cs @@ -221,7 +221,7 @@ public static IEnumerable ValidateModuleDependencies( /// /// Validates whether the load order is correctly sorted /// - /// Assumed that only valid and selected to launch modules are in the list + /// All available modules in they order they are expected to be loaded in /// Assumed that it's present in /// Any errors that were detected during inspection public static IEnumerable ValidateLoadOrder( @@ -240,7 +240,7 @@ public static IEnumerable ValidateLoadOrder( /// /// Validates whether the load order is correctly sorted /// - /// Assumed that only valid and selected to launch modules are in the list + /// All available modules in they order they are expected to be loaded in /// Assumed that it's present in /// Used to track that we traverse each module only once /// Any errors that were detected during inspection @@ -430,21 +430,23 @@ private static IEnumerable ValidateModuleDependenciesEx( { if (metadata.Version != ApplicationVersion.Empty) { - yield return new ModuleMissingDependenciesIssue( - targetModule, + yield return new ModuleMissingExactVersionDependencyIssue( + targetModule, metadata.Id, - new ApplicationVersionRange(metadata.Version, metadata.Version)); + metadata.Version); } else if (metadata.VersionRange != ApplicationVersionRange.Empty) { - yield return new ModuleMissingDependenciesIssue( + yield return new ModuleMissingVersionRangeDependencyIssue( targetModule, metadata.Id, metadata.VersionRange); } else { - yield return new ModuleMissingDependenciesIssue(targetModule, metadata.Id); + yield return new ModuleMissingUnversionedDependencyIssue( + targetModule, + metadata.Id); } yield break; } @@ -481,7 +483,7 @@ private static IEnumerable ValidateModuleDependenciesEx( } } -, // Check that the dependencies have the minimum required version set by DependedModuleMetadatas + // Check that the dependencies have the minimum required version set by DependedModuleMetadatas foreach (var metadata in targetModule.DependenciesToLoadDistinct()) { // Ignore the check for empty versions @@ -561,7 +563,7 @@ private static IEnumerable ValidateModuleDependenciesEx( /// /// Validates whether the load order is correctly sorted using the new variant-based issue system /// - /// All available modules + /// All available modules in they order they are expected to be loaded in /// The module whose load order is being validated /// Any errors that were detected during inspection public static IEnumerable ValidateLoadOrderEx( @@ -579,10 +581,13 @@ public static IEnumerable ValidateLoadOrderEx( /// /// Validates whether the load order is correctly sorted using the new variant-based issue system /// - /// All available modules + /// All available modules in they order they are expected to be loaded in /// The module whose load order is being validated /// Set of modules already validated to prevent cycles /// Any errors that were detected during inspection + /// + /// In this case 'load order' means the set of enabled mods. + /// public static IEnumerable ValidateLoadOrderEx( IReadOnlyList modules, ModuleInfoExtended targetModule, @@ -597,6 +602,7 @@ public static IEnumerable ValidateLoadOrderEx( yield break; } + // Check that all dependencies are present foreach (var metadata in targetModule.DependenciesToLoad().DistinctBy(x => x.Id)) { var metadataIdx = CollectionsExtensions.IndexOf(modules, x => @@ -608,21 +614,23 @@ public static IEnumerable ValidateLoadOrderEx( { if (metadata.Version != ApplicationVersion.Empty) { - yield return new ModuleMissingDependenciesIssue( + yield return new ModuleMissingExactVersionDependencyIssue( targetModule, metadata.Id, - new ApplicationVersionRange(metadata.Version, metadata.Version)); + metadata.Version); } else if (metadata.VersionRange != ApplicationVersionRange.Empty) { - yield return new ModuleMissingDependenciesIssue( + yield return new ModuleMissingVersionRangeDependencyIssue( targetModule, metadata.Id, metadata.VersionRange); } else { - yield return new ModuleMissingDependenciesIssue(targetModule, metadata.Id); + yield return new ModuleMissingUnversionedDependencyIssue( + targetModule, + metadata.Id); } } continue; From 4ba6c25d0ab5eda4afe49725f34fcdbf465c10c2 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Tue, 12 Nov 2024 12:29:27 +0000 Subject: [PATCH 3/5] Added: Option to suppress checking dependencies of the current module for errors. This is useful if you want to check for diagnostics for a given module in isolation. When you are checking for issues with every module in a loadout, you do not want certain issues to be reported twice. --- .../ModuleUtilities.cs | 81 ++++++++++--------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/src/Bannerlord.ModuleManager/ModuleUtilities.cs b/src/Bannerlord.ModuleManager/ModuleUtilities.cs index cb696c5..c646c6c 100644 --- a/src/Bannerlord.ModuleManager/ModuleUtilities.cs +++ b/src/Bannerlord.ModuleManager/ModuleUtilities.cs @@ -299,13 +299,15 @@ public static IEnumerable ValidateModuleEx( /// Set of modules already validated to prevent cycles /// Function that determines if a module is enabled /// Function that determines if a module is valid + /// Set this to true to also report errors in the target module's dependencies. e.g. Missing dependencies of dependencies. /// Any errors that were detected during inspection private static IEnumerable ValidateModuleEx( IReadOnlyList modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func isSelected, - Func isValid) + Func isValid, + bool validateDependencies = true) { foreach (var issue in ValidateModuleCommonDataEx(targetModule)) yield return issue; @@ -313,7 +315,7 @@ private static IEnumerable ValidateModuleEx( foreach (var issue in ValidateModuleDependenciesDeclarationsEx(modules, targetModule)) yield return issue; - foreach (var issue in ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid)) + foreach (var issue in ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid, validateDependencies)) yield return issue; } @@ -412,13 +414,15 @@ public static IEnumerable ValidateModuleDependenciesDeclarationsE /// Set of modules already validated to prevent cycles /// Function that determines if a module is enabled /// Function that determines if a module is valid + /// Set this to true to also report errors in the target module's dependencies. e.g. Missing dependencies of dependencies. /// Any errors that were detected during inspection private static IEnumerable ValidateModuleDependenciesEx( IReadOnlyList modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func isSelected, - Func isValid) + Func isValid, + bool validateDependencies = true) { // Check that all dependencies are present foreach (var metadata in targetModule.DependenciesToLoadDistinct()) @@ -453,33 +457,35 @@ private static IEnumerable ValidateModuleDependenciesEx( } // Check that the dependencies themselves have all dependencies present - var opts = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; - var dependencies = GetDependencies(modules, targetModule, visitedModules, opts).ToArray(); - - foreach (var dependency in dependencies) + if (validateDependencies) { - if (targetModule.DependenciesAllDistinct().FirstOrDefault(x => - string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is { } metadata) + var opts = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; + var dependencies = GetDependencies(modules, targetModule, visitedModules, opts).ToArray(); + foreach (var dependency in dependencies) { - // Not found, should not be possible - if (metadata is null) continue; - // Handle only direct dependencies - if (metadata.LoadType != LoadType.LoadBeforeThis) continue; - // Ignore the check for Optional - if (metadata.IsOptional) continue; - // Ignore the check for Incompatible - if (metadata.IsIncompatible) continue; - - // Check missing dependency dependencies - if (modules.FirstOrDefault(x => string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is not { } dependencyModuleInfo) + if (targetModule.DependenciesAllDistinct().FirstOrDefault(x => + string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is { } metadata) { - yield return new ModuleDependencyMissingDependenciesIssue(targetModule, dependency.Id); - continue; - } + // Not found, should not be possible + if (metadata is null) continue; + // Handle only direct dependencies + if (metadata.LoadType != LoadType.LoadBeforeThis) continue; + // Ignore the check for Optional + if (metadata.IsOptional) continue; + // Ignore the check for Incompatible + if (metadata.IsIncompatible) continue; + + // Check missing dependency dependencies + if (modules.FirstOrDefault(x => string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is not { } dependencyModuleInfo) + { + yield return new ModuleDependencyMissingDependenciesIssue(targetModule, dependency.Id); + continue; + } - // Check depencency correctness - if (!isValid(dependencyModuleInfo)) - yield return new ModuleDependencyValidationIssue(targetModule, dependency.Id); + // Check dependency correctness + if (!isValid(dependencyModuleInfo)) + yield return new ModuleDependencyValidationIssue(targetModule, dependency.Id); + } } } @@ -543,19 +549,22 @@ private static IEnumerable ValidateModuleDependenciesEx( } // If another mod declared incompatibility and is selected, disable this - foreach (var module in modules) + if (validateDependencies) { - // Ignore self - if (string.Equals(module.Id, targetModule.Id, StringComparison.Ordinal)) continue; - if (!isSelected(module)) continue; - - foreach (var metadata in module.DependenciesIncompatiblesDistinct()) + foreach (var module in modules) { - if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; + // Ignore self + if (string.Equals(module.Id, targetModule.Id, StringComparison.Ordinal)) continue; + if (!isSelected(module)) continue; + + foreach (var metadata in module.DependenciesIncompatiblesDistinct()) + { + if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; - // If the incompatible mod is selected, this mod is disabled - if (isSelected(module)) - yield return new ModuleIncompatibleIssue(targetModule, module.Id); + // If the incompatible mod is selected, this mod is disabled + if (isSelected(module)) + yield return new ModuleIncompatibleIssue(targetModule, module.Id); + } } } } From 3174d1738e357c481f2778ba7cefe9dd461d1bf6 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Tue, 12 Nov 2024 13:41:29 +0000 Subject: [PATCH 4/5] Improve: Expose entire module info/metadata in additional ModuleIssueV2 variants --- .../Issues/ModuleIssueV2.cs | 99 ++++++++++--------- .../ModuleUtilities.cs | 50 +++------- 2 files changed, 61 insertions(+), 88 deletions(-) diff --git a/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs index 7936b05..0ba2278 100644 --- a/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs +++ b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs @@ -38,7 +38,7 @@ ApplicationVersionRange SourceVersion /// Represents an issue where a required dependency module is missing and no version was specified /// /// The module with the missing dependency -/// The ID of the missing dependency module +/// The missing dependency module /// /// This issue occurs when a required unversioned dependency is missing. /// @@ -58,13 +58,13 @@ ApplicationVersionRange SourceVersion /// public sealed record ModuleMissingUnversionedDependencyIssue( ModuleInfoExtended Module, - string DependencyId + DependentModuleMetadata Dependency ) : ModuleIssueV2(Module) { - public override string ToString() => $"Missing '{DependencyId}' module"; + public override string ToString() => $"Missing '{Dependency.Id}' module"; public override ModuleIssue ToLegacy() => new( Module, - DependencyId, + Dependency.Id, ModuleIssueType.MissingDependencies, ToString(), ApplicationVersionRange.Empty); @@ -74,8 +74,7 @@ string DependencyId /// Represents an issue where a required dependency module is missing AND an exact version was specified /// /// The module with the missing dependency -/// The ID of the missing dependency module -/// The specific version that was required +/// The missing dependency module /// /// This issue occurs when a required dependency with an exact version requirement is missing. /// @@ -95,25 +94,23 @@ string DependencyId /// public sealed record ModuleMissingExactVersionDependencyIssue( ModuleInfoExtended Module, - string DependencyId, - ApplicationVersion RequiredVersion + DependentModuleMetadata Dependency ) : ModuleIssueV2(Module) { - public override string ToString() => $"Missing '{DependencyId}' with required version {RequiredVersion}"; + public override string ToString() => $"Missing '{Dependency.Id}' with required version {Dependency.Version}"; public override ModuleIssue ToLegacy() => new( Module, - DependencyId, + Dependency.Id, ModuleIssueType.MissingDependencies, ToString(), - new ApplicationVersionRange(RequiredVersion, RequiredVersion)); + new ApplicationVersionRange(Dependency.Version, Dependency.Version)); } /// /// Represents an issue where a required dependency module is missing and a version range was specified /// /// The module with the missing dependency -/// The ID of the missing dependency module -/// The version range that was required +/// The missing dependency module /// /// This issue occurs when a required dependency with a version range requirement is missing. /// @@ -133,16 +130,15 @@ ApplicationVersion RequiredVersion /// public sealed record ModuleMissingVersionRangeDependencyIssue( ModuleInfoExtended Module, - string DependencyId, - ApplicationVersionRange RequiredVersionRange + DependentModuleMetadata Dependency ) : ModuleIssueV2(Module) { - public override string ToString() => $"Missing '{DependencyId}' with required version range [{RequiredVersionRange}]"; + public override string ToString() => $"Missing '{Dependency.Id}' with required version range [{Dependency.VersionRange}]"; public override ModuleIssue ToLegacy() => new(Module, - DependencyId, + Dependency.Id, ModuleIssueType.MissingDependencies, ToString(), - RequiredVersionRange); + Dependency.VersionRange); } /// @@ -217,10 +213,10 @@ string DependencyId /// This serves as an abstract base for both specific version and version range issues. /// /// The module with the version mismatch -/// The ID of the dependency module with mismatched version +/// The dependency module with mismatched version public abstract record ModuleVersionMismatchIssue( ModuleInfoExtended Module, - string DependencyId + ModuleInfoExtended Dependency ) : ModuleIssueV2(Module); /// @@ -228,33 +224,33 @@ string DependencyId /// Used when comparing against exact version numbers rather than ranges. /// /// The module with the version mismatch -/// The ID of the dependency module with mismatched version +/// The dependency module with mismatched version /// The specific version being compared against public abstract record ModuleVersionMismatchSpecificIssue( ModuleInfoExtended Module, - string DependencyId, + ModuleInfoExtended Dependency, ApplicationVersion Version -) : ModuleVersionMismatchIssue(Module, DependencyId); +) : ModuleVersionMismatchIssue(Module, Dependency); /// /// Base record type for version mismatch issues involving version ranges. /// Used when comparing against version ranges rather than specific versions. /// /// The module with the version mismatch -/// The ID of the dependency module with mismatched version +/// The dependency module with mismatched version /// The version range being compared against public abstract record ModuleVersionMismatchRangeIssue( ModuleInfoExtended Module, - string DependencyId, + ModuleInfoExtended Dependency, ApplicationVersionRange VersionRange -) : ModuleVersionMismatchIssue(Module, DependencyId); +) : ModuleVersionMismatchIssue(Module, Dependency); /// /// Represents an issue where a dependency's version is higher than the maximum allowed specific version. /// This occurs when a dependency module's version exceeds an exact version requirement. /// /// The module with the version constraint -/// The ID of the dependency module that exceeds the version requirement +/// The dependency module that exceeds the version requirement /// The specific version that should not be exceeded /// /// This issue occurs when a module specifies incompatible versions. @@ -277,12 +273,14 @@ ApplicationVersionRange VersionRange /// public sealed record ModuleVersionMismatchLessThanOrEqualSpecificIssue( ModuleInfoExtended Module, - string DependencyId, + ModuleInfoExtended Dependency, ApplicationVersion Version -) : ModuleVersionMismatchSpecificIssue(Module, DependencyId, Version) +) : ModuleVersionMismatchSpecificIssue(Module, Dependency, Version) { - public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' has wrong version (required <= {Version})"; - public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThanOrEqual, ToString(), new ApplicationVersionRange(Version, Version)); + public override string ToString() => + $"The module '{Module.Id}' requires version {Version} or lower of '{Dependency.Id}', but version {Dependency.Version} is installed"; + + public override ModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchLessThanOrEqual, ToString(), new ApplicationVersionRange(Version, Version)); } /// @@ -290,7 +288,7 @@ ApplicationVersion Version /// This occurs when a dependency module's version is below the minimum version specified in a range. /// /// The module with the version requirement -/// The ID of the dependency module that doesn't meet the minimum version +/// The dependency module that doesn't meet the minimum version /// The version range containing the minimum version requirement /// /// This issue occurs when a dependency's version is below the minimum required version range. @@ -312,12 +310,14 @@ ApplicationVersion Version /// public sealed record ModuleVersionMismatchLessThanRangeIssue( ModuleInfoExtended Module, - string DependencyId, + ModuleInfoExtended Dependency, ApplicationVersionRange VersionRange -) : ModuleVersionMismatchRangeIssue(Module, DependencyId, VersionRange) +) : ModuleVersionMismatchRangeIssue(Module, Dependency, VersionRange) { - public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' version is below minimum required range ($version < [{VersionRange}])"; - public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchLessThan, ToString(), VersionRange); + public override string ToString() => + $"The module '{Module.Id}' requires '{Dependency.Id}' version {VersionRange}, but version {Dependency.Version} is installed (below minimum)"; + + public override ModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchLessThan, ToString(), VersionRange); } /// @@ -325,7 +325,7 @@ ApplicationVersionRange VersionRange /// This occurs when a dependency module's version is above the maximum version specified in a range. /// /// The module with the version constraint -/// The ID of the dependency module that exceeds the version limit +/// The dependency module that exceeds the version limit /// The version range containing the maximum version requirement /// /// This issue occurs when a dependency's version exceeds the maximum allowed version range. @@ -347,12 +347,13 @@ ApplicationVersionRange VersionRange /// public sealed record ModuleVersionMismatchGreaterThanRangeIssue( ModuleInfoExtended Module, - string DependencyId, + ModuleInfoExtended Dependency, ApplicationVersionRange VersionRange -) : ModuleVersionMismatchRangeIssue(Module, DependencyId, VersionRange) +) : ModuleVersionMismatchRangeIssue(Module, Dependency, VersionRange) { - public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' version exceeds maximum allowed range ($version > [{VersionRange}])"; - public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.VersionMismatchGreaterThan, ToString(), VersionRange); + public override string ToString() => + $"The module '{Module.Id}' requires '{Dependency.Id}' version {VersionRange}, but version {Dependency.Version} is installed (above maximum)"; + public override ModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchGreaterThan, ToString(), VersionRange); } /// @@ -427,7 +428,7 @@ string ConflictingModuleId /// This indicates a contradictory load order configuration that cannot be satisfied. /// /// The module with the conflicting load order declaration -/// The ID of the module that has conflicting load order requirements +/// The module that has conflicting load order requirements /// /// This issue occurs when a module has conflicting load order requirements. /// @@ -449,11 +450,11 @@ string ConflictingModuleId /// public sealed record ModuleDependencyConflictLoadBeforeAndAfterIssue( ModuleInfoExtended Module, - string ConflictingModuleId + DependentModuleMetadata ConflictingModule ) : ModuleIssueV2(Module) { - public override string ToString() => $"Module '{Module.Id}' has conflicting load order requirements with '{ConflictingModuleId}' (both LoadBefore and LoadAfter)"; - public override ModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter, ToString(), ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}' has conflicting load order requirements with '{ConflictingModule.Id}' (both LoadBefore and LoadAfter)"; + public override ModuleIssue ToLegacy() => new(Module, ConflictingModule.Id, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter, ToString(), ApplicationVersionRange.Empty); } /// @@ -461,7 +462,7 @@ string ConflictingModuleId /// This occurs when two or more modules form a dependency cycle that cannot be resolved. /// /// One of the modules in the circular dependency chain -/// The ID of another module in the circular dependency chain +/// The other module in the circular dependency chain /// /// This issue occurs when modules create a circular dependency chain. /// @@ -494,11 +495,11 @@ string ConflictingModuleId /// public sealed record ModuleDependencyConflictCircularIssue( ModuleInfoExtended Module, - string CircularDependencyId + DependentModuleMetadata CircularDependency ) : ModuleIssueV2(Module) { - public override string ToString() => $"Module '{Module.Id}' and '{CircularDependencyId}' have circular dependencies"; - public override ModuleIssue ToLegacy() => new(Module, CircularDependencyId, ModuleIssueType.DependencyConflictCircular, ToString(), ApplicationVersionRange.Empty); + public override string ToString() => $"Module '{Module.Id}' and '{CircularDependency.Id}' have circular dependencies"; + public override ModuleIssue ToLegacy() => new(Module, CircularDependency.Id, ModuleIssueType.DependencyConflictCircular, ToString(), ApplicationVersionRange.Empty); } /// diff --git a/src/Bannerlord.ModuleManager/ModuleUtilities.cs b/src/Bannerlord.ModuleManager/ModuleUtilities.cs index c646c6c..32674b5 100644 --- a/src/Bannerlord.ModuleManager/ModuleUtilities.cs +++ b/src/Bannerlord.ModuleManager/ModuleUtilities.cs @@ -388,7 +388,7 @@ public static IEnumerable ValidateModuleDependenciesDeclarationsE if (targetModule.DependenciesLoadAfterThisDistinct() .FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)) is { } metadata) { - yield return new ModuleDependencyConflictLoadBeforeAndAfterIssue(targetModule, metadata.Id); + yield return new ModuleDependencyConflictLoadBeforeAndAfterIssue(targetModule, metadata); } } @@ -401,7 +401,7 @@ public static IEnumerable ValidateModuleDependenciesDeclarationsE .FirstOrDefault(x => string.Equals(x.Id, targetModule.Id, StringComparison.Ordinal)) is { } metadata) { if (metadata.LoadType == module.LoadType) - yield return new ModuleDependencyConflictCircularIssue(targetModule, metadata.Id); + yield return new ModuleDependencyConflictCircularIssue(targetModule, metadata); } } } @@ -433,25 +433,11 @@ private static IEnumerable ValidateModuleDependenciesEx( if (!modules.Any(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal))) { if (metadata.Version != ApplicationVersion.Empty) - { - yield return new ModuleMissingExactVersionDependencyIssue( - targetModule, - metadata.Id, - metadata.Version); - } + yield return new ModuleMissingExactVersionDependencyIssue(targetModule, metadata); else if (metadata.VersionRange != ApplicationVersionRange.Empty) - { - yield return new ModuleMissingVersionRangeDependencyIssue( - targetModule, - metadata.Id, - metadata.VersionRange); - } + yield return new ModuleMissingVersionRangeDependencyIssue(targetModule, metadata); else - { - yield return new ModuleMissingUnversionedDependencyIssue( - targetModule, - metadata.Id); - } + yield return new ModuleMissingUnversionedDependencyIssue(targetModule, metadata); yield break; } } @@ -507,7 +493,7 @@ private static IEnumerable ValidateModuleDependenciesEx( { yield return new ModuleVersionMismatchLessThanOrEqualSpecificIssue( targetModule, - metadataModule.Id, + metadataModule, metadata.Version); continue; } @@ -521,7 +507,7 @@ private static IEnumerable ValidateModuleDependenciesEx( { yield return new ModuleVersionMismatchLessThanRangeIssue( targetModule, - metadataModule.Id, + metadataModule, metadata.VersionRange); continue; } @@ -529,7 +515,7 @@ private static IEnumerable ValidateModuleDependenciesEx( { yield return new ModuleVersionMismatchGreaterThanRangeIssue( targetModule, - metadataModule.Id, + metadataModule, metadata.VersionRange); continue; } @@ -622,25 +608,11 @@ public static IEnumerable ValidateLoadOrderEx( if (!metadata.IsOptional) { if (metadata.Version != ApplicationVersion.Empty) - { - yield return new ModuleMissingExactVersionDependencyIssue( - targetModule, - metadata.Id, - metadata.Version); - } + yield return new ModuleMissingExactVersionDependencyIssue(targetModule, metadata); else if (metadata.VersionRange != ApplicationVersionRange.Empty) - { - yield return new ModuleMissingVersionRangeDependencyIssue( - targetModule, - metadata.Id, - metadata.VersionRange); - } + yield return new ModuleMissingVersionRangeDependencyIssue(targetModule, metadata); else - { - yield return new ModuleMissingUnversionedDependencyIssue( - targetModule, - metadata.Id); - } + yield return new ModuleMissingUnversionedDependencyIssue(targetModule, metadata); } continue; } From 47de52a7a913f36ea3a08f9bc9d511213c8b4e2d Mon Sep 17 00:00:00 2001 From: Vitalii Mikhailov Date: Tue, 12 Nov 2024 22:48:00 +0200 Subject: [PATCH 5/5] Dropped packages.config support Dropped IsExternalInit polyfill, replaced with IsExternalInit package Adapted code to support the source package Updated version to 6 Lil Any() optimization --- build/common.props | 2 +- .../ApplicationVersion.cs | 228 ++-- .../ApplicationVersionComparer.cs | 78 +- .../ApplicationVersionRange.cs | 96 +- .../ApplicationVersionType.cs | 40 +- .../Bannerlord.ModuleManager.Models.csproj | 1 + .../DependentModule.cs | 30 +- .../DependentModuleMetadata.cs | 84 +- .../Issues/ModuleIssueV2.cs | 232 +++- .../LoadType.cs | 44 +- .../Misc/Polyfills.cs | 7 - .../ModuleInfoExtended.cs | 430 +++--- .../ModuleIssue.cs | 80 +- .../ModuleIssueType.cs | 66 +- .../SubModuleInfoExtended.cs | 152 +-- .../SubModuleTags.cs | 42 +- .../Bannerlord.ModuleManager.Source.csproj | 5 +- .../ModuleStorageTests.cs | 2 +- .../AlphanumComparatorFast.cs | 186 ++- .../CollectionsExtensions.cs | 94 +- .../ModuleInfoExtendedExtensions.cs | 233 ++-- src/Bannerlord.ModuleManager/ModuleSorter.cs | 128 +- .../ModuleUtilities.cs | 1184 ++++++++--------- 23 files changed, 1676 insertions(+), 1768 deletions(-) delete mode 100644 src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs diff --git a/build/common.props b/build/common.props index 6ae030e..e6f063b 100644 --- a/build/common.props +++ b/build/common.props @@ -3,7 +3,7 @@ 0 - 5.0.$(VersionOverride) + 6.0.$(VersionOverride) diff --git a/src/Bannerlord.ModuleManager.Models/ApplicationVersion.cs b/src/Bannerlord.ModuleManager.Models/ApplicationVersion.cs index ff42910..2eb85b3 100644 --- a/src/Bannerlord.ModuleManager.Models/ApplicationVersion.cs +++ b/src/Bannerlord.ModuleManager.Models/ApplicationVersion.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ApplicationVersion.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,133 +27,133 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; +namespace Bannerlord.ModuleManager; + +using System; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - record ApplicationVersion : IComparable + record ApplicationVersion : IComparable +{ + public static ApplicationVersion Empty { get; } = new(); + + public ApplicationVersionType ApplicationVersionType { get; set; } + public int Major { get; set; } + public int Minor { get; set; } + public int Revision { get; set; } + public int ChangeSet { get; set; } + + public ApplicationVersion() { } + public ApplicationVersion(ApplicationVersionType applicationVersionType, int major, int minor, int revision, int changeSet) { - public static ApplicationVersion Empty { get; } = new(); + ApplicationVersionType = applicationVersionType; + Major = major; + Minor = minor; + Revision = revision; + ChangeSet = changeSet; + } - public ApplicationVersionType ApplicationVersionType { get; set; } - public int Major { get; set; } - public int Minor { get; set; } - public int Revision { get; set; } - public int ChangeSet { get; set; } + public bool IsSame(ApplicationVersion? other) => + Major == other?.Major && Minor == other.Minor && Revision == other.Revision; - public ApplicationVersion() { } - public ApplicationVersion(ApplicationVersionType applicationVersionType, int major, int minor, int revision, int changeSet) - { - ApplicationVersionType = applicationVersionType; - Major = major; - Minor = minor; - Revision = revision; - ChangeSet = changeSet; - } + public bool IsSameWithChangeSet(ApplicationVersion? other) => + Major == other?.Major && Minor == other.Minor && Revision == other.Revision && ChangeSet == other.ChangeSet; - public bool IsSame(ApplicationVersion? other) => - Major == other?.Major && Minor == other.Minor && Revision == other.Revision; + public override string ToString() => $"{GetPrefix(ApplicationVersionType)}{Major}.{Minor}.{Revision}.{ChangeSet}"; - public bool IsSameWithChangeSet(ApplicationVersion? other) => - Major == other?.Major && Minor == other.Minor && Revision == other.Revision && ChangeSet == other.ChangeSet; + public int CompareTo(ApplicationVersion? other) => ApplicationVersionComparer.CompareStandard(this, other); - public override string ToString() => $"{GetPrefix(ApplicationVersionType)}{Major}.{Minor}.{Revision}.{ChangeSet}"; + public static bool operator <(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) < 0; + public static bool operator >(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) > 0; + public static bool operator <=(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) <= 0; + public static bool operator >=(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) >= 0; - public int CompareTo(ApplicationVersion? other) => ApplicationVersionComparer.CompareStandard(this, other); + public static char GetPrefix(ApplicationVersionType applicationVersionType) => applicationVersionType switch + { + ApplicationVersionType.Alpha => 'a', + ApplicationVersionType.Beta => 'b', + ApplicationVersionType.EarlyAccess => 'e', + ApplicationVersionType.Release => 'v', + ApplicationVersionType.Development => 'd', + _ => 'i' + }; + + public static ApplicationVersionType FromPrefix(char applicationVersionType) => applicationVersionType switch + { + 'a' => ApplicationVersionType.Alpha, + 'b' => ApplicationVersionType.Beta, + 'e' => ApplicationVersionType.EarlyAccess, + 'v' => ApplicationVersionType.Release, + 'd' => ApplicationVersionType.Development, + _ => ApplicationVersionType.Invalid + }; - public static bool operator <(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) < 0; - public static bool operator >(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) > 0; - public static bool operator <=(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) <= 0; - public static bool operator >=(ApplicationVersion left, ApplicationVersion right) => left.CompareTo(right) >= 0; + public static bool TryParse(string? versionAsString, out ApplicationVersion version) => TryParse(versionAsString, out version, true); - public static char GetPrefix(ApplicationVersionType applicationVersionType) => applicationVersionType switch + public static bool TryParse(string? versionAsString, out ApplicationVersion version, bool asMin) + { + var major = asMin ? 0 : int.MaxValue; + var minor = asMin ? 0 : int.MaxValue; + var revision = asMin ? 0 : int.MaxValue; + var changeSet = asMin ? 0 : int.MaxValue; + var skipCheck = false; + version = Empty; + if (versionAsString is null) + return false; + + var array = versionAsString.Split('.'); + if (array.Length != 3 && array.Length != 4 && array[0].Length == 0) + return false; + + var applicationVersionType = FromPrefix(array[0][0]); + if (!skipCheck && !int.TryParse(array[0].Substring(1), out major)) { - ApplicationVersionType.Alpha => 'a', - ApplicationVersionType.Beta => 'b', - ApplicationVersionType.EarlyAccess => 'e', - ApplicationVersionType.Release => 'v', - ApplicationVersionType.Development => 'd', - _ => 'i' - }; - - public static ApplicationVersionType FromPrefix(char applicationVersionType) => applicationVersionType switch + if (array[0].Substring(1) != "*") return false; + major = int.MinValue; + minor = int.MinValue; + revision = int.MinValue; + changeSet = int.MinValue; + skipCheck = true; + } + if (!skipCheck && !int.TryParse(array[1], out minor)) { - 'a' => ApplicationVersionType.Alpha, - 'b' => ApplicationVersionType.Beta, - 'e' => ApplicationVersionType.EarlyAccess, - 'v' => ApplicationVersionType.Release, - 'd' => ApplicationVersionType.Development, - _ => ApplicationVersionType.Invalid - }; - - public static bool TryParse(string? versionAsString, out ApplicationVersion version) => TryParse(versionAsString, out version, true); + if (array[1] != "*") return false; + minor = asMin ? 0 : int.MaxValue; + revision = asMin ? 0 : int.MaxValue; + changeSet = asMin ? 0 : int.MaxValue; + skipCheck = true; + } + if (!skipCheck && !int.TryParse(array[2], out revision)) + { + if (array[2] != "*") return false; + revision = asMin ? 0 : int.MaxValue; + changeSet = asMin ? 0 : int.MaxValue; + skipCheck = true; + } - public static bool TryParse(string? versionAsString, out ApplicationVersion version, bool asMin) + if (!skipCheck && array.Length == 4 && !int.TryParse(array[3], out changeSet)) { - var major = asMin ? 0 : int.MaxValue; - var minor = asMin ? 0 : int.MaxValue; - var revision = asMin ? 0 : int.MaxValue; - var changeSet = asMin ? 0 : int.MaxValue; - var skipCheck = false; - version = Empty; - if (versionAsString is null) - return false; - - var array = versionAsString.Split('.'); - if (array.Length != 3 && array.Length != 4 && array[0].Length == 0) - return false; - - var applicationVersionType = FromPrefix(array[0][0]); - if (!skipCheck && !int.TryParse(array[0].Substring(1), out major)) - { - if (array[0].Substring(1) != "*") return false; - major = int.MinValue; - minor = int.MinValue; - revision = int.MinValue; - changeSet = int.MinValue; - skipCheck = true; - } - if (!skipCheck && !int.TryParse(array[1], out minor)) - { - if (array[1] != "*") return false; - minor = asMin ? 0 : int.MaxValue; - revision = asMin ? 0 : int.MaxValue; - changeSet = asMin ? 0 : int.MaxValue; - skipCheck = true; - } - if (!skipCheck && !int.TryParse(array[2], out revision)) - { - if (array[2] != "*") return false; - revision = asMin ? 0 : int.MaxValue; - changeSet = asMin ? 0 : int.MaxValue; - skipCheck = true; - } - - if (!skipCheck && array.Length == 4 && !int.TryParse(array[3], out changeSet)) - { - if (array[3] != "*") return false; - changeSet = asMin ? 0 : int.MaxValue; - skipCheck = true; - } - - version = new ApplicationVersion - { - ApplicationVersionType = applicationVersionType, - Major = major, - Minor = minor, - Revision = revision, - ChangeSet = changeSet - }; - - return true; + if (array[3] != "*") return false; + changeSet = asMin ? 0 : int.MaxValue; + skipCheck = true; } + + version = new ApplicationVersion + { + ApplicationVersionType = applicationVersionType, + Major = major, + Minor = minor, + Revision = revision, + ChangeSet = changeSet + }; + + return true; } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/ApplicationVersionComparer.cs b/src/Bannerlord.ModuleManager.Models/ApplicationVersionComparer.cs index 231efb4..1f94e19 100644 --- a/src/Bannerlord.ModuleManager.Models/ApplicationVersionComparer.cs +++ b/src/Bannerlord.ModuleManager.Models/ApplicationVersionComparer.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ApplicationVersionComparer.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,54 +27,54 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System.Collections; - using global::System.Collections.Generic; +namespace Bannerlord.ModuleManager; + +using System.Collections; +using System.Collections.Generic; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - class ApplicationVersionComparer : IComparer, IComparer - { - /// - public int Compare(object? x, object? y) => Compare(x as ApplicationVersion, y as ApplicationVersion); + class ApplicationVersionComparer : IComparer, IComparer +{ + /// + public int Compare(object? x, object? y) => Compare(x as ApplicationVersion, y as ApplicationVersion); - /// - public virtual int Compare(ApplicationVersion? x, ApplicationVersion? y) => CompareStandard(x, y); + /// + public virtual int Compare(ApplicationVersion? x, ApplicationVersion? y) => CompareStandard(x, y); - public static int CompareStandard(ApplicationVersion? x, ApplicationVersion? y) - { - if (x is null && y is null) - return 0; + public static int CompareStandard(ApplicationVersion? x, ApplicationVersion? y) + { + if (x is null && y is null) + return 0; - if (x is null) - return -1; + if (x is null) + return -1; - if (y is null) - return 1; + if (y is null) + return 1; - var versionTypeComparison = x.ApplicationVersionType.CompareTo(y.ApplicationVersionType); - if (versionTypeComparison != 0) return versionTypeComparison; + var versionTypeComparison = x.ApplicationVersionType.CompareTo(y.ApplicationVersionType); + if (versionTypeComparison != 0) return versionTypeComparison; - var majorComparison = x.Major.CompareTo(y.Major); - if (majorComparison != 0) return majorComparison; + var majorComparison = x.Major.CompareTo(y.Major); + if (majorComparison != 0) return majorComparison; - var minorComparison = x.Minor.CompareTo(y.Minor); - if (minorComparison != 0) return minorComparison; + var minorComparison = x.Minor.CompareTo(y.Minor); + if (minorComparison != 0) return minorComparison; - var revisionComparison = x.Revision.CompareTo(y.Revision); - if (revisionComparison != 0) return revisionComparison; + var revisionComparison = x.Revision.CompareTo(y.Revision); + if (revisionComparison != 0) return revisionComparison; - var changeSetComparison = x.ChangeSet.CompareTo(y.ChangeSet); - if (changeSetComparison != 0) return changeSetComparison; + var changeSetComparison = x.ChangeSet.CompareTo(y.ChangeSet); + if (changeSetComparison != 0) return changeSetComparison; - return 0; - } + return 0; } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/ApplicationVersionRange.cs b/src/Bannerlord.ModuleManager.Models/ApplicationVersionRange.cs index 483d656..04185b2 100644 --- a/src/Bannerlord.ModuleManager.Models/ApplicationVersionRange.cs +++ b/src/Bannerlord.ModuleManager.Models/ApplicationVersionRange.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ApplicationVersionRange.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,64 +27,64 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ +namespace Bannerlord.ModuleManager; + #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - record ApplicationVersionRange - { - public static ApplicationVersionRange Empty => new(); + record ApplicationVersionRange +{ + public static ApplicationVersionRange Empty => new(); - public ApplicationVersion Min { get; set; } = ApplicationVersion.Empty; - public ApplicationVersion Max { get; set; } = ApplicationVersion.Empty; + public ApplicationVersion Min { get; set; } = ApplicationVersion.Empty; + public ApplicationVersion Max { get; set; } = ApplicationVersion.Empty; - public ApplicationVersionRange() { } - public ApplicationVersionRange(ApplicationVersion min, ApplicationVersion max) - { - Max = max; - Min = min; - } + public ApplicationVersionRange() { } + public ApplicationVersionRange(ApplicationVersion min, ApplicationVersion max) + { + Max = max; + Min = min; + } - public bool IsSame(ApplicationVersionRange? other) => - Min.IsSame(other?.Min) && Max.IsSame(other?.Max); + public bool IsSame(ApplicationVersionRange? other) => + Min.IsSame(other?.Min) && Max.IsSame(other?.Max); - public bool IsSameWithChangeSet(ApplicationVersionRange? other) => - Min.IsSameWithChangeSet(other?.Min) && Max.IsSameWithChangeSet(other?.Max); + public bool IsSameWithChangeSet(ApplicationVersionRange? other) => + Min.IsSameWithChangeSet(other?.Min) && Max.IsSameWithChangeSet(other?.Max); - public override string ToString() => $"{Min} - {Max}"; + public override string ToString() => $"{Min} - {Max}"; - public static bool TryParse(string versionRangeAsString, out ApplicationVersionRange versionRange) - { - versionRange = Empty; + public static bool TryParse(string versionRangeAsString, out ApplicationVersionRange versionRange) + { + versionRange = Empty; - if (string.IsNullOrWhiteSpace(versionRangeAsString)) - return false; + if (string.IsNullOrWhiteSpace(versionRangeAsString)) + return false; - versionRangeAsString = versionRangeAsString.Replace(" ", string.Empty); - var index = versionRangeAsString.IndexOf('-'); - if (index < 0) - return false; + versionRangeAsString = versionRangeAsString.Replace(" ", string.Empty); + var index = versionRangeAsString.IndexOf('-'); + if (index < 0) + return false; - var minAsString = versionRangeAsString.Substring(0, index); - var maxAsString = versionRangeAsString.Substring(index + 1, versionRangeAsString.Length - 1 - index); + var minAsString = versionRangeAsString.Substring(0, index); + var maxAsString = versionRangeAsString.Substring(index + 1, versionRangeAsString.Length - 1 - index); - if (ApplicationVersion.TryParse(minAsString, out var min, true) && ApplicationVersion.TryParse(maxAsString, out var max, false)) + if (ApplicationVersion.TryParse(minAsString, out var min, true) && ApplicationVersion.TryParse(maxAsString, out var max, false)) + { + versionRange = new ApplicationVersionRange { - versionRange = new ApplicationVersionRange - { - Min = min, - Max = max - }; - return true; - } - - return false; + Min = min, + Max = max + }; + return true; } + + return false; } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/ApplicationVersionType.cs b/src/Bannerlord.ModuleManager.Models/ApplicationVersionType.cs index de0e38f..454f6f2 100644 --- a/src/Bannerlord.ModuleManager.Models/ApplicationVersionType.cs +++ b/src/Bannerlord.ModuleManager.Models/ApplicationVersionType.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ApplicationVersionType.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,23 +27,23 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ +namespace Bannerlord.ModuleManager; + #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - enum ApplicationVersionType - { - Alpha, - Beta, - EarlyAccess, - Release, - Development, - Invalid - } + enum ApplicationVersionType +{ + Alpha, + Beta, + EarlyAccess, + Release, + Development, + Invalid } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/Bannerlord.ModuleManager.Models.csproj b/src/Bannerlord.ModuleManager.Models/Bannerlord.ModuleManager.Models.csproj index a53442e..bed91c1 100644 --- a/src/Bannerlord.ModuleManager.Models/Bannerlord.ModuleManager.Models.csproj +++ b/src/Bannerlord.ModuleManager.Models/Bannerlord.ModuleManager.Models.csproj @@ -31,6 +31,7 @@ + \ No newline at end of file diff --git a/src/Bannerlord.ModuleManager.Models/DependentModule.cs b/src/Bannerlord.ModuleManager.Models/DependentModule.cs index a11f717..c53fd32 100644 --- a/src/Bannerlord.ModuleManager.Models/DependentModule.cs +++ b/src/Bannerlord.ModuleManager.Models/DependentModule.cs @@ -41,28 +41,28 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ +namespace Bannerlord.ModuleManager; + #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - record DependentModule - { - public string Id { get; set; } = string.Empty; - public ApplicationVersion Version { get; set; } = ApplicationVersion.Empty; - public bool IsOptional { get; set; } = false; + record DependentModule +{ + public string Id { get; set; } = string.Empty; + public ApplicationVersion Version { get; set; } = ApplicationVersion.Empty; + public bool IsOptional { get; set; } = false; - public DependentModule() { } - public DependentModule(string id, ApplicationVersion version, bool isOptional) - { - Id = id; - Version = version; - IsOptional = isOptional; - } + public DependentModule() { } + public DependentModule(string id, ApplicationVersion version, bool isOptional) + { + Id = id; + Version = version; + IsOptional = isOptional; } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/DependentModuleMetadata.cs b/src/Bannerlord.ModuleManager.Models/DependentModuleMetadata.cs index dbd887a..e7174b9 100644 --- a/src/Bannerlord.ModuleManager.Models/DependentModuleMetadata.cs +++ b/src/Bannerlord.ModuleManager.Models/DependentModuleMetadata.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "DependentModuleMetadata.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,47 +27,47 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ +namespace Bannerlord.ModuleManager; + #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - record DependentModuleMetadata - { - public string Id { get; set; } = string.Empty; - public LoadType LoadType { get; set; } - public bool IsOptional { get; set; } - public bool IsIncompatible { get; set; } - public ApplicationVersion Version { get; set; } = ApplicationVersion.Empty; - public ApplicationVersionRange VersionRange { get; set; } = ApplicationVersionRange.Empty; - - public DependentModuleMetadata() { } - public DependentModuleMetadata(string id, LoadType loadType, bool isOptional, bool isIncompatible, ApplicationVersion version, ApplicationVersionRange versionRange) - { - Id = id; - LoadType = loadType; - IsOptional = isOptional; - IsIncompatible = isIncompatible; - Version = version; - VersionRange = versionRange; - } + record DependentModuleMetadata +{ + public string Id { get; set; } = string.Empty; + public LoadType LoadType { get; set; } + public bool IsOptional { get; set; } + public bool IsIncompatible { get; set; } + public ApplicationVersion Version { get; set; } = ApplicationVersion.Empty; + public ApplicationVersionRange VersionRange { get; set; } = ApplicationVersionRange.Empty; - public static string GetLoadType(LoadType loadType) => loadType switch - { - LoadType.None => "", - LoadType.LoadAfterThis => "Before ", - LoadType.LoadBeforeThis => "After ", - _ => "ERROR " - }; - public static string GetVersion(ApplicationVersion? av) => av?.IsSameWithChangeSet(ApplicationVersion.Empty) == true ? "" : $" {av}"; - public static string GetVersionRange(ApplicationVersionRange? avr) => avr == ApplicationVersionRange.Empty ? "" : $" {avr}"; - public static string GetOptional(bool isOptional) => isOptional ? " Optional" : ""; - public static string GetIncompatible(bool isOptional) => isOptional ? "Incompatible " : ""; - public override string ToString() => GetLoadType(LoadType) + GetIncompatible(IsIncompatible) + Id + GetVersion(Version) + GetVersionRange(VersionRange) + GetOptional(IsOptional); + public DependentModuleMetadata() { } + public DependentModuleMetadata(string id, LoadType loadType, bool isOptional, bool isIncompatible, ApplicationVersion version, ApplicationVersionRange versionRange) + { + Id = id; + LoadType = loadType; + IsOptional = isOptional; + IsIncompatible = isIncompatible; + Version = version; + VersionRange = versionRange; } + + public static string GetLoadType(LoadType loadType) => loadType switch + { + LoadType.None => "", + LoadType.LoadAfterThis => "Before ", + LoadType.LoadBeforeThis => "After ", + _ => "ERROR " + }; + public static string GetVersion(ApplicationVersion? av) => av?.IsSameWithChangeSet(ApplicationVersion.Empty) == true ? "" : $" {av}"; + public static string GetVersionRange(ApplicationVersionRange? avr) => avr == ApplicationVersionRange.Empty ? "" : $" {avr}"; + public static string GetOptional(bool isOptional) => isOptional ? " Optional" : ""; + public static string GetIncompatible(bool isOptional) => isOptional ? "Incompatible " : ""; + public override string ToString() => GetLoadType(LoadType) + GetIncompatible(IsIncompatible) + Id + GetVersion(Version) + GetVersionRange(VersionRange) + GetOptional(IsOptional); } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs index 0ba2278..d2f08bb 100644 --- a/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs +++ b/src/Bannerlord.ModuleManager.Models/Issues/ModuleIssueV2.cs @@ -1,15 +1,50 @@ -using LegacyModuleIssue = Bannerlord.ModuleManager.ModuleIssue; +#region License +// MIT License +// +// Copyright (c) Bannerlord's Unofficial Tools & Resources +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion + +#nullable enable +#if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING +#pragma warning disable +#endif + // ReSharper disable MemberCanBePrivate.Global namespace Bannerlord.ModuleManager.Models.Issues; +using LegacyModuleIssue = ModuleIssue; + /// /// Base record type for all module-related issues that can occur during validation. /// This record serves as the abstract base class for all specific issue variants, /// providing a common structure and conversion capability to legacy formats. /// /// The module in which the issue was detected. This is always required. -public abstract record ModuleIssueV2(ModuleInfoExtended Module) +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + abstract record ModuleIssueV2(ModuleInfoExtended Module) { /// /// Converts this issue instance to the legacy ModuleIssue format for backwards compatibility @@ -25,13 +60,18 @@ public abstract record ModuleIssueV2(ModuleInfoExtended Module) /// /// The module that was found to be missing /// The version range in which the module should exist -public sealed record ModuleMissingIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleMissingIssue( ModuleInfoExtended Module, ApplicationVersionRange SourceVersion ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}' {Module.Version} is missing from modules list"; - public override ModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.Missing, ToString(), SourceVersion); + public override LegacyModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.Missing, ToString(), SourceVersion); } /// @@ -56,13 +96,18 @@ ApplicationVersionRange SourceVersion /// If `TournamentOverhaul` is not installed at all, this issue will be raised if `SimpleTournaments` is enabled. /// Note that it's recommended to use `DependedModuleMetadatas` with version specifications instead. /// -public sealed record ModuleMissingUnversionedDependencyIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleMissingUnversionedDependencyIssue( ModuleInfoExtended Module, DependentModuleMetadata Dependency ) : ModuleIssueV2(Module) { public override string ToString() => $"Missing '{Dependency.Id}' module"; - public override ModuleIssue ToLegacy() => new( + public override LegacyModuleIssue ToLegacy() => new( Module, Dependency.Id, ModuleIssueType.MissingDependencies, @@ -92,13 +137,18 @@ DependentModuleMetadata Dependency /// If `Bannerlord.UIExtenderEx` is not installed at all (any version), this issue will be raised. /// This is different from version mismatch issues where the module is present but with wrong version. /// -public sealed record ModuleMissingExactVersionDependencyIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleMissingExactVersionDependencyIssue( ModuleInfoExtended Module, DependentModuleMetadata Dependency ) : ModuleIssueV2(Module) { public override string ToString() => $"Missing '{Dependency.Id}' with required version {Dependency.Version}"; - public override ModuleIssue ToLegacy() => new( + public override LegacyModuleIssue ToLegacy() => new( Module, Dependency.Id, ModuleIssueType.MissingDependencies, @@ -128,13 +178,18 @@ DependentModuleMetadata Dependency /// If `Bannerlord.UIExtenderEx` module is not installed at all (any version), this issue will be raised. /// This is different from version mismatch issues where the module is present but with wrong version. /// -public sealed record ModuleMissingVersionRangeDependencyIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleMissingVersionRangeDependencyIssue( ModuleInfoExtended Module, DependentModuleMetadata Dependency ) : ModuleIssueV2(Module) { public override string ToString() => $"Missing '{Dependency.Id}' with required version range [{Dependency.VersionRange}]"; - public override ModuleIssue ToLegacy() => new(Module, + public override LegacyModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.MissingDependencies, ToString(), @@ -166,13 +221,18 @@ DependentModuleMetadata Dependency /// If `BetterTime` requires `Harmony` but `Harmony` is not installed, this issue will be raised for `RealisticWeather` /// because its dependency (`BetterTime`) has missing dependencies. /// -public sealed record ModuleDependencyMissingDependenciesIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyMissingDependenciesIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}': Required dependency '{DependencyId}' is missing its own dependencies"; - public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyMissingDependencies, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyMissingDependencies, ToString(), ApplicationVersionRange.Empty); } /// @@ -199,13 +259,18 @@ string DependencyId /// If `CustomSpawnsFramework` has its own issues (like missing dependencies or invalid configuration), /// this issue will be raised for `CustomSpawns` since its dependency needs to be fixed first. /// -public sealed record ModuleDependencyValidationIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyValidationIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}': Dependency '{DependencyId}' has unresolved validation issues"; - public override ModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyValidationError, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, DependencyId, ModuleIssueType.DependencyValidationError, ToString(), ApplicationVersionRange.Empty); } /// @@ -214,7 +279,12 @@ string DependencyId /// /// The module with the version mismatch /// The dependency module with mismatched version -public abstract record ModuleVersionMismatchIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + abstract record ModuleVersionMismatchIssue( ModuleInfoExtended Module, ModuleInfoExtended Dependency ) : ModuleIssueV2(Module); @@ -226,7 +296,12 @@ ModuleInfoExtended Dependency /// The module with the version mismatch /// The dependency module with mismatched version /// The specific version being compared against -public abstract record ModuleVersionMismatchSpecificIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + abstract record ModuleVersionMismatchSpecificIssue( ModuleInfoExtended Module, ModuleInfoExtended Dependency, ApplicationVersion Version @@ -239,7 +314,12 @@ ApplicationVersion Version /// The module with the version mismatch /// The dependency module with mismatched version /// The version range being compared against -public abstract record ModuleVersionMismatchRangeIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + abstract record ModuleVersionMismatchRangeIssue( ModuleInfoExtended Module, ModuleInfoExtended Dependency, ApplicationVersionRange VersionRange @@ -271,7 +351,12 @@ ApplicationVersionRange VersionRange /// /// If a higher version of Harmony (e.g., `v2.3.0`) is installed than allowed, this issue will be raised. /// -public sealed record ModuleVersionMismatchLessThanOrEqualSpecificIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleVersionMismatchLessThanOrEqualSpecificIssue( ModuleInfoExtended Module, ModuleInfoExtended Dependency, ApplicationVersion Version @@ -280,7 +365,7 @@ ApplicationVersion Version public override string ToString() => $"The module '{Module.Id}' requires version {Version} or lower of '{Dependency.Id}', but version {Dependency.Version} is installed"; - public override ModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchLessThanOrEqual, ToString(), new ApplicationVersionRange(Version, Version)); + public override LegacyModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchLessThanOrEqual, ToString(), new ApplicationVersionRange(Version, Version)); } /// @@ -308,7 +393,12 @@ public override string ToString() => /// ``` /// If an older version of ButterLib (e.g., v2.8.14) is installed, this issue will be raised. /// -public sealed record ModuleVersionMismatchLessThanRangeIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleVersionMismatchLessThanRangeIssue( ModuleInfoExtended Module, ModuleInfoExtended Dependency, ApplicationVersionRange VersionRange @@ -317,7 +407,7 @@ ApplicationVersionRange VersionRange public override string ToString() => $"The module '{Module.Id}' requires '{Dependency.Id}' version {VersionRange}, but version {Dependency.Version} is installed (below minimum)"; - public override ModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchLessThan, ToString(), VersionRange); + public override LegacyModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchLessThan, ToString(), VersionRange); } /// @@ -345,7 +435,12 @@ public override string ToString() => /// ``` /// If a version of `ModB` that falls within the range is installed, this issue will be raised. /// -public sealed record ModuleVersionMismatchGreaterThanRangeIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleVersionMismatchGreaterThanRangeIssue( ModuleInfoExtended Module, ModuleInfoExtended Dependency, ApplicationVersionRange VersionRange @@ -353,7 +448,7 @@ ApplicationVersionRange VersionRange { public override string ToString() => $"The module '{Module.Id}' requires '{Dependency.Id}' version {VersionRange}, but version {Dependency.Version} is installed (above maximum)"; - public override ModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchGreaterThan, ToString(), VersionRange); + public override LegacyModuleIssue ToLegacy() => new(Module, Dependency.Id, ModuleIssueType.VersionMismatchGreaterThan, ToString(), VersionRange); } /// @@ -378,7 +473,12 @@ public override string ToString() => /// ``` /// If both `AlternativeArmorSystem` is enabled when `RealisticBattleArmor` is already enabled, this issue will be raised. /// -public sealed record ModuleIncompatibleIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleIncompatibleIssue( ModuleInfoExtended Module, string IncompatibleModuleId ) : ModuleIssueV2(Module) @@ -414,13 +514,18 @@ string IncompatibleModuleId /// ``` /// This is a configuration error as `TroopOverhaul` cannot be both required and incompatible. /// -public sealed record ModuleDependencyConflictDependentAndIncompatibleIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyConflictDependentAndIncompatibleIssue( ModuleInfoExtended Module, string ConflictingModuleId ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}' has conflicting configuration: '{ConflictingModuleId}' is marked as both required and incompatible"; - public override ModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentAndIncompatible, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, ConflictingModuleId, ModuleIssueType.DependencyConflictDependentAndIncompatible, ToString(), ApplicationVersionRange.Empty); } /// @@ -448,13 +553,18 @@ string ConflictingModuleId /// ``` /// This creates an impossible load order requirement as `ArenaOverhaul` cannot load both before and after `ImprovedTournaments`. /// -public sealed record ModuleDependencyConflictLoadBeforeAndAfterIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyConflictLoadBeforeAndAfterIssue( ModuleInfoExtended Module, DependentModuleMetadata ConflictingModule ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}' has conflicting load order requirements with '{ConflictingModule.Id}' (both LoadBefore and LoadAfter)"; - public override ModuleIssue ToLegacy() => new(Module, ConflictingModule.Id, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, ConflictingModule.Id, ModuleIssueType.DependencyConflictDependentLoadBeforeAndAfter, ToString(), ApplicationVersionRange.Empty); } /// @@ -493,13 +603,18 @@ DependentModuleMetadata ConflictingModule /// /// ❌ This creates an impossible situation where each module must load before the other. /// -public sealed record ModuleDependencyConflictCircularIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyConflictCircularIssue( ModuleInfoExtended Module, DependentModuleMetadata CircularDependency ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}' and '{CircularDependency.Id}' have circular dependencies"; - public override ModuleIssue ToLegacy() => new(Module, CircularDependency.Id, ModuleIssueType.DependencyConflictCircular, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, CircularDependency.Id, ModuleIssueType.DependencyConflictCircular, ToString(), ApplicationVersionRange.Empty); } /// @@ -526,7 +641,12 @@ DependentModuleMetadata CircularDependency /// ``` /// If `Harmony` is loading after `BetterBattles` in the actual load order, this issue will be raised. /// -public sealed record ModuleDependencyNotLoadedBeforeIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyNotLoadedBeforeIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) @@ -559,7 +679,12 @@ string DependencyId /// ``` /// If `ImprovedGarrisons` is loading before `BetterSettlements`, this issue will be raised. /// -public sealed record ModuleDependencyNotLoadedAfterIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyNotLoadedAfterIssue( ModuleInfoExtended Module, string DependencyId ) : ModuleIssueV2(Module) @@ -588,12 +713,17 @@ string DependencyId /// ``` /// The Id field is required for module identification and dependency management. /// -public sealed record ModuleMissingIdIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleMissingIdIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { public override string ToString() => $"Module with Name '{Module.Name}' is missing its Id field"; - public override ModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.MissingModuleId, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.MissingModuleId, ToString(), ApplicationVersionRange.Empty); } /// @@ -616,12 +746,17 @@ ModuleInfoExtended Module /// ``` /// The Name field is required for module identification and display purposes. /// -public sealed record ModuleMissingNameIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleMissingNameIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { public override string ToString() => $"Module with Id '{Module.Id}' is missing its Name field"; - public override ModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.MissingModuleName, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, Module.Id, ModuleIssueType.MissingModuleName, ToString(), ApplicationVersionRange.Empty); } /// @@ -648,12 +783,17 @@ ModuleInfoExtended Module /// ``` /// All dependency entries must be properly formed with required attributes. /// -public sealed record ModuleDependencyNullIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyNullIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}' has a null dependency entry"; - public override ModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyIsNull, ToString(), ApplicationVersionRange.Empty); + public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyIsNull, ToString(), ApplicationVersionRange.Empty); } /// @@ -680,10 +820,20 @@ ModuleInfoExtended Module /// ``` /// All dependency entries must include an Id to identify the required module. /// -public sealed record ModuleDependencyMissingIdIssue( +#if !BANNERLORDBUTRMODULEMANAGER_PUBLIC +internal +#else +public +# endif + sealed record ModuleDependencyMissingIdIssue( ModuleInfoExtended Module ) : ModuleIssueV2(Module) { public override string ToString() => $"Module '{Module.Id}' has a dependency entry missing its Id field"; - public override ModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyMissingModuleId, ToString(), ApplicationVersionRange.Empty); -} \ No newline at end of file + public override LegacyModuleIssue ToLegacy() => new(Module, "UNKNOWN", ModuleIssueType.DependencyMissingModuleId, ToString(), ApplicationVersionRange.Empty); +} + +#nullable restore +#if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING +#pragma warning restore +#endif \ No newline at end of file diff --git a/src/Bannerlord.ModuleManager.Models/LoadType.cs b/src/Bannerlord.ModuleManager.Models/LoadType.cs index deed30f..df87bfc 100644 --- a/src/Bannerlord.ModuleManager.Models/LoadType.cs +++ b/src/Bannerlord.ModuleManager.Models/LoadType.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "LoadType.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,26 +27,26 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ +namespace Bannerlord.ModuleManager; + #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - enum LoadType - { - None = 0, - LoadAfterThis = 1, - LoadBeforeThis = 2 - } + enum LoadType +{ + None = 0, + LoadAfterThis = 1, + LoadBeforeThis = 2 +} - internal enum LoadTypeParse - { - LoadAfterThis = 1, - LoadBeforeThis = 2 - } +internal enum LoadTypeParse +{ + LoadAfterThis = 1, + LoadBeforeThis = 2 } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs b/src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs deleted file mode 100644 index d2f12cc..0000000 --- a/src/Bannerlord.ModuleManager.Models/Misc/Polyfills.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Polyfill for NS2.0 -// ReSharper disable once UnusedType.Global -// ReSharper disable once CheckNamespace -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit {} -} \ No newline at end of file diff --git a/src/Bannerlord.ModuleManager.Models/ModuleInfoExtended.cs b/src/Bannerlord.ModuleManager.Models/ModuleInfoExtended.cs index 6eaacf0..1afe7d8 100644 --- a/src/Bannerlord.ModuleManager.Models/ModuleInfoExtended.cs +++ b/src/Bannerlord.ModuleManager.Models/ModuleInfoExtended.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ModuleInfoExtended.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,269 +27,269 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; - using global::System.Linq; - using global::System.Xml; +namespace Bannerlord.ModuleManager; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - record ModuleInfoExtended - { - private static readonly string NativeModuleId = "Native"; - private static readonly string[] OfficialModuleIds = { NativeModuleId, "SandBox", "SandBoxCore", "StoryMode", "CustomBattle", "BirthAndDeath", "Multiplayer" }; + record ModuleInfoExtended +{ + private static readonly string NativeModuleId = "Native"; + private static readonly string[] OfficialModuleIds = { NativeModuleId, "SandBox", "SandBoxCore", "StoryMode", "CustomBattle", "BirthAndDeath", "Multiplayer" }; - public string Id { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public bool IsOfficial { get; set; } - public ApplicationVersion Version { get; set; } = ApplicationVersion.Empty; - public bool IsSingleplayerModule { get; set; } - public bool IsMultiplayerModule { get; set; } - public bool IsServerModule { get; set; } - public IReadOnlyList SubModules { get; set; } = Array.Empty(); - public IReadOnlyList DependentModules { get; set; } = Array.Empty(); - public IReadOnlyList ModulesToLoadAfterThis { get; set; } = Array.Empty(); - public IReadOnlyList IncompatibleModules { get; set; } = Array.Empty(); - public string Url { get; set; } = string.Empty; - public string UpdateInfo { get; set; } = string.Empty; - public IReadOnlyList DependentModuleMetadatas { get; set; } = Array.Empty(); + public string Id { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public bool IsOfficial { get; set; } + public ApplicationVersion Version { get; set; } = ApplicationVersion.Empty; + public bool IsSingleplayerModule { get; set; } + public bool IsMultiplayerModule { get; set; } + public bool IsServerModule { get; set; } + public IReadOnlyList SubModules { get; set; } = Array.Empty(); + public IReadOnlyList DependentModules { get; set; } = Array.Empty(); + public IReadOnlyList ModulesToLoadAfterThis { get; set; } = Array.Empty(); + public IReadOnlyList IncompatibleModules { get; set; } = Array.Empty(); + public string Url { get; set; } = string.Empty; + public string UpdateInfo { get; set; } = string.Empty; + public IReadOnlyList DependentModuleMetadatas { get; set; } = Array.Empty(); #if BANNERLORDBUTRMODULEMANAGER_NULLABLE - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("xmlDocument")] + [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("xmlDocument")] #endif - public static ModuleInfoExtended? FromXml(XmlDocument? xmlDocument) + public static ModuleInfoExtended? FromXml(XmlDocument? xmlDocument) + { + if (xmlDocument is null) { - if (xmlDocument is null) - { - return null; - } + return null; + } - var moduleNode = xmlDocument.SelectSingleNode("Module"); + var moduleNode = xmlDocument.SelectSingleNode("Module"); - var id = moduleNode?.SelectSingleNode("Id")?.Attributes?["value"]?.InnerText ?? string.Empty; - var name = moduleNode?.SelectSingleNode("Name")?.Attributes?["value"]?.InnerText ?? string.Empty; - ApplicationVersion.TryParse(moduleNode?.SelectSingleNode("Version")?.Attributes?["value"]?.InnerText, out var version); + var id = moduleNode?.SelectSingleNode("Id")?.Attributes?["value"]?.InnerText ?? string.Empty; + var name = moduleNode?.SelectSingleNode("Name")?.Attributes?["value"]?.InnerText ?? string.Empty; + ApplicationVersion.TryParse(moduleNode?.SelectSingleNode("Version")?.Attributes?["value"]?.InnerText, out var version); - var moduleType = moduleNode?.SelectSingleNode("ModuleType")?.Attributes?["value"].InnerText; - var isOfficial = moduleNode?.SelectSingleNode("Official")?.Attributes?["value"]?.InnerText.Equals("true") == true || - moduleType == "Official" || moduleType == "OfficialOptional"; + var moduleType = moduleNode?.SelectSingleNode("ModuleType")?.Attributes?["value"].InnerText; + var isOfficial = moduleNode?.SelectSingleNode("Official")?.Attributes?["value"]?.InnerText.Equals("true") == true || + moduleType == "Official" || moduleType == "OfficialOptional"; - var moduleCategory = moduleNode?.SelectSingleNode("ModuleCategory")?.Attributes?["value"]?.InnerText; - var isSingleplayerModule = moduleNode?.SelectSingleNode("SingleplayerModule")?.Attributes?["value"]?.InnerText.Equals("true") == true || - moduleCategory == "Singleplayer" || moduleCategory == "SingleplayerOptional"; - var isMultiplayerModule = moduleNode?.SelectSingleNode("MultiplayerModule")?.Attributes?["value"]?.InnerText.Equals("true") == true || - moduleCategory == "Multiplayer" || moduleCategory == "MultiplayerOptional"; - var isServerModule = moduleCategory == "Server" || moduleCategory == "ServerOptional"; + var moduleCategory = moduleNode?.SelectSingleNode("ModuleCategory")?.Attributes?["value"]?.InnerText; + var isSingleplayerModule = moduleNode?.SelectSingleNode("SingleplayerModule")?.Attributes?["value"]?.InnerText.Equals("true") == true || + moduleCategory == "Singleplayer" || moduleCategory == "SingleplayerOptional"; + var isMultiplayerModule = moduleNode?.SelectSingleNode("MultiplayerModule")?.Attributes?["value"]?.InnerText.Equals("true") == true || + moduleCategory == "Multiplayer" || moduleCategory == "MultiplayerOptional"; + var isServerModule = moduleCategory == "Server" || moduleCategory == "ServerOptional"; - var dependentModulesNode = moduleNode?.SelectSingleNode("DependedModules"); - var dependentModulesList = dependentModulesNode?.SelectNodes("DependedModule"); - var dependentModules = new List(dependentModulesList?.Count ?? 0); - for (var i = 0; i < dependentModulesList?.Count; i++) + var dependentModulesNode = moduleNode?.SelectSingleNode("DependedModules"); + var dependentModulesList = dependentModulesNode?.SelectNodes("DependedModule"); + var dependentModules = new List(dependentModulesList?.Count ?? 0); + for (var i = 0; i < dependentModulesList?.Count; i++) + { + if (dependentModulesList?[i]?.Attributes?["Id"] is { } idAttr) { - if (dependentModulesList?[i]?.Attributes?["Id"] is { } idAttr) - { - ApplicationVersion.TryParse(dependentModulesList[i]?.Attributes?["DependentVersion"]?.InnerText, out var dVersion); - var isOptional = dependentModulesList[i]?.Attributes?["Optional"] is { } optional && optional.InnerText.Equals("true"); - dependentModules.Add(new DependentModule(idAttr.InnerText, dVersion, isOptional)); - } + ApplicationVersion.TryParse(dependentModulesList[i]?.Attributes?["DependentVersion"]?.InnerText, out var dVersion); + var isOptional = dependentModulesList[i]?.Attributes?["Optional"] is { } optional && optional.InnerText.Equals("true"); + dependentModules.Add(new DependentModule(idAttr.InnerText, dVersion, isOptional)); } + } - var modulesToLoadAfterThisNode = moduleNode?.SelectSingleNode("ModulesToLoadAfterThis"); - var modulesToLoadAfterThisList = modulesToLoadAfterThisNode?.SelectNodes("Module"); - var modulesToLoadAfterThis = new List(modulesToLoadAfterThisList?.Count ?? 0); - for (var i = 0; i < modulesToLoadAfterThisList?.Count; i++) + var modulesToLoadAfterThisNode = moduleNode?.SelectSingleNode("ModulesToLoadAfterThis"); + var modulesToLoadAfterThisList = modulesToLoadAfterThisNode?.SelectNodes("Module"); + var modulesToLoadAfterThis = new List(modulesToLoadAfterThisList?.Count ?? 0); + for (var i = 0; i < modulesToLoadAfterThisList?.Count; i++) + { + if (modulesToLoadAfterThisList?[i]?.Attributes?["Id"] is { } idAttr) { - if (modulesToLoadAfterThisList?[i]?.Attributes?["Id"] is { } idAttr) + modulesToLoadAfterThis.Add(new DependentModule { - modulesToLoadAfterThis.Add(new DependentModule - { - Id = idAttr.InnerText, - IsOptional = true - }); - } + Id = idAttr.InnerText, + IsOptional = true + }); } + } - var incompatibleModulesNode = moduleNode?.SelectSingleNode("IncompatibleModules"); - var incompatibleModulesList = incompatibleModulesNode?.SelectNodes("Module"); - var incompatibleModules = new List(incompatibleModulesList?.Count ?? 0); - for (var i = 0; i < incompatibleModulesList?.Count; i++) + var incompatibleModulesNode = moduleNode?.SelectSingleNode("IncompatibleModules"); + var incompatibleModulesList = incompatibleModulesNode?.SelectNodes("Module"); + var incompatibleModules = new List(incompatibleModulesList?.Count ?? 0); + for (var i = 0; i < incompatibleModulesList?.Count; i++) + { + if (incompatibleModulesList?[i]?.Attributes?["Id"] is { } idAttr) { - if (incompatibleModulesList?[i]?.Attributes?["Id"] is { } idAttr) + incompatibleModules.Add(new DependentModule { - incompatibleModules.Add(new DependentModule - { - Id = idAttr.InnerText, - IsOptional = true - }); - } + Id = idAttr.InnerText, + IsOptional = true + }); } + } - var subModulesNode = moduleNode?.SelectSingleNode("SubModules"); - var subModuleList = subModulesNode?.SelectNodes("SubModule"); - var subModules = new List(subModuleList?.Count ?? 0); - for (var i = 0; i < subModuleList?.Count; i++) + var subModulesNode = moduleNode?.SelectSingleNode("SubModules"); + var subModuleList = subModulesNode?.SelectNodes("SubModule"); + var subModules = new List(subModuleList?.Count ?? 0); + for (var i = 0; i < subModuleList?.Count; i++) + { + if (SubModuleInfoExtended.FromXml(subModuleList?[i]) is { } subModule) { - if (SubModuleInfoExtended.FromXml(subModuleList?[i]) is { } subModule) - { - subModules.Add(subModule); - } + subModules.Add(subModule); } + } - // Custom data - // - var url = moduleNode?.SelectSingleNode("Url")?.Attributes?["value"]?.InnerText ?? string.Empty; + // Custom data + // + var url = moduleNode?.SelectSingleNode("Url")?.Attributes?["value"]?.InnerText ?? string.Empty; - var updateInfo = moduleNode?.SelectSingleNode("UpdateInfo")?.Attributes?["value"]?.InnerText ?? string.Empty; + var updateInfo = moduleNode?.SelectSingleNode("UpdateInfo")?.Attributes?["value"]?.InnerText ?? string.Empty; - var dependentModuleMetadatasNode = moduleNode?.SelectSingleNode("DependedModuleMetadatas"); - var dependentModuleMetadatasList = dependentModuleMetadatasNode?.SelectNodes("DependedModuleMetadata"); + var dependentModuleMetadatasNode = moduleNode?.SelectSingleNode("DependedModuleMetadatas"); + var dependentModuleMetadatasList = dependentModuleMetadatasNode?.SelectNodes("DependedModuleMetadata"); - // Fixed Launcher supported optional tag - var loadAfterModules = moduleNode?.SelectSingleNode("LoadAfterModules"); - var loadAfterModuleList = loadAfterModules?.SelectNodes("LoadAfterModule"); + // Fixed Launcher supported optional tag + var loadAfterModules = moduleNode?.SelectSingleNode("LoadAfterModules"); + var loadAfterModuleList = loadAfterModules?.SelectNodes("LoadAfterModule"); - // Bannerlord Launcher supported optional tag - var optionalDependentModules = moduleNode?.SelectSingleNode("OptionalDependModules"); - var optionalDependentModuleList = - (dependentModulesNode?.SelectNodes("OptionalDependModule")?.Cast() ?? Enumerable.Empty()) - .Concat(optionalDependentModules?.SelectNodes("OptionalDependModule")?.Cast() ?? Enumerable.Empty()) - .Concat(optionalDependentModules?.SelectNodes("DependModule")?.Cast() ?? Enumerable.Empty()).ToList(); + // Bannerlord Launcher supported optional tag + var optionalDependentModules = moduleNode?.SelectSingleNode("OptionalDependModules"); + var optionalDependentModuleList = + (dependentModulesNode?.SelectNodes("OptionalDependModule")?.Cast() ?? Enumerable.Empty()) + .Concat(optionalDependentModules?.SelectNodes("OptionalDependModule")?.Cast() ?? Enumerable.Empty()) + .Concat(optionalDependentModules?.SelectNodes("DependModule")?.Cast() ?? Enumerable.Empty()).ToList(); - var dependentModuleMetadatas = new List(dependentModuleMetadatasList?.Count ?? 0 + loadAfterModuleList?.Count ?? 0 + optionalDependentModuleList.Count); - for (var i = 0; i < dependentModuleMetadatasList?.Count; i++) + var dependentModuleMetadatas = new List(dependentModuleMetadatasList?.Count ?? 0 + loadAfterModuleList?.Count ?? 0 + optionalDependentModuleList.Count); + for (var i = 0; i < dependentModuleMetadatasList?.Count; i++) + { + if (dependentModuleMetadatasList?[i]?.Attributes?["id"] is { } idAttr) { - if (dependentModuleMetadatasList?[i]?.Attributes?["id"] is { } idAttr) + var order = dependentModuleMetadatasList[i]?.Attributes?["order"] is { } orderAttr && Enum.TryParse(orderAttr.InnerText, out var loadType) ? (LoadType) loadType : LoadType.None; + var optional = dependentModuleMetadatasList[i]?.Attributes?["optional"]?.InnerText.Equals("true") ?? false; + var incompatible = dependentModuleMetadatasList[i]?.Attributes?["incompatible"]?.InnerText.Equals("true") ?? false; + var dVersion = ApplicationVersion.TryParse(dependentModuleMetadatasList[i]?.Attributes?["version"]?.InnerText, out var v) ? v : ApplicationVersion.Empty; + var dVersionRange = ApplicationVersionRange.TryParse(dependentModuleMetadatasList[i]?.Attributes?["version"]?.InnerText ?? string.Empty, out var vr) ? vr : ApplicationVersionRange.Empty; + dependentModuleMetadatas.Add(new DependentModuleMetadata { - var order = dependentModuleMetadatasList[i]?.Attributes?["order"] is { } orderAttr && Enum.TryParse(orderAttr.InnerText, out var loadType) ? (LoadType) loadType : LoadType.None; - var optional = dependentModuleMetadatasList[i]?.Attributes?["optional"]?.InnerText.Equals("true") ?? false; - var incompatible = dependentModuleMetadatasList[i]?.Attributes?["incompatible"]?.InnerText.Equals("true") ?? false; - var dVersion = ApplicationVersion.TryParse(dependentModuleMetadatasList[i]?.Attributes?["version"]?.InnerText, out var v) ? v : ApplicationVersion.Empty; - var dVersionRange = ApplicationVersionRange.TryParse(dependentModuleMetadatasList[i]?.Attributes?["version"]?.InnerText ?? string.Empty, out var vr) ? vr : ApplicationVersionRange.Empty; - dependentModuleMetadatas.Add(new DependentModuleMetadata - { - Id = idAttr.InnerText, - LoadType = (LoadType) order, - IsOptional = optional, - IsIncompatible = incompatible, - Version = dVersion, - VersionRange = dVersionRange - }); - } + Id = idAttr.InnerText, + LoadType = (LoadType) order, + IsOptional = optional, + IsIncompatible = incompatible, + Version = dVersion, + VersionRange = dVersionRange + }); } - for (var i = 0; i < loadAfterModuleList?.Count; i++) + } + for (var i = 0; i < loadAfterModuleList?.Count; i++) + { + if (loadAfterModuleList?[i]?.Attributes?["Id"] is { } idAttr) { - if (loadAfterModuleList?[i]?.Attributes?["Id"] is { } idAttr) + dependentModuleMetadatas.Add(new DependentModuleMetadata { - dependentModuleMetadatas.Add(new DependentModuleMetadata - { - Id = idAttr.InnerText, - LoadType = LoadType.LoadAfterThis, - IsOptional = false, - IsIncompatible = false, - Version = ApplicationVersion.Empty, - VersionRange = ApplicationVersionRange.Empty - }); - } + Id = idAttr.InnerText, + LoadType = LoadType.LoadAfterThis, + IsOptional = false, + IsIncompatible = false, + Version = ApplicationVersion.Empty, + VersionRange = ApplicationVersionRange.Empty + }); } - for (var i = 0; i < optionalDependentModuleList.Count; i++) + } + for (var i = 0; i < optionalDependentModuleList.Count; i++) + { + if (optionalDependentModuleList[i].Attributes?["Id"] is { } idAttr) { - if (optionalDependentModuleList[i].Attributes?["Id"] is { } idAttr) + dependentModuleMetadatas.Add(new DependentModuleMetadata { - dependentModuleMetadatas.Add(new DependentModuleMetadata - { - Id = idAttr.InnerText, - LoadType = LoadType.None, - IsOptional = true, - IsIncompatible = false, - Version = ApplicationVersion.Empty, - VersionRange = ApplicationVersionRange.Empty - }); - } + Id = idAttr.InnerText, + LoadType = LoadType.None, + IsOptional = true, + IsIncompatible = false, + Version = ApplicationVersion.Empty, + VersionRange = ApplicationVersionRange.Empty + }); } + } - var requiredGameVersion = moduleNode?.SelectSingleNode("RequiredGameVersion"); - var requiredGameVersionVal = requiredGameVersion?.Attributes?["value"]?.InnerText ?? string.Empty; - var requiredGameVersionOptional = requiredGameVersion?.Attributes?["optional"]?.InnerText.Equals("true") == true; - if (!string.IsNullOrWhiteSpace(requiredGameVersionVal) && ApplicationVersion.TryParse(requiredGameVersionVal, out var gameVersion)) + var requiredGameVersion = moduleNode?.SelectSingleNode("RequiredGameVersion"); + var requiredGameVersionVal = requiredGameVersion?.Attributes?["value"]?.InnerText ?? string.Empty; + var requiredGameVersionOptional = requiredGameVersion?.Attributes?["optional"]?.InnerText.Equals("true") == true; + if (!string.IsNullOrWhiteSpace(requiredGameVersionVal) && ApplicationVersion.TryParse(requiredGameVersionVal, out var gameVersion)) + { + foreach (var moduleId in OfficialModuleIds) { - foreach (var moduleId in OfficialModuleIds) - { - var isNative = moduleId.Equals(NativeModuleId); + var isNative = moduleId.Equals(NativeModuleId); - // Override any existing metadata - if (dependentModuleMetadatas.Find(dmm => moduleId.Equals(dmm.Id, StringComparison.Ordinal)) is { } module) - { - dependentModuleMetadatas.Remove(module); - } - - dependentModuleMetadatas.Add(new DependentModuleMetadata - { - Id = moduleId, - LoadType = LoadType.LoadBeforeThis, - IsOptional = requiredGameVersionOptional && !isNative, - IsIncompatible = false, - Version = gameVersion, - VersionRange = ApplicationVersionRange.Empty - }); + // Override any existing metadata + if (dependentModuleMetadatas.Find(dmm => moduleId.Equals(dmm.Id, StringComparison.Ordinal)) is { } module) + { + dependentModuleMetadatas.Remove(module); } - } - return new ModuleInfoExtended - { - Id = id, - Name = name, - IsOfficial = isOfficial, - Version = version, - IsSingleplayerModule = isSingleplayerModule, - IsMultiplayerModule = isMultiplayerModule, - IsServerModule = isServerModule, - SubModules = subModules, - DependentModules = dependentModules, - ModulesToLoadAfterThis = modulesToLoadAfterThis, - IncompatibleModules = incompatibleModules, - Url = url, - UpdateInfo = !string.IsNullOrEmpty(updateInfo) ? updateInfo : string.Empty, - DependentModuleMetadatas = dependentModuleMetadatas - }; + dependentModuleMetadatas.Add(new DependentModuleMetadata + { + Id = moduleId, + LoadType = LoadType.LoadBeforeThis, + IsOptional = requiredGameVersionOptional && !isNative, + IsIncompatible = false, + Version = gameVersion, + VersionRange = ApplicationVersionRange.Empty + }); + } } - public ModuleInfoExtended() { } - public ModuleInfoExtended(string id, string name, bool isOfficial, ApplicationVersion version, bool isSingleplayerModule, bool isMultiplayerModule, - IReadOnlyList subModules, IReadOnlyList dependentModules, IReadOnlyList modulesToLoadAfterThis, - IReadOnlyList incompatibleModules, IReadOnlyList dependentModuleMetadatas, string url) + return new ModuleInfoExtended { - Id = id; - Name = name; - IsOfficial = isOfficial; - Version = version; - IsSingleplayerModule = isSingleplayerModule; - IsMultiplayerModule = isMultiplayerModule; - SubModules = subModules; - DependentModules = dependentModules; - ModulesToLoadAfterThis = modulesToLoadAfterThis; - IncompatibleModules = incompatibleModules; - DependentModuleMetadatas = dependentModuleMetadatas; - Url = url; - } + Id = id, + Name = name, + IsOfficial = isOfficial, + Version = version, + IsSingleplayerModule = isSingleplayerModule, + IsMultiplayerModule = isMultiplayerModule, + IsServerModule = isServerModule, + SubModules = subModules, + DependentModules = dependentModules, + ModulesToLoadAfterThis = modulesToLoadAfterThis, + IncompatibleModules = incompatibleModules, + Url = url, + UpdateInfo = !string.IsNullOrEmpty(updateInfo) ? updateInfo : string.Empty, + DependentModuleMetadatas = dependentModuleMetadatas + }; + } + + public ModuleInfoExtended() { } + public ModuleInfoExtended(string id, string name, bool isOfficial, ApplicationVersion version, bool isSingleplayerModule, bool isMultiplayerModule, + IReadOnlyList subModules, IReadOnlyList dependentModules, IReadOnlyList modulesToLoadAfterThis, + IReadOnlyList incompatibleModules, IReadOnlyList dependentModuleMetadatas, string url) + { + Id = id; + Name = name; + IsOfficial = isOfficial; + Version = version; + IsSingleplayerModule = isSingleplayerModule; + IsMultiplayerModule = isMultiplayerModule; + SubModules = subModules; + DependentModules = dependentModules; + ModulesToLoadAfterThis = modulesToLoadAfterThis; + IncompatibleModules = incompatibleModules; + DependentModuleMetadatas = dependentModuleMetadatas; + Url = url; + } - public bool IsNative() => NativeModuleId.Equals(Id, StringComparison.OrdinalIgnoreCase); + public bool IsNative() => NativeModuleId.Equals(Id, StringComparison.OrdinalIgnoreCase); - public override string ToString() => $"{Id} - {Version}"; + public override string ToString() => $"{Id} - {Version}"; - public virtual bool Equals(ModuleInfoExtended? other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return Id == other.Id; - } - public override int GetHashCode() => Id.GetHashCode(); + public virtual bool Equals(ModuleInfoExtended? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id; } + public override int GetHashCode() => Id.GetHashCode(); } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/ModuleIssue.cs b/src/Bannerlord.ModuleManager.Models/ModuleIssue.cs index 3901c71..0d3378e 100644 --- a/src/Bannerlord.ModuleManager.Models/ModuleIssue.cs +++ b/src/Bannerlord.ModuleManager.Models/ModuleIssue.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ModuleIssue.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,48 +27,44 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; - using global::System.Linq; +namespace Bannerlord.ModuleManager; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - sealed record ModuleIssue - { - public ModuleInfoExtended Target { get; set; } - public string SourceId { get; set; } - public ModuleIssueType Type { get; set; } - public string Reason { get; set; } - public ApplicationVersionRange SourceVersion { get; set; } + sealed record ModuleIssue +{ + public ModuleInfoExtended Target { get; set; } + public string SourceId { get; set; } + public ModuleIssueType Type { get; set; } + public string Reason { get; set; } + public ApplicationVersionRange SourceVersion { get; set; } - public ModuleIssue() - { - Target = new(); - SourceId = string.Empty; - Type = ModuleIssueType.NONE; - Reason = string.Empty; - SourceVersion = ApplicationVersionRange.Empty; - } - public ModuleIssue(ModuleInfoExtended target, string sourceId, ModuleIssueType type) - { - Target = target; - SourceId = sourceId; - Type = type; - Reason = string.Empty; - SourceVersion = ApplicationVersionRange.Empty; - } - public ModuleIssue(ModuleInfoExtended target, string sourceId, ModuleIssueType type, string reason, ApplicationVersionRange sourceVersion) : this(target, sourceId, type) - { - Reason = reason; - SourceVersion = sourceVersion; - } + public ModuleIssue() + { + Target = new(); + SourceId = string.Empty; + Type = ModuleIssueType.NONE; + Reason = string.Empty; + SourceVersion = ApplicationVersionRange.Empty; + } + public ModuleIssue(ModuleInfoExtended target, string sourceId, ModuleIssueType type) + { + Target = target; + SourceId = sourceId; + Type = type; + Reason = string.Empty; + SourceVersion = ApplicationVersionRange.Empty; + } + public ModuleIssue(ModuleInfoExtended target, string sourceId, ModuleIssueType type, string reason, ApplicationVersionRange sourceVersion) : this(target, sourceId, type) + { + Reason = reason; + SourceVersion = sourceVersion; } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/ModuleIssueType.cs b/src/Bannerlord.ModuleManager.Models/ModuleIssueType.cs index fe81e54..5048785 100644 --- a/src/Bannerlord.ModuleManager.Models/ModuleIssueType.cs +++ b/src/Bannerlord.ModuleManager.Models/ModuleIssueType.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ModuleIssueType.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,41 +27,37 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; - using global::System.Linq; +namespace Bannerlord.ModuleManager; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - enum ModuleIssueType - { - NONE, - Missing, - MissingDependencies, - DependencyMissingDependencies, - DependencyValidationError, - VersionMismatchLessThanOrEqual, - VersionMismatchLessThan, - VersionMismatchGreaterThan, - Incompatible, - DependencyConflictDependentAndIncompatible, - DependencyConflictDependentLoadBeforeAndAfter, - DependencyConflictCircular, + enum ModuleIssueType +{ + NONE, + Missing, + MissingDependencies, + DependencyMissingDependencies, + DependencyValidationError, + VersionMismatchLessThanOrEqual, + VersionMismatchLessThan, + VersionMismatchGreaterThan, + Incompatible, + DependencyConflictDependentAndIncompatible, + DependencyConflictDependentLoadBeforeAndAfter, + DependencyConflictCircular, - DependencyNotLoadedBeforeThis, - DependencyNotLoadedAfterThis, + DependencyNotLoadedBeforeThis, + DependencyNotLoadedAfterThis, - MissingModuleId, - MissingModuleName, - DependencyIsNull, - DependencyMissingModuleId, - } + MissingModuleId, + MissingModuleName, + DependencyIsNull, + DependencyMissingModuleId, } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/SubModuleInfoExtended.cs b/src/Bannerlord.ModuleManager.Models/SubModuleInfoExtended.cs index fcd89a0..6b37f31 100644 --- a/src/Bannerlord.ModuleManager.Models/SubModuleInfoExtended.cs +++ b/src/Bannerlord.ModuleManager.Models/SubModuleInfoExtended.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "SubModuleInfoExtended.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,97 +27,97 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; - using global::System.Collections.ObjectModel; - using global::System.Linq; - using global::System.Xml; +namespace Bannerlord.ModuleManager; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Xml; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - record SubModuleInfoExtended - { - public string Name { get; set; } = string.Empty; - public string DLLName { get; set; } = string.Empty; - public IReadOnlyList Assemblies { get; set; } = Array.Empty(); - public string SubModuleClassType { get; set; } = string.Empty; - public IReadOnlyDictionary> Tags { get; set; } = new Dictionary>(); + record SubModuleInfoExtended +{ + public string Name { get; set; } = string.Empty; + public string DLLName { get; set; } = string.Empty; + public IReadOnlyList Assemblies { get; set; } = Array.Empty(); + public string SubModuleClassType { get; set; } = string.Empty; + public IReadOnlyDictionary> Tags { get; set; } = new Dictionary>(); - public SubModuleInfoExtended() { } - public SubModuleInfoExtended(string name, string dllName, IReadOnlyList assemblies, string subModuleClassType, IReadOnlyDictionary> tags) - { - Name = name; - DLLName = dllName; - Assemblies = assemblies; - SubModuleClassType = subModuleClassType; - Tags = tags; - } + public SubModuleInfoExtended() { } + public SubModuleInfoExtended(string name, string dllName, IReadOnlyList assemblies, string subModuleClassType, IReadOnlyDictionary> tags) + { + Name = name; + DLLName = dllName; + Assemblies = assemblies; + SubModuleClassType = subModuleClassType; + Tags = tags; + } #if BANNERLORDBUTRMODULEMANAGER_NULLABLE - [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("xmlDocument")] + [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("xmlDocument")] #endif - public static SubModuleInfoExtended? FromXml(XmlNode? subModuleNode) - { - if (subModuleNode is null) return null; + public static SubModuleInfoExtended? FromXml(XmlNode? subModuleNode) + { + if (subModuleNode is null) return null; - var name = subModuleNode.SelectSingleNode("Name")?.Attributes?["value"]?.InnerText ?? string.Empty; - var dllName = subModuleNode.SelectSingleNode("DLLName")?.Attributes?["value"]?.InnerText ?? string.Empty; + var name = subModuleNode.SelectSingleNode("Name")?.Attributes?["value"]?.InnerText ?? string.Empty; + var dllName = subModuleNode.SelectSingleNode("DLLName")?.Attributes?["value"]?.InnerText ?? string.Empty; - var subModuleClassType = subModuleNode.SelectSingleNode("SubModuleClassType")?.Attributes?["value"]?.InnerText ?? string.Empty; - var assemblies = Array.Empty(); - if (subModuleNode.SelectSingleNode("Assemblies") != null) + var subModuleClassType = subModuleNode.SelectSingleNode("SubModuleClassType")?.Attributes?["value"]?.InnerText ?? string.Empty; + var assemblies = Array.Empty(); + if (subModuleNode.SelectSingleNode("Assemblies") != null) + { + var assembliesList = subModuleNode.SelectSingleNode("Assemblies")?.SelectNodes("Assembly"); + assemblies = new string[assembliesList?.Count ?? 0]; + for (var i = 0; i < assembliesList?.Count; i++) { - var assembliesList = subModuleNode.SelectSingleNode("Assemblies")?.SelectNodes("Assembly"); - assemblies = new string[assembliesList?.Count ?? 0]; - for (var i = 0; i < assembliesList?.Count; i++) - { - assemblies[i] = assembliesList?[i]?.Attributes?["value"]?.InnerText is { } value ? value : string.Empty; - } + assemblies[i] = assembliesList?[i]?.Attributes?["value"]?.InnerText is { } value ? value : string.Empty; } + } - var tagsList = subModuleNode.SelectSingleNode("Tags")?.SelectNodes("Tag"); - var tags = new Dictionary>(); - for (var i = 0; i < tagsList?.Count; i++) + var tagsList = subModuleNode.SelectSingleNode("Tags")?.SelectNodes("Tag"); + var tags = new Dictionary>(); + for (var i = 0; i < tagsList?.Count; i++) + { + if (tagsList?[i]?.Attributes?["key"]?.InnerText is { } key && tagsList[i]?.Attributes?["value"]?.InnerText is { } value) { - if (tagsList?[i]?.Attributes?["key"]?.InnerText is { } key && tagsList[i]?.Attributes?["value"]?.InnerText is { } value) + if (tags.TryGetValue(key, out var list)) { - if (tags.TryGetValue(key, out var list)) - { - list.Add(value); - } - else - { - tags[key] = new List { value }; - } + list.Add(value); + } + else + { + tags[key] = new List { value }; } } - - return new SubModuleInfoExtended - { - Name = name, - DLLName = dllName, - Assemblies = assemblies, - SubModuleClassType = subModuleClassType, - Tags = new ReadOnlyDictionary>(tags.ToDictionary(x => x.Key, x => new ReadOnlyCollection(x.Value) as IReadOnlyList)) - }; } - public override string ToString() => $"{Name} - {DLLName}"; - - public virtual bool Equals(SubModuleInfoExtended? other) + return new SubModuleInfoExtended { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return Name == other.Name; - } - public override int GetHashCode() => Name.GetHashCode(); + Name = name, + DLLName = dllName, + Assemblies = assemblies, + SubModuleClassType = subModuleClassType, + Tags = new ReadOnlyDictionary>(tags.ToDictionary(x => x.Key, x => new ReadOnlyCollection(x.Value) as IReadOnlyList)) + }; } + + public override string ToString() => $"{Name} - {DLLName}"; + + public virtual bool Equals(SubModuleInfoExtended? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name; + } + public override int GetHashCode() => Name.GetHashCode(); } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Models/SubModuleTags.cs b/src/Bannerlord.ModuleManager.Models/SubModuleTags.cs index 70f7237..d178f91 100644 --- a/src/Bannerlord.ModuleManager.Models/SubModuleTags.cs +++ b/src/Bannerlord.ModuleManager.Models/SubModuleTags.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "SubModuleTags.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,24 +27,24 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ +namespace Bannerlord.ModuleManager; + #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - enum SubModuleTags - { - RejectedPlatform, - ExclusivePlatform, - DedicatedServerType, - IsNoRenderModeElement, - DependantRuntimeLibrary, - PlayerHostedDedicatedServer, - EngineType - } + enum SubModuleTags +{ + RejectedPlatform, + ExclusivePlatform, + DedicatedServerType, + IsNoRenderModeElement, + DependantRuntimeLibrary, + PlayerHostedDedicatedServer, + EngineType } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj b/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj index 19ca099..ef309f2 100644 --- a/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj +++ b/src/Bannerlord.ModuleManager.Source/Bannerlord.ModuleManager.Source.csproj @@ -3,7 +3,6 @@ netstandard2.0 10.0 - enable $(DefineConstants);BANNERLORDBUTRMODULEMANAGER_PUBLIC;BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING;BANNERLORDBUTRMODULEMANAGER_NULLABLE; Bannerlord.ModuleManager @@ -38,17 +37,15 @@ - - - + diff --git a/src/Bannerlord.ModuleManager.Tests/ModuleStorageTests.cs b/src/Bannerlord.ModuleManager.Tests/ModuleStorageTests.cs index d16a8e4..0b17ed3 100644 --- a/src/Bannerlord.ModuleManager.Tests/ModuleStorageTests.cs +++ b/src/Bannerlord.ModuleManager.Tests/ModuleStorageTests.cs @@ -162,7 +162,7 @@ public void Test_Main() Assert.That(validationResult1, Is.Empty, () => string.Join(", ", validationResult1?.Select(x => x.Reason) ?? Enumerable.Empty())); var validationResult2 = ModuleUtilities.ValidateModule(unsortedInvalid, invalid, validationManager.IsSelected).ToArray(); - Assert.That(validationResult2, Has.Length.EqualTo(1), () => string.Join(", ", validationResult2?.Select(x => x.Reason) ?? Enumerable.Empty())); + Assert.That(validationResult2, Has.Length.EqualTo(2), () => string.Join(", ", validationResult2?.Select(x => x.Reason) ?? Enumerable.Empty())); var enableDisableManager = new EnableDisableManager(); ModuleUtilities.EnableModule(unsorted, uiExtenderEx, enableDisableManager.GetSelected, enableDisableManager.SetSelected, enableDisableManager.GetDisabled, enableDisableManager.SetDisabled); diff --git a/src/Bannerlord.ModuleManager/AlphanumComparatorFast.cs b/src/Bannerlord.ModuleManager/AlphanumComparatorFast.cs index 17f1749..f73105e 100644 --- a/src/Bannerlord.ModuleManager/AlphanumComparatorFast.cs +++ b/src/Bannerlord.ModuleManager/AlphanumComparatorFast.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "AlphanumComparatorFast.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,104 +27,104 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager +namespace Bannerlord.ModuleManager; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +/// +/// Alphanumeric sort. This sorting algorithm logically handles numbers in strings. It makes sense to humans. +/// Highway names like 50F and 100F will be sorted wrongly with ASCII sort. +/// It is different from alphabetic, ASCII or numeric sorting. This algorithmic approach is used in file managers. +/// +internal sealed class AlphanumComparatorFast : IComparer, IComparer { - using global::System; - using global::System.Collections; - using global::System.Collections.Generic; - using global::System.Text; - - /// - /// Alphanumeric sort. This sorting algorithm logically handles numbers in strings. It makes sense to humans. - /// Highway names like 50F and 100F will be sorted wrongly with ASCII sort. - /// It is different from alphabetic, ASCII or numeric sorting. This algorithmic approach is used in file managers. - /// - internal sealed class AlphanumComparatorFast : IComparer, IComparer - { - /// - public int Compare(object? x, object? y) => Compare(x as string, y as string); + /// + public int Compare(object? x, object? y) => Compare(x as string, y as string); - /// - public int Compare(string? s1, string? s2) + /// + public int Compare(string? s1, string? s2) + { + if (s1 is null && s2 is null) return 0; + if (s1 is null) return -1; + if (s2 is null) return 1; + + var len1 = s1.Length; + var len2 = s2.Length; + if (len1 == 0 && len2 == 0) return 0; + if (len1 == 0) return -1; + if (len2 == 0) return 1; + + var marker1 = 0; + var marker2 = 0; + while (marker1 < len1 || marker2 < len2) { - if (s1 is null && s2 is null) return 0; - if (s1 is null) return -1; - if (s2 is null) return 1; - - var len1 = s1.Length; - var len2 = s2.Length; - if (len1 == 0 && len2 == 0) return 0; - if (len1 == 0) return -1; - if (len2 == 0) return 1; - - var marker1 = 0; - var marker2 = 0; - while (marker1 < len1 || marker2 < len2) + if (marker1 >= len1) return -1; + if (marker2 >= len2) return 1; + var ch1 = s1[marker1]; + var ch2 = s2[marker2]; + + var chunk1 = new StringBuilder(); + var chunk2 = new StringBuilder(); + + while (marker1 < len1 && (chunk1.Length == 0 || InChunk(ch1, chunk1[0]))) { - if (marker1 >= len1) return -1; - if (marker2 >= len2) return 1; - var ch1 = s1[marker1]; - var ch2 = s2[marker2]; - - var chunk1 = new StringBuilder(); - var chunk2 = new StringBuilder(); - - while (marker1 < len1 && (chunk1.Length == 0 || InChunk(ch1, chunk1[0]))) - { - chunk1.Append(ch1); - marker1++; - - if (marker1 < len1) - ch1 = s1[marker1]; - } - - while (marker2 < len2 && (chunk2.Length == 0 || InChunk(ch2, chunk2[0]))) - { - chunk2.Append(ch2); - marker2++; - - if (marker2 < len2) - ch2 = s2[marker2]; - } - - // If both chunks contain numeric characters, sort them numerically - if (char.IsDigit(chunk1[0]) && char.IsDigit(chunk2[0])) - { - var numericChunk1 = Convert.ToInt32(chunk1.ToString()); - var numericChunk2 = Convert.ToInt32(chunk2.ToString()); - - if (numericChunk1 < numericChunk2) return -1; - if (numericChunk1 > numericChunk2) return 1; - } - else - { - var result = string.CompareOrdinal(chunk1.ToString(), chunk2.ToString()); - // Normalize for cases when the code can't handle 2 or -2 and futher - if (result >= 1) return 1; - if (result <= -1) return -1; - } + chunk1.Append(ch1); + marker1++; + + if (marker1 < len1) + ch1 = s1[marker1]; } - return 0; - } + while (marker2 < len2 && (chunk2.Length == 0 || InChunk(ch2, chunk2[0]))) + { + chunk2.Append(ch2); + marker2++; - private static bool InChunk(char ch, char otherCh) - { - var type = ChunkType.Alphanumeric; + if (marker2 < len2) + ch2 = s2[marker2]; + } - if (char.IsDigit(otherCh)) - type = ChunkType.Numeric; + // If both chunks contain numeric characters, sort them numerically + if (char.IsDigit(chunk1[0]) && char.IsDigit(chunk2[0])) + { + var numericChunk1 = Convert.ToInt32(chunk1.ToString()); + var numericChunk2 = Convert.ToInt32(chunk2.ToString()); - return (type != ChunkType.Alphanumeric || !char.IsDigit(ch)) && (type != ChunkType.Numeric || char.IsDigit(ch)); + if (numericChunk1 < numericChunk2) return -1; + if (numericChunk1 > numericChunk2) return 1; + } + else + { + var result = string.CompareOrdinal(chunk1.ToString(), chunk2.ToString()); + // Normalize for cases when the code can't handle 2 or -2 and futher + if (result >= 1) return 1; + if (result <= -1) return -1; + } } - private enum ChunkType - { - Alphanumeric, - Numeric - } + return 0; + } + + private static bool InChunk(char ch, char otherCh) + { + var type = ChunkType.Alphanumeric; + + if (char.IsDigit(otherCh)) + type = ChunkType.Numeric; + + return (type != ChunkType.Alphanumeric || !char.IsDigit(ch)) && (type != ChunkType.Numeric || char.IsDigit(ch)); + } + + private enum ChunkType + { + Alphanumeric, + Numeric } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager/CollectionsExtensions.cs b/src/Bannerlord.ModuleManager/CollectionsExtensions.cs index 9f9d3ec..e809f3d 100644 --- a/src/Bannerlord.ModuleManager/CollectionsExtensions.cs +++ b/src/Bannerlord.ModuleManager/CollectionsExtensions.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "CollectionsExtensions.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,62 +27,62 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; +namespace Bannerlord.ModuleManager; + +using System; +using System.Collections.Generic; - internal static class CollectionsExtensions +internal static class CollectionsExtensions +{ + public static int IndexOf(this IReadOnlyList self, T elementToFind) { - public static int IndexOf(this IReadOnlyList self, T elementToFind) + var i = 0; + foreach (T element in self) { - var i = 0; - foreach (T element in self) - { - if (Equals(element, elementToFind)) - return i; - i++; - } - return -1; + if (Equals(element, elementToFind)) + return i; + i++; } - public static int IndexOf(this IReadOnlyList self, Func preficate) + return -1; + } + public static int IndexOf(this IReadOnlyList self, Func preficate) + { + var i = 0; + foreach (T element in self) { - var i = 0; - foreach (T element in self) - { - if (preficate(element)) - return i; - i++; - } - return -1; + if (preficate(element)) + return i; + i++; } + return -1; + } - public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) => - DistinctBy(source, keySelector, null); + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) => + DistinctBy(source, keySelector, null); - public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) => - DistinctByIterator(source, keySelector, comparer); + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) => + DistinctByIterator(source, keySelector, comparer); - private static IEnumerable DistinctByIterator(IEnumerable source, Func keySelector, IEqualityComparer? comparer) - { - using var enumerator = source.GetEnumerator(); + private static IEnumerable DistinctByIterator(IEnumerable source, Func keySelector, IEqualityComparer? comparer) + { + using var enumerator = source.GetEnumerator(); - if (enumerator.MoveNext()) + if (enumerator.MoveNext()) + { + var set = new HashSet(comparer); + do { - var set = new HashSet(comparer); - do + TSource element = enumerator.Current; + if (set.Add(keySelector(element))) { - TSource element = enumerator.Current; - if (set.Add(keySelector(element))) - { - yield return element; - } + yield return element; } - while (enumerator.MoveNext()); } + while (enumerator.MoveNext()); } } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager/ModuleInfoExtendedExtensions.cs b/src/Bannerlord.ModuleManager/ModuleInfoExtendedExtensions.cs index afc1869..2c04317 100644 --- a/src/Bannerlord.ModuleManager/ModuleInfoExtendedExtensions.cs +++ b/src/Bannerlord.ModuleManager/ModuleInfoExtendedExtensions.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ModuleInfoExtendedExtensions.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,145 +27,144 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; - using global::System.Linq; +namespace Bannerlord.ModuleManager; + +using System.Collections.Generic; +using System.Linq; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - static class ModuleInfoExtendedExtensions + static class ModuleInfoExtendedExtensions +{ + public static IEnumerable DependenciesAllDistinct(this ModuleInfoExtended module) => DependenciesAll(module).DistinctBy(x => x.Id); + public static IEnumerable DependenciesAll(this ModuleInfoExtended module) { - public static IEnumerable DependenciesAllDistinct(this ModuleInfoExtended module) => DependenciesAll(module).DistinctBy(x => x.Id); - public static IEnumerable DependenciesAll(this ModuleInfoExtended module) + foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null)) { - foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null)) - { - yield return metadata; - } - foreach (var metadata in module.DependentModules.Where(x => x is not null)) + yield return metadata; + } + foreach (var metadata in module.DependentModules.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - LoadType = LoadType.LoadBeforeThis, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } - foreach (var metadata in module.ModulesToLoadAfterThis.Where(x => x is not null)) + Id = metadata.Id, + LoadType = LoadType.LoadBeforeThis, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; + } + foreach (var metadata in module.ModulesToLoadAfterThis.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - LoadType = LoadType.LoadAfterThis, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } - foreach (var metadata in module.IncompatibleModules.Where(x => x is not null)) + Id = metadata.Id, + LoadType = LoadType.LoadAfterThis, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; + } + foreach (var metadata in module.IncompatibleModules.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - IsIncompatible = true, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } + Id = metadata.Id, + IsIncompatible = true, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; } + } - public static IEnumerable DependenciesToLoadDistinct(this ModuleInfoExtended module) => DependenciesToLoad(module).DistinctBy(x => x.Id); - public static IEnumerable DependenciesToLoad(this ModuleInfoExtended module) + public static IEnumerable DependenciesToLoadDistinct(this ModuleInfoExtended module) => DependenciesToLoad(module).DistinctBy(x => x.Id); + public static IEnumerable DependenciesToLoad(this ModuleInfoExtended module) + { + foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => !x.IsIncompatible)) { - foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => !x.IsIncompatible)) - { - yield return metadata; - } - foreach (var metadata in module.DependentModules.Where(x => x is not null)) + yield return metadata; + } + foreach (var metadata in module.DependentModules.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - LoadType = LoadType.LoadBeforeThis, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } - foreach (var metadata in module.ModulesToLoadAfterThis.Where(x => x is not null)) + Id = metadata.Id, + LoadType = LoadType.LoadBeforeThis, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; + } + foreach (var metadata in module.ModulesToLoadAfterThis.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - LoadType = LoadType.LoadAfterThis, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } + Id = metadata.Id, + LoadType = LoadType.LoadAfterThis, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; } + } - public static IEnumerable DependenciesLoadBeforeThisDistinct(this ModuleInfoExtended module) => DependenciesLoadBeforeThis(module).DistinctBy(x => x.Id); - public static IEnumerable DependenciesLoadBeforeThis(this ModuleInfoExtended module) + public static IEnumerable DependenciesLoadBeforeThisDistinct(this ModuleInfoExtended module) => DependenciesLoadBeforeThis(module).DistinctBy(x => x.Id); + public static IEnumerable DependenciesLoadBeforeThis(this ModuleInfoExtended module) + { + foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => x.LoadType == LoadType.LoadBeforeThis)) { - foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => x.LoadType == LoadType.LoadBeforeThis)) - { - yield return metadata; - } - foreach (var metadata in module.DependentModules.Where(x => x is not null)) + yield return metadata; + } + foreach (var metadata in module.DependentModules.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - LoadType = LoadType.LoadBeforeThis, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } + Id = metadata.Id, + LoadType = LoadType.LoadBeforeThis, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; } + } - public static IEnumerable DependenciesLoadAfterThisDistinct(this ModuleInfoExtended module) => DependenciesLoadAfterThis(module).DistinctBy(x => x.Id); - public static IEnumerable DependenciesLoadAfterThis(this ModuleInfoExtended module) + public static IEnumerable DependenciesLoadAfterThisDistinct(this ModuleInfoExtended module) => DependenciesLoadAfterThis(module).DistinctBy(x => x.Id); + public static IEnumerable DependenciesLoadAfterThis(this ModuleInfoExtended module) + { + foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => x.LoadType == LoadType.LoadAfterThis)) { - foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => x.LoadType == LoadType.LoadAfterThis)) - { - yield return metadata; - } - foreach (var metadata in module.ModulesToLoadAfterThis.Where(x => x is not null)) + yield return metadata; + } + foreach (var metadata in module.ModulesToLoadAfterThis.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - LoadType = LoadType.LoadAfterThis, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } + Id = metadata.Id, + LoadType = LoadType.LoadAfterThis, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; } + } - public static IEnumerable DependenciesIncompatiblesDistinct(this ModuleInfoExtended module) => DependenciesIncompatibles(module).DistinctBy(x => x.Id); - public static IEnumerable DependenciesIncompatibles(this ModuleInfoExtended module) + public static IEnumerable DependenciesIncompatiblesDistinct(this ModuleInfoExtended module) => DependenciesIncompatibles(module).DistinctBy(x => x.Id); + public static IEnumerable DependenciesIncompatibles(this ModuleInfoExtended module) + { + foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => x.IsIncompatible)) { - foreach (var metadata in module.DependentModuleMetadatas.Where(x => x is not null).Where(x => x.IsIncompatible)) - { - yield return metadata; - } - foreach (var metadata in module.IncompatibleModules.Where(x => x is not null)) + yield return metadata; + } + foreach (var metadata in module.IncompatibleModules.Where(x => x is not null)) + { + yield return new DependentModuleMetadata { - yield return new DependentModuleMetadata - { - Id = metadata.Id, - IsIncompatible = true, - IsOptional = metadata.IsOptional, - Version = metadata.Version - }; - } + Id = metadata.Id, + IsIncompatible = true, + IsOptional = metadata.IsOptional, + Version = metadata.Version + }; } } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager/ModuleSorter.cs b/src/Bannerlord.ModuleManager/ModuleSorter.cs index b937f1a..9f53f99 100644 --- a/src/Bannerlord.ModuleManager/ModuleSorter.cs +++ b/src/Bannerlord.ModuleManager/ModuleSorter.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ModuleSorter.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,88 +27,88 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; - using global::System.Linq; +namespace Bannerlord.ModuleManager; + +using System; +using System.Collections.Generic; +using System.Linq; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - sealed record ModuleSorterOptions - { - public bool SkipOptionals { get; set; } - public bool SkipExternalDependencies { get; set; } + sealed record ModuleSorterOptions +{ + public bool SkipOptionals { get; set; } + public bool SkipExternalDependencies { get; set; } - public ModuleSorterOptions() { } - public ModuleSorterOptions(bool skipOptionals, bool skipExternalDependencies) - { - SkipOptionals = skipOptionals; - SkipExternalDependencies = skipExternalDependencies; - } + public ModuleSorterOptions() { } + public ModuleSorterOptions(bool skipOptionals, bool skipExternalDependencies) + { + SkipOptionals = skipOptionals; + SkipExternalDependencies = skipExternalDependencies; } +} #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - static class ModuleSorter + static class ModuleSorter +{ + public static IList Sort(IReadOnlyCollection source) { - public static IList Sort(IReadOnlyCollection source) - { - var correctModules = source - .Where(x => ModuleUtilities.AreDependenciesPresent(source, x)) - .OrderByDescending(x => x.IsOfficial) - .ThenByDescending(mim => mim.Id, new AlphanumComparatorFast()) - .ToArray(); + var correctModules = source + .Where(x => ModuleUtilities.AreDependenciesPresent(source, x)) + .OrderByDescending(x => x.IsOfficial) + .ThenByDescending(mim => mim.Id, new AlphanumComparatorFast()) + .ToArray(); - return TopologySort(correctModules, module => ModuleUtilities.GetDependencies(correctModules, module)); - } - public static IList Sort(IReadOnlyCollection source, ModuleSorterOptions options) - { - var correctModules = source - .Where(x => ModuleUtilities.AreDependenciesPresent(source, x)) - .OrderByDescending(x => x.IsOfficial) - .ThenByDescending(mim => mim.Id, new AlphanumComparatorFast()) - .ToArray(); + return TopologySort(correctModules, module => ModuleUtilities.GetDependencies(correctModules, module)); + } + public static IList Sort(IReadOnlyCollection source, ModuleSorterOptions options) + { + var correctModules = source + .Where(x => ModuleUtilities.AreDependenciesPresent(source, x)) + .OrderByDescending(x => x.IsOfficial) + .ThenByDescending(mim => mim.Id, new AlphanumComparatorFast()) + .ToArray(); - return TopologySort(correctModules, module => ModuleUtilities.GetDependencies(correctModules, module, options)); - } + return TopologySort(correctModules, module => ModuleUtilities.GetDependencies(correctModules, module, options)); + } - public static IList TopologySort(IEnumerable source, Func> getDependencies) + public static IList TopologySort(IEnumerable source, Func> getDependencies) + { + var list = new List(); + var visited = new HashSet(); + foreach (var item in source) { - var list = new List(); - var visited = new HashSet(); - foreach (var item in source) - { - Visit(item, getDependencies, item => list.Add(item), visited); - } - return list; + Visit(item, getDependencies, item => list.Add(item), visited); } + return list; + } - public static void Visit(T item, Func> getDependencies, Action addItem, HashSet visited) + public static void Visit(T item, Func> getDependencies, Action addItem, HashSet visited) + { + if (visited.Contains(item)) { - if (visited.Contains(item)) - { - return; - } + return; + } - visited.Add(item); - if (getDependencies(item) is { } enumerable) + visited.Add(item); + if (getDependencies(item) is { } enumerable) + { + foreach (var item2 in enumerable) { - foreach (var item2 in enumerable) - { - Visit(item2, getDependencies, addItem, visited); - } + Visit(item2, getDependencies, addItem, visited); } - addItem(item); } + addItem(item); } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore diff --git a/src/Bannerlord.ModuleManager/ModuleUtilities.cs b/src/Bannerlord.ModuleManager/ModuleUtilities.cs index 32674b5..d756de6 100644 --- a/src/Bannerlord.ModuleManager/ModuleUtilities.cs +++ b/src/Bannerlord.ModuleManager/ModuleUtilities.cs @@ -1,18 +1,4 @@ -// -// This code file has automatically been added by the "Bannerlord.ModuleManager.Source" NuGet package (https://www.nuget.org/packages/Bannerlord.ModuleManager.Source). -// Please see https://github.com/BUTR/Bannerlord.ModuleManager for more information. -// -// IMPORTANT: -// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. -// Consider migrating to PackageReferences instead: -// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference -// Migrating brings the following benefits: -// * The "Bannerlord.ModuleManager.Source" folder and the "ModuleUtilities.cs" file don't appear in your project. -// * The added file is immutable and can therefore not be modified by coincidence. -// * Updating/Uninstalling the package will work flawlessly. -// - -#region License +#region License // MIT License // // Copyright (c) Bannerlord's Unofficial Tools & Resources @@ -41,728 +27,728 @@ #pragma warning disable #endif -namespace Bannerlord.ModuleManager -{ - using global::System; - using global::System.Collections.Generic; - using global::System.Linq; - using Bannerlord.ModuleManager.Models.Issues; +namespace Bannerlord.ModuleManager; + +using System; +using System.Collections.Generic; +using System.Linq; +using Models.Issues; #if !BANNERLORDBUTRMODULEMANAGER_PUBLIC internal #else - public +public # endif - static class ModuleUtilities + static class ModuleUtilities +{ + /// + /// Checks if all dependencies for a module are present + /// + /// Assumed that only valid and selected to launch modules are in the list + /// The module that is being checked + public static bool AreDependenciesPresent(IReadOnlyCollection modules, ModuleInfoExtended module) { - /// - /// Checks if all dependencies for a module are present - /// - /// Assumed that only valid and selected to launch modules are in the list - /// The module that is being checked - public static bool AreDependenciesPresent(IReadOnlyCollection modules, ModuleInfoExtended module) + foreach (var metadata in module.DependenciesLoadBeforeThisDistinct()) { - foreach (var metadata in module.DependenciesLoadBeforeThisDistinct()) - { - if (metadata.IsOptional) - continue; + if (metadata.IsOptional) + continue; - if (modules.All(x => !string.Equals(x.Id, metadata.Id, StringComparison.Ordinal))) - return false; - } - foreach (var metadata in module.DependenciesIncompatiblesDistinct()) - { - if (modules.Any(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal))) - return false; - } - return true; + if (modules.All(x => !string.Equals(x.Id, metadata.Id, StringComparison.Ordinal))) + return false; } - - /// - /// Returns all dependencies for a module - /// - public static IEnumerable GetDependencies(IReadOnlyCollection modules, ModuleInfoExtended module) + foreach (var metadata in module.DependenciesIncompatiblesDistinct()) { - var visited = new HashSet(); - return GetDependencies(modules, module, visited, new ModuleSorterOptions() { SkipOptionals = false, SkipExternalDependencies = false }); + if (modules.Any(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal))) + return false; } - /// - /// Returns all dependencies for a module - /// - public static IEnumerable GetDependencies(IReadOnlyCollection modules, ModuleInfoExtended module, ModuleSorterOptions options) + return true; + } + + /// + /// Returns all dependencies for a module + /// + public static IEnumerable GetDependencies(IReadOnlyCollection modules, ModuleInfoExtended module) + { + var visited = new HashSet(); + return GetDependencies(modules, module, visited, new ModuleSorterOptions() { SkipOptionals = false, SkipExternalDependencies = false }); + } + /// + /// Returns all dependencies for a module + /// + public static IEnumerable GetDependencies(IReadOnlyCollection modules, ModuleInfoExtended module, ModuleSorterOptions options) + { + var visited = new HashSet(); + return GetDependencies(modules, module, visited, options); + } + /// + /// Returns all dependencies for a module + /// + public static IEnumerable GetDependencies(IReadOnlyCollection modules, ModuleInfoExtended module, HashSet visited, ModuleSorterOptions options) + { + var dependencies = new List(); + ModuleSorter.Visit(module, x => GetDependenciesInternal(modules, x, options), moduleToAdd => { - var visited = new HashSet(); - return GetDependencies(modules, module, visited, options); - } - /// - /// Returns all dependencies for a module - /// - public static IEnumerable GetDependencies(IReadOnlyCollection modules, ModuleInfoExtended module, HashSet visited, ModuleSorterOptions options) + if (moduleToAdd != module) + dependencies.Add(moduleToAdd); + }, visited); + return dependencies; + } + private static IEnumerable GetDependenciesInternal(IReadOnlyCollection modules, ModuleInfoExtended module, ModuleSorterOptions options) + { + foreach (var dependentModuleMetadata in module.DependenciesLoadBeforeThisDistinct()) { - var dependencies = new List(); - ModuleSorter.Visit(module, x => GetDependenciesInternal(modules, x, options), moduleToAdd => + if (dependentModuleMetadata.IsOptional && options.SkipOptionals) + continue; + + var moduleInfo = modules.FirstOrDefault(x => string.Equals(x.Id, dependentModuleMetadata.Id, StringComparison.Ordinal)); + if (!dependentModuleMetadata.IsOptional && moduleInfo is null) { - if (moduleToAdd != module) - dependencies.Add(moduleToAdd); - }, visited); - return dependencies; - } - private static IEnumerable GetDependenciesInternal(IReadOnlyCollection modules, ModuleInfoExtended module, ModuleSorterOptions options) - { - foreach (var dependentModuleMetadata in module.DependenciesLoadBeforeThisDistinct()) + // We should not hit this place. If we do, the module list is invalid + } + else if (moduleInfo is not null) { - if (dependentModuleMetadata.IsOptional && options.SkipOptionals) - continue; - - var moduleInfo = modules.FirstOrDefault(x => string.Equals(x.Id, dependentModuleMetadata.Id, StringComparison.Ordinal)); - if (!dependentModuleMetadata.IsOptional && moduleInfo is null) - { - // We should not hit this place. If we do, the module list is invalid - } - else if (moduleInfo is not null) - { - yield return moduleInfo; - } + yield return moduleInfo; } + } - if (!options.SkipExternalDependencies) + if (!options.SkipExternalDependencies) + { + foreach (var moduleInfo in modules) { - foreach (var moduleInfo in modules) + foreach (var dependentModuleMetadata in moduleInfo.DependenciesLoadAfterThisDistinct()) { - foreach (var dependentModuleMetadata in moduleInfo.DependenciesLoadAfterThisDistinct()) - { - if (dependentModuleMetadata.IsOptional && options.SkipOptionals) - continue; + if (dependentModuleMetadata.IsOptional && options.SkipOptionals) + continue; - if (!string.Equals(dependentModuleMetadata.Id, module.Id, StringComparison.Ordinal)) - continue; + if (!string.Equals(dependentModuleMetadata.Id, module.Id, StringComparison.Ordinal)) + continue; - yield return moduleInfo; - } + yield return moduleInfo; } } } + } -#region Polyfills - /// - /// Validates a module - /// - /// All available modules (in any order) - /// The module to validate - /// Function that determines if a module is selected (is enabled) - /// Any errors that were detected during inspection - public static IEnumerable ValidateModule( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - Func isSelected) - { - var visited = new HashSet(); - return ValidateModuleEx(modules, targetModule, visited, isSelected, + #region Polyfills + /// + /// Validates a module + /// + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is selected (is enabled) + /// Any errors that were detected during inspection + public static IEnumerable ValidateModule( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected) + { + var visited = new HashSet(); + return ValidateModuleEx(modules, targetModule, visited, isSelected, x => ValidateModuleEx(modules, x, isSelected).Count() == 0) - .Select(x => x.ToLegacy()); - } + .Select(x => x.ToLegacy()); + } + + /// + /// Validates a module + /// + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is selected (is enabled) + /// Function that determines if a module is valid + /// Any errors that were detected during inspection + public static IEnumerable ValidateModule( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected, + Func isValid) + { + var visited = new HashSet(); + return ValidateModuleEx(modules, targetModule, visited, isSelected, isValid) + .Select(x => x.ToLegacy()); + } + + /// + /// Validates a module's common data + /// + /// The module to validate + /// Any errors that were detected during inspection of common data + public static IEnumerable ValidateModuleCommonData(ModuleInfoExtended module) => + ValidateModuleCommonDataEx(module).Select(x => x.ToLegacy()); + + /// + /// Validates a module's dependency declarations + /// + /// All available modules (in any order) + /// The module whose dependencies are being validated + /// Any errors that were detected during inspection of dependencies + public static IEnumerable ValidateModuleDependenciesDeclarations( + IReadOnlyList modules, + ModuleInfoExtended targetModule) => + ValidateModuleDependenciesDeclarationsEx(modules, targetModule).Select(x => x.ToLegacy()); + + /// + /// Validates module dependencies + /// + /// All available modules (in any order) + /// The module whose dependencies are being validated + /// Set of modules already validated to prevent cycles + /// Function that determines if a module is selected + /// Function that determines if a module is valid + /// Any errors that were detected during inspection of dependencies + public static IEnumerable ValidateModuleDependencies( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules, + Func isSelected, + Func isValid) => + ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid) + .Select(x => x.ToLegacy()); + + /// + /// Validates whether the load order is correctly sorted + /// + /// All available modules in they order they are expected to be loaded in + /// Assumed that it's present in + /// Any errors that were detected during inspection + public static IEnumerable ValidateLoadOrder( + IReadOnlyList modules, + ModuleInfoExtended targetModule) + { + foreach (var issue in ValidateModuleCommonData(targetModule)) + yield return issue; - /// - /// Validates a module - /// - /// All available modules (in any order) - /// The module to validate - /// Function that determines if a module is selected (is enabled) - /// Function that determines if a module is valid - /// Any errors that were detected during inspection - public static IEnumerable ValidateModule( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - Func isSelected, - Func isValid) + var visited = new HashSet(); + foreach (var issue in ValidateLoadOrderEx(modules, targetModule, visited) + .Select(x => x.ToLegacy())) + yield return issue; + } + + /// + /// Validates whether the load order is correctly sorted + /// + /// All available modules in they order they are expected to be loaded in + /// Assumed that it's present in + /// Used to track that we traverse each module only once + /// Any errors that were detected during inspection + public static IEnumerable ValidateLoadOrder( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules) => + ValidateLoadOrderEx(modules, targetModule, visitedModules).Select(x => x.ToLegacy()); + #endregion + + /// + /// Validates a module using the new variant-based issue system + /// + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is enabled + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected) + { + var visited = new HashSet(); + foreach (var issue in ValidateModuleEx(modules, targetModule, visited, isSelected, x => ValidateModuleEx(modules, x, isSelected).Count() == 0)) { - var visited = new HashSet(); - return ValidateModuleEx(modules, targetModule, visited, isSelected, isValid) - .Select(x => x.ToLegacy()); + yield return issue; } + } - /// - /// Validates a module's common data - /// - /// The module to validate - /// Any errors that were detected during inspection of common data - public static IEnumerable ValidateModuleCommonData(ModuleInfoExtended module) => - ValidateModuleCommonDataEx(module).Select(x => x.ToLegacy()); - - /// - /// Validates a module's dependency declarations - /// - /// All available modules (in any order) - /// The module whose dependencies are being validated - /// Any errors that were detected during inspection of dependencies - public static IEnumerable ValidateModuleDependenciesDeclarations( - IReadOnlyList modules, - ModuleInfoExtended targetModule) => - ValidateModuleDependenciesDeclarationsEx(modules, targetModule).Select(x => x.ToLegacy()); - - /// - /// Validates module dependencies - /// - /// All available modules (in any order) - /// The module whose dependencies are being validated - /// Set of modules already validated to prevent cycles - /// Function that determines if a module is selected - /// Function that determines if a module is valid - /// Any errors that were detected during inspection of dependencies - public static IEnumerable ValidateModuleDependencies( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - HashSet visitedModules, - Func isSelected, - Func isValid) => - ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid) - .Select(x => x.ToLegacy()); - - /// - /// Validates whether the load order is correctly sorted - /// - /// All available modules in they order they are expected to be loaded in - /// Assumed that it's present in - /// Any errors that were detected during inspection - public static IEnumerable ValidateLoadOrder( - IReadOnlyList modules, - ModuleInfoExtended targetModule) + /// + /// Validates a module using the new variant-based issue system + /// + /// All available modules (in any order) + /// The module to validate + /// Function that determines if a module is enabled + /// Function that determines if a module is valid + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + Func isSelected, + Func isValid) + { + var visited = new HashSet(); + foreach (var issue in ValidateModuleEx(modules, targetModule, visited, isSelected, isValid)) { - foreach (var issue in ValidateModuleCommonData(targetModule)) - yield return issue; - - var visited = new HashSet(); - foreach (var issue in ValidateLoadOrderEx(modules, targetModule, visited) - .Select(x => x.ToLegacy())) - yield return issue; + yield return issue; } + } - /// - /// Validates whether the load order is correctly sorted - /// - /// All available modules in they order they are expected to be loaded in - /// Assumed that it's present in - /// Used to track that we traverse each module only once - /// Any errors that were detected during inspection - public static IEnumerable ValidateLoadOrder( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - HashSet visitedModules) => - ValidateLoadOrderEx(modules, targetModule, visitedModules).Select(x => x.ToLegacy()); -#endregion + /// + /// Internal validation method that handles the core validation logic + /// + /// All available modules (in any order) + /// The module to validate + /// Set of modules already validated to prevent cycles + /// Function that determines if a module is enabled + /// Function that determines if a module is valid + /// Set this to true to also report errors in the target module's dependencies. e.g. Missing dependencies of dependencies. + /// Any errors that were detected during inspection + private static IEnumerable ValidateModuleEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules, + Func isSelected, + Func isValid, + bool validateDependencies = true) + { + foreach (var issue in ValidateModuleCommonDataEx(targetModule)) + yield return issue; + + foreach (var issue in ValidateModuleDependenciesDeclarationsEx(modules, targetModule)) + yield return issue; + + foreach (var issue in ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid, validateDependencies)) + yield return issue; + } - /// - /// Validates a module using the new variant-based issue system - /// - /// All available modules (in any order) - /// The module to validate - /// Function that determines if a module is enabled - /// Any errors that were detected during inspection - public static IEnumerable ValidateModuleEx( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - Func isSelected) + /// + /// Validates a module's common data using the new variant-based issue system + /// + /// The module whose common data is being validated + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleCommonDataEx(ModuleInfoExtended module) + { + if (string.IsNullOrWhiteSpace(module.Id)) + yield return new ModuleMissingIdIssue(module); + + if (string.IsNullOrWhiteSpace(module.Name)) + yield return new ModuleMissingNameIssue(module); + + foreach (var dependentModule in module.DependentModules.Where(x => x is not null)) { - var visited = new HashSet(); - foreach (var issue in ValidateModuleEx(modules, targetModule, visited, isSelected, x => ValidateModuleEx(modules, x, isSelected).Count() == 0)) + if (dependentModule is null) { - yield return issue; + yield return new ModuleDependencyNullIssue(module); + break; } + if (string.IsNullOrWhiteSpace(dependentModule.Id)) + yield return new ModuleDependencyMissingIdIssue(module); } - /// - /// Validates a module using the new variant-based issue system - /// - /// All available modules (in any order) - /// The module to validate - /// Function that determines if a module is enabled - /// Function that determines if a module is valid - /// Any errors that were detected during inspection - public static IEnumerable ValidateModuleEx( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - Func isSelected, - Func isValid) + foreach (var dependentModuleMetadata in module.DependentModuleMetadatas.Where(x => x is not null)) { - var visited = new HashSet(); - foreach (var issue in ValidateModuleEx(modules, targetModule, visited, isSelected, isValid)) + if (dependentModuleMetadata is null) { - yield return issue; + yield return new ModuleDependencyNullIssue(module); + break; } + if (string.IsNullOrWhiteSpace(dependentModuleMetadata.Id)) + yield return new ModuleDependencyMissingIdIssue(module); } - - /// - /// Internal validation method that handles the core validation logic - /// - /// All available modules (in any order) - /// The module to validate - /// Set of modules already validated to prevent cycles - /// Function that determines if a module is enabled - /// Function that determines if a module is valid - /// Set this to true to also report errors in the target module's dependencies. e.g. Missing dependencies of dependencies. - /// Any errors that were detected during inspection - private static IEnumerable ValidateModuleEx( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - HashSet visitedModules, - Func isSelected, - Func isValid, - bool validateDependencies = true) + } + + /// + /// Validates module dependencies declarations using the new variant-based issue system + /// + /// All available modules (in any order) + /// The module whose dependency declarations are being validated + /// Any errors that were detected during inspection + public static IEnumerable ValidateModuleDependenciesDeclarationsEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule) + { + // Any Incompatible module is depended on + // TODO: Will a null Id break things? + foreach (var moduleId in targetModule.DependenciesToLoadDistinct() + .Select(x => x.Id) + .Intersect(targetModule.DependenciesIncompatiblesDistinct().Select(x => x.Id))) { - foreach (var issue in ValidateModuleCommonDataEx(targetModule)) - yield return issue; - - foreach (var issue in ValidateModuleDependenciesDeclarationsEx(modules, targetModule)) - yield return issue; - - foreach (var issue in ValidateModuleDependenciesEx(modules, targetModule, visitedModules, isSelected, isValid, validateDependencies)) - yield return issue; + yield return new ModuleDependencyConflictDependentAndIncompatibleIssue(targetModule, moduleId); } - /// - /// Validates a module's common data using the new variant-based issue system - /// - /// The module whose common data is being validated - /// Any errors that were detected during inspection - public static IEnumerable ValidateModuleCommonDataEx(ModuleInfoExtended module) + // Check raw metadata too + foreach (var dependency in targetModule.DependentModuleMetadatas + .Where(x => x is not null) + .Where(x => x.IsIncompatible && x.LoadType != LoadType.None)) { - if (string.IsNullOrWhiteSpace(module.Id)) - yield return new ModuleMissingIdIssue(module); - - if (string.IsNullOrWhiteSpace(module.Name)) - yield return new ModuleMissingNameIssue(module); + yield return new ModuleDependencyConflictDependentAndIncompatibleIssue(targetModule, dependency.Id); + } - foreach (var dependentModule in module.DependentModules.Where(x => x is not null)) + // LoadBeforeThis conflicts with LoadAfterThis + foreach (var module in targetModule.DependenciesLoadBeforeThisDistinct()) + { + if (targetModule.DependenciesLoadAfterThisDistinct() + .FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)) is { } metadata) { - if (dependentModule is null) - { - yield return new ModuleDependencyNullIssue(module); - break; - } - if (string.IsNullOrWhiteSpace(dependentModule.Id)) - yield return new ModuleDependencyMissingIdIssue(module); + yield return new ModuleDependencyConflictLoadBeforeAndAfterIssue(targetModule, metadata); } + } - foreach (var dependentModuleMetadata in module.DependentModuleMetadatas.Where(x => x is not null)) + // Circular dependency detection + foreach (var module in targetModule.DependenciesToLoadDistinct().Where(x => x.LoadType != LoadType.None)) + { + var moduleInfo = modules.FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)); + if (moduleInfo?.DependenciesToLoadDistinct() + .Where(x => x.LoadType != LoadType.None) + .FirstOrDefault(x => string.Equals(x.Id, targetModule.Id, StringComparison.Ordinal)) is { } metadata) { - if (dependentModuleMetadata is null) - { - yield return new ModuleDependencyNullIssue(module); - break; - } - if (string.IsNullOrWhiteSpace(dependentModuleMetadata.Id)) - yield return new ModuleDependencyMissingIdIssue(module); + if (metadata.LoadType == module.LoadType) + yield return new ModuleDependencyConflictCircularIssue(targetModule, metadata); } } - - /// - /// Validates module dependencies declarations using the new variant-based issue system - /// - /// All available modules (in any order) - /// The module whose dependency declarations are being validated - /// Any errors that were detected during inspection - public static IEnumerable ValidateModuleDependenciesDeclarationsEx( - IReadOnlyList modules, - ModuleInfoExtended targetModule) + } + + /// + /// Validates module dependencies using the new variant-based issue system + /// + /// All available modules (in any order) + /// The module whose dependencies are being validated + /// Set of modules already validated to prevent cycles + /// Function that determines if a module is enabled + /// Function that determines if a module is valid + /// Set this to true to also report errors in the target module's dependencies. e.g. Missing dependencies of dependencies. + /// Any errors that were detected during inspection + private static IEnumerable ValidateModuleDependenciesEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules, + Func isSelected, + Func isValid, + bool validateDependencies = true) + { + // Check that all dependencies are present + foreach (var metadata in targetModule.DependenciesToLoadDistinct()) { - // Any Incompatible module is depended on - // TODO: Will a null Id break things? - foreach (var moduleId in targetModule.DependenciesToLoadDistinct() - .Select(x => x.Id) - .Intersect(targetModule.DependenciesIncompatiblesDistinct().Select(x => x.Id))) - { - yield return new ModuleDependencyConflictDependentAndIncompatibleIssue(targetModule, moduleId); - } + // Ignore the check for Optional + if (metadata.IsOptional) continue; - // Check raw metadata too - foreach (var dependency in targetModule.DependentModuleMetadatas - .Where(x => x is not null) - .Where(x => x.IsIncompatible && x.LoadType != LoadType.None)) + if (!modules.Any(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal))) { - yield return new ModuleDependencyConflictDependentAndIncompatibleIssue(targetModule, dependency.Id); + if (metadata.Version != ApplicationVersion.Empty) + yield return new ModuleMissingExactVersionDependencyIssue(targetModule, metadata); + else if (metadata.VersionRange != ApplicationVersionRange.Empty) + yield return new ModuleMissingVersionRangeDependencyIssue(targetModule, metadata); + else + yield return new ModuleMissingUnversionedDependencyIssue(targetModule, metadata); + yield break; } + } - // LoadBeforeThis conflicts with LoadAfterThis - foreach (var module in targetModule.DependenciesLoadBeforeThisDistinct()) + // Check that the dependencies themselves have all dependencies present + if (validateDependencies) + { + var opts = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; + var dependencies = GetDependencies(modules, targetModule, visitedModules, opts).ToArray(); + foreach (var dependency in dependencies) { - if (targetModule.DependenciesLoadAfterThisDistinct() - .FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)) is { } metadata) + if (targetModule.DependenciesAllDistinct().FirstOrDefault(x => + string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is { } metadata) { - yield return new ModuleDependencyConflictLoadBeforeAndAfterIssue(targetModule, metadata); - } - } + // Not found, should not be possible + if (metadata is null) continue; + // Handle only direct dependencies + if (metadata.LoadType != LoadType.LoadBeforeThis) continue; + // Ignore the check for Optional + if (metadata.IsOptional) continue; + // Ignore the check for Incompatible + if (metadata.IsIncompatible) continue; - // Circular dependency detection - foreach (var module in targetModule.DependenciesToLoadDistinct().Where(x => x.LoadType != LoadType.None)) - { - var moduleInfo = modules.FirstOrDefault(x => string.Equals(x.Id, module.Id, StringComparison.Ordinal)); - if (moduleInfo?.DependenciesToLoadDistinct() - .Where(x => x.LoadType != LoadType.None) - .FirstOrDefault(x => string.Equals(x.Id, targetModule.Id, StringComparison.Ordinal)) is { } metadata) - { - if (metadata.LoadType == module.LoadType) - yield return new ModuleDependencyConflictCircularIssue(targetModule, metadata); + // Check missing dependency dependencies + if (modules.FirstOrDefault(x => string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is not { } dependencyModuleInfo) + { + yield return new ModuleDependencyMissingDependenciesIssue(targetModule, dependency.Id); + continue; + } + + // Check dependency correctness + if (!isValid(dependencyModuleInfo)) + yield return new ModuleDependencyValidationIssue(targetModule, dependency.Id); } } } - /// - /// Validates module dependencies using the new variant-based issue system - /// - /// All available modules (in any order) - /// The module whose dependencies are being validated - /// Set of modules already validated to prevent cycles - /// Function that determines if a module is enabled - /// Function that determines if a module is valid - /// Set this to true to also report errors in the target module's dependencies. e.g. Missing dependencies of dependencies. - /// Any errors that were detected during inspection - private static IEnumerable ValidateModuleDependenciesEx( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - HashSet visitedModules, - Func isSelected, - Func isValid, - bool validateDependencies = true) + // Check that the dependencies have the minimum required version set by DependedModuleMetadatas + foreach (var metadata in targetModule.DependenciesToLoadDistinct()) { - // Check that all dependencies are present - foreach (var metadata in targetModule.DependenciesToLoadDistinct()) - { - // Ignore the check for Optional - if (metadata.IsOptional) continue; + // Ignore the check for empty versions + if (metadata.Version == ApplicationVersion.Empty && metadata.VersionRange == ApplicationVersionRange.Empty) + continue; - if (!modules.Any(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal))) - { - if (metadata.Version != ApplicationVersion.Empty) - yield return new ModuleMissingExactVersionDependencyIssue(targetModule, metadata); - else if (metadata.VersionRange != ApplicationVersionRange.Empty) - yield return new ModuleMissingVersionRangeDependencyIssue(targetModule, metadata); - else - yield return new ModuleMissingUnversionedDependencyIssue(targetModule, metadata); - yield break; - } - } + // Dependency is loaded + if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) + continue; - // Check that the dependencies themselves have all dependencies present - if (validateDependencies) + if (metadata.Version != ApplicationVersion.Empty) { - var opts = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; - var dependencies = GetDependencies(modules, targetModule, visitedModules, opts).ToArray(); - foreach (var dependency in dependencies) + // dependedModuleMetadata.Version > dependedModule.Version + if (!metadata.IsOptional && (ApplicationVersionComparer.CompareStandard(metadata.Version, metadataModule.Version) > 0)) { - if (targetModule.DependenciesAllDistinct().FirstOrDefault(x => - string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is { } metadata) - { - // Not found, should not be possible - if (metadata is null) continue; - // Handle only direct dependencies - if (metadata.LoadType != LoadType.LoadBeforeThis) continue; - // Ignore the check for Optional - if (metadata.IsOptional) continue; - // Ignore the check for Incompatible - if (metadata.IsIncompatible) continue; - - // Check missing dependency dependencies - if (modules.FirstOrDefault(x => string.Equals(x.Id, dependency.Id, StringComparison.Ordinal)) is not { } dependencyModuleInfo) - { - yield return new ModuleDependencyMissingDependenciesIssue(targetModule, dependency.Id); - continue; - } - - // Check dependency correctness - if (!isValid(dependencyModuleInfo)) - yield return new ModuleDependencyValidationIssue(targetModule, dependency.Id); - } + yield return new ModuleVersionMismatchLessThanOrEqualSpecificIssue( + targetModule, + metadataModule, + metadata.Version); + continue; } } - // Check that the dependencies have the minimum required version set by DependedModuleMetadatas - foreach (var metadata in targetModule.DependenciesToLoadDistinct()) + if (metadata.VersionRange != ApplicationVersionRange.Empty && !metadata.IsOptional) { - // Ignore the check for empty versions - if (metadata.Version == ApplicationVersion.Empty && metadata.VersionRange == ApplicationVersionRange.Empty) - continue; - - // Dependency is loaded - if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) - continue; - - if (metadata.Version != ApplicationVersion.Empty) + // dependedModuleMetadata.Version > dependedModule.VersionRange.Min + // dependedModuleMetadata.Version < dependedModule.VersionRange.Max + if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Min, metadataModule.Version) > 0) { - // dependedModuleMetadata.Version > dependedModule.Version - if (!metadata.IsOptional && (ApplicationVersionComparer.CompareStandard(metadata.Version, metadataModule.Version) > 0)) - { - yield return new ModuleVersionMismatchLessThanOrEqualSpecificIssue( - targetModule, - metadataModule, - metadata.Version); - continue; - } + yield return new ModuleVersionMismatchLessThanRangeIssue( + targetModule, + metadataModule, + metadata.VersionRange); + continue; } - - if (metadata.VersionRange != ApplicationVersionRange.Empty && !metadata.IsOptional) + if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Max, metadataModule.Version) < 0) { - // dependedModuleMetadata.Version > dependedModule.VersionRange.Min - // dependedModuleMetadata.Version < dependedModule.VersionRange.Max - if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Min, metadataModule.Version) > 0) - { - yield return new ModuleVersionMismatchLessThanRangeIssue( - targetModule, - metadataModule, - metadata.VersionRange); - continue; - } - if (ApplicationVersionComparer.CompareStandard(metadata.VersionRange.Max, metadataModule.Version) < 0) - { - yield return new ModuleVersionMismatchGreaterThanRangeIssue( - targetModule, - metadataModule, - metadata.VersionRange); - continue; - } + yield return new ModuleVersionMismatchGreaterThanRangeIssue( + targetModule, + metadataModule, + metadata.VersionRange); + continue; } } + } - // Do not load this mod if an incompatible mod is selected - foreach (var metadata in targetModule.DependenciesIncompatiblesDistinct()) - { - // Dependency is loaded - if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule || - !isSelected(metadataModule)) continue; + // Do not load this mod if an incompatible mod is selected + foreach (var metadata in targetModule.DependenciesIncompatiblesDistinct()) + { + // Dependency is loaded + if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule || + !isSelected(metadataModule)) continue; - // If the incompatible mod is selected, this mod should be disabled - if (isSelected(metadataModule)) - yield return new ModuleIncompatibleIssue(targetModule, metadataModule.Id); - } + // If the incompatible mod is selected, this mod should be disabled + if (isSelected(metadataModule)) + yield return new ModuleIncompatibleIssue(targetModule, metadataModule.Id); + } - // If another mod declared incompatibility and is selected, disable this - if (validateDependencies) + // If another mod declared incompatibility and is selected, disable this + if (validateDependencies) + { + foreach (var module in modules) { - foreach (var module in modules) - { - // Ignore self - if (string.Equals(module.Id, targetModule.Id, StringComparison.Ordinal)) continue; - if (!isSelected(module)) continue; + // Ignore self + if (string.Equals(module.Id, targetModule.Id, StringComparison.Ordinal)) continue; + if (!isSelected(module)) continue; - foreach (var metadata in module.DependenciesIncompatiblesDistinct()) - { - if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; + foreach (var metadata in module.DependenciesIncompatiblesDistinct()) + { + if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; - // If the incompatible mod is selected, this mod is disabled - if (isSelected(module)) - yield return new ModuleIncompatibleIssue(targetModule, module.Id); - } + // If the incompatible mod is selected, this mod is disabled + if (isSelected(module)) + yield return new ModuleIncompatibleIssue(targetModule, module.Id); } } } + } - /// - /// Validates whether the load order is correctly sorted using the new variant-based issue system - /// - /// All available modules in they order they are expected to be loaded in - /// The module whose load order is being validated - /// Any errors that were detected during inspection - public static IEnumerable ValidateLoadOrderEx( - IReadOnlyList modules, - ModuleInfoExtended targetModule) - { - foreach (var issue in ValidateModuleCommonDataEx(targetModule)) - yield return issue; + /// + /// Validates whether the load order is correctly sorted using the new variant-based issue system + /// + /// All available modules in they order they are expected to be loaded in + /// The module whose load order is being validated + /// Any errors that were detected during inspection + public static IEnumerable ValidateLoadOrderEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule) + { + foreach (var issue in ValidateModuleCommonDataEx(targetModule)) + yield return issue; - var visited = new HashSet(); - foreach (var issue in ValidateLoadOrderEx(modules, targetModule, visited)) - yield return issue; - } + var visited = new HashSet(); + foreach (var issue in ValidateLoadOrderEx(modules, targetModule, visited)) + yield return issue; + } - /// - /// Validates whether the load order is correctly sorted using the new variant-based issue system - /// - /// All available modules in they order they are expected to be loaded in - /// The module whose load order is being validated - /// Set of modules already validated to prevent cycles - /// Any errors that were detected during inspection - /// - /// In this case 'load order' means the set of enabled mods. - /// - public static IEnumerable ValidateLoadOrderEx( - IReadOnlyList modules, - ModuleInfoExtended targetModule, - HashSet visitedModules) + /// + /// Validates whether the load order is correctly sorted using the new variant-based issue system + /// + /// All available modules in they order they are expected to be loaded in + /// The module whose load order is being validated + /// Set of modules already validated to prevent cycles + /// Any errors that were detected during inspection + /// + /// In this case 'load order' means the set of enabled mods. + /// + public static IEnumerable ValidateLoadOrderEx( + IReadOnlyList modules, + ModuleInfoExtended targetModule, + HashSet visitedModules) + { + var targetModuleIdx = CollectionsExtensions.IndexOf(modules, targetModule); + if (targetModuleIdx == -1) { - var targetModuleIdx = CollectionsExtensions.IndexOf(modules, targetModule); - if (targetModuleIdx == -1) - { - yield return new ModuleMissingIssue( - targetModule, - new ApplicationVersionRange(targetModule.Version, targetModule.Version)); - yield break; - } + yield return new ModuleMissingIssue( + targetModule, + new ApplicationVersionRange(targetModule.Version, targetModule.Version)); + yield break; + } - // Check that all dependencies are present - foreach (var metadata in targetModule.DependenciesToLoad().DistinctBy(x => x.Id)) - { - var metadataIdx = CollectionsExtensions.IndexOf(modules, x => - string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)); + // Check that all dependencies are present + foreach (var metadata in targetModule.DependenciesToLoad().DistinctBy(x => x.Id)) + { + var metadataIdx = CollectionsExtensions.IndexOf(modules, x => + string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)); - if (metadataIdx == -1) + if (metadataIdx == -1) + { + if (!metadata.IsOptional) { - if (!metadata.IsOptional) - { - if (metadata.Version != ApplicationVersion.Empty) - yield return new ModuleMissingExactVersionDependencyIssue(targetModule, metadata); - else if (metadata.VersionRange != ApplicationVersionRange.Empty) - yield return new ModuleMissingVersionRangeDependencyIssue(targetModule, metadata); - else - yield return new ModuleMissingUnversionedDependencyIssue(targetModule, metadata); - } - continue; + if (metadata.Version != ApplicationVersion.Empty) + yield return new ModuleMissingExactVersionDependencyIssue(targetModule, metadata); + else if (metadata.VersionRange != ApplicationVersionRange.Empty) + yield return new ModuleMissingVersionRangeDependencyIssue(targetModule, metadata); + else + yield return new ModuleMissingUnversionedDependencyIssue(targetModule, metadata); } + continue; + } - if (metadata.LoadType == LoadType.LoadBeforeThis && metadataIdx > targetModuleIdx) - { - yield return new ModuleDependencyNotLoadedBeforeIssue(targetModule, metadata.Id); - } + if (metadata.LoadType == LoadType.LoadBeforeThis && metadataIdx > targetModuleIdx) + { + yield return new ModuleDependencyNotLoadedBeforeIssue(targetModule, metadata.Id); + } - if (metadata.LoadType == LoadType.LoadAfterThis && metadataIdx < targetModuleIdx) - { - yield return new ModuleDependencyNotLoadedAfterIssue(targetModule, metadata.Id); - } + if (metadata.LoadType == LoadType.LoadAfterThis && metadataIdx < targetModuleIdx) + { + yield return new ModuleDependencyNotLoadedAfterIssue(targetModule, metadata.Id); } } + } - /// - /// Will enable the target module and all its dependencies. Assumes that validation was being done before - /// - public static void EnableModule(IReadOnlyCollection modules, ModuleInfoExtended targetModule, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) - { - var visited = new HashSet(); - EnableModuleInternal(modules, targetModule, visited, getSelected, setSelected, getDisabled, setDisabled); - } - private static void EnableModuleInternal(IReadOnlyCollection modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) - { - if (visitedModules.Contains(targetModule)) return; - visitedModules.Add(targetModule); + /// + /// Will enable the target module and all its dependencies. Assumes that validation was being done before + /// + public static void EnableModule(IReadOnlyCollection modules, ModuleInfoExtended targetModule, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) + { + var visited = new HashSet(); + EnableModuleInternal(modules, targetModule, visited, getSelected, setSelected, getDisabled, setDisabled); + } + private static void EnableModuleInternal(IReadOnlyCollection modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) + { + if (visitedModules.Contains(targetModule)) return; + visitedModules.Add(targetModule); - setSelected(targetModule, true); + setSelected(targetModule, true); - var opt = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; - var dependencies = GetDependencies(modules, targetModule, opt).ToArray(); + var opt = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; + var dependencies = GetDependencies(modules, targetModule, opt).ToArray(); - // Select all dependencies - foreach (var module in modules) + // Select all dependencies + foreach (var module in modules) + { + if (!getSelected(module) && dependencies.Any(d => string.Equals(d.Id, module.Id, StringComparison.Ordinal))) { - if (!getSelected(module) && dependencies.Any(d => string.Equals(d.Id, module.Id, StringComparison.Ordinal))) - { - EnableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); - } + EnableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); } + } - // Enable modules that are marked as LoadAfterThis - foreach (var metadata in targetModule.DependenciesLoadAfterThisDistinct()) - { - if (metadata.IsOptional) continue; + // Enable modules that are marked as LoadAfterThis + foreach (var metadata in targetModule.DependenciesLoadAfterThisDistinct()) + { + if (metadata.IsOptional) continue; - if (metadata.IsIncompatible) continue; + if (metadata.IsIncompatible) continue; - if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) continue; + if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) continue; - if (!getSelected(metadataModule)) - { - EnableModuleInternal(modules, metadataModule, visitedModules, getSelected, setSelected, getDisabled, setDisabled); - } + if (!getSelected(metadataModule)) + { + EnableModuleInternal(modules, metadataModule, visitedModules, getSelected, setSelected, getDisabled, setDisabled); } + } - // Deselect and disable any mod that is incompatible with this one - foreach (var metadata in targetModule.DependenciesIncompatiblesDistinct()) - { - if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) continue; + // Deselect and disable any mod that is incompatible with this one + foreach (var metadata in targetModule.DependenciesIncompatiblesDistinct()) + { + if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) continue; - if (getSelected(metadataModule)) - { - DisableModuleInternal(modules, metadataModule, visitedModules, getSelected, setSelected, getDisabled, setDisabled); - } - - setDisabled(metadataModule, true); + if (getSelected(metadataModule)) + { + DisableModuleInternal(modules, metadataModule, visitedModules, getSelected, setSelected, getDisabled, setDisabled); } - // Disable any mod that declares this mod as incompatible - foreach (var module in modules) + setDisabled(metadataModule, true); + } + + // Disable any mod that declares this mod as incompatible + foreach (var module in modules) + { + foreach (var metadata in module.DependenciesIncompatiblesDistinct()) { - foreach (var metadata in module.DependenciesIncompatiblesDistinct()) - { - if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; + if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; - if (getSelected(module)) - { - DisableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); - } - - // We need to re-check that everything is alright with the external dependency - setDisabled(module, getDisabled(module) | !AreDependenciesPresent(modules, module)); + if (getSelected(module)) + { + DisableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); } + + // We need to re-check that everything is alright with the external dependency + setDisabled(module, getDisabled(module) | !AreDependenciesPresent(modules, module)); } } + } - /// - /// Will disable the target module and all its dependencies. Assumes that validation was being done before - /// - public static void DisableModule(IReadOnlyCollection modules, ModuleInfoExtended targetModule, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) - { - var visited = new HashSet(); - DisableModuleInternal(modules, targetModule, visited, getSelected, setSelected, getDisabled, setDisabled); - } - private static void DisableModuleInternal(IReadOnlyCollection modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) - { - if (visitedModules.Contains(targetModule)) return; - visitedModules.Add(targetModule); + /// + /// Will disable the target module and all its dependencies. Assumes that validation was being done before + /// + public static void DisableModule(IReadOnlyCollection modules, ModuleInfoExtended targetModule, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) + { + var visited = new HashSet(); + DisableModuleInternal(modules, targetModule, visited, getSelected, setSelected, getDisabled, setDisabled); + } + private static void DisableModuleInternal(IReadOnlyCollection modules, ModuleInfoExtended targetModule, HashSet visitedModules, Func getSelected, Action setSelected, Func getDisabled, Action setDisabled) + { + if (visitedModules.Contains(targetModule)) return; + visitedModules.Add(targetModule); - setSelected(targetModule, false); + setSelected(targetModule, false); - var opt = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; + var opt = new ModuleSorterOptions { SkipOptionals = true, SkipExternalDependencies = true }; - // Vanilla check - // Deselect all modules that depend on this module if they are selected - foreach (var module in modules) + // Vanilla check + // Deselect all modules that depend on this module if they are selected + foreach (var module in modules) + { + var dependencies = GetDependencies(modules, module, opt); + if (getSelected(module) && dependencies.Any(d => string.Equals(d.Id, targetModule.Id, StringComparison.Ordinal))) { - var dependencies = ModuleUtilities.GetDependencies(modules, module, opt); - if (getSelected(module) && dependencies.Any(d => string.Equals(d.Id, targetModule.Id, StringComparison.Ordinal))) - { - DisableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); - } + DisableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); + } - // Enable modules that are marked as LoadAfterThis - foreach (var metadata in module.DependenciesLoadAfterThisDistinct()) - { - if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; + // Enable modules that are marked as LoadAfterThis + foreach (var metadata in module.DependenciesLoadAfterThisDistinct()) + { + if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; - if (metadata.IsOptional) continue; + if (metadata.IsOptional) continue; - if (metadata.IsIncompatible) continue; + if (metadata.IsIncompatible) continue; - if (getSelected(module)) - { - DisableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); - } - } - - // Check if any mod that declares this mod as incompatible can be Enabled - foreach (var metadata in module.DependenciesIncompatiblesDistinct()) + if (getSelected(module)) { - if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; - - // We need to re-check that everything is alright with the external dependency - setDisabled(module, getDisabled(module) & !ModuleUtilities.AreDependenciesPresent(modules, module)); + DisableModuleInternal(modules, module, visitedModules, getSelected, setSelected, getDisabled, setDisabled); } } - - // Enable for selection any mod that is incompatible with this one - foreach (var metadata in targetModule.DependenciesIncompatiblesDistinct()) + + // Check if any mod that declares this mod as incompatible can be Enabled + foreach (var metadata in module.DependenciesIncompatiblesDistinct()) { - if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) continue; - setDisabled(metadataModule, false); + if (!string.Equals(metadata.Id, targetModule.Id, StringComparison.Ordinal)) continue; + + // We need to re-check that everything is alright with the external dependency + setDisabled(module, getDisabled(module) & !AreDependenciesPresent(modules, module)); } } + + // Enable for selection any mod that is incompatible with this one + foreach (var metadata in targetModule.DependenciesIncompatiblesDistinct()) + { + if (modules.FirstOrDefault(x => string.Equals(x.Id, metadata.Id, StringComparison.Ordinal)) is not { } metadataModule) continue; + setDisabled(metadataModule, false); + } } } + #nullable restore #if !BANNERLORDBUTRMODULEMANAGER_ENABLE_WARNING #pragma warning restore