diff --git a/LANCommander.SDK.Tests/IniHandling/ConfigurationTest.cs b/LANCommander.SDK.Tests/IniHandling/ConfigurationTest.cs new file mode 100644 index 00000000..13f445f2 --- /dev/null +++ b/LANCommander.SDK.Tests/IniHandling/ConfigurationTest.cs @@ -0,0 +1,73 @@ +using MadMilkman.Ini; + +namespace LANCommander.SDK.Tests.IniHandling +{ + using SectionKey = KeyValuePair>; + + public class ConfigurationTest + { + public required string Ini { get; set; } + + public IList RequiredSections { get; set; } = []; + public IList> RequiredSectionsCount { get; set; } = []; + + + public IList RequiredKeys { get; set; } = []; + public IList>>> RequiredKeysCount { get; set; } = []; + + public IList RequiredKeyValues { get; set; } = []; + + public IList UpdateKeyValues { get; set; } = []; + + + public class SectionKeyValue(string section) + { + public class KeyValue + { + public string Key { get; set; } + public string? Value { get; set; } + public IList? Values { get; set; } + + public KeyValue(string key, string? value) + { + Key = key; + Value = value; + } + + public KeyValue(string key, IList? values) + { + Key = key; + Values = values; + } + } + + public string Section { get; set; } = section; + public IniOptions? Options { get; set; } + + public SectionKeyValue(string section, IniOptions options) : this(section) => Options = options; + + public IList KeyValues { get; set; } = []; + } + + public class UpdateSectionKeyValue(string section) + { + public class UpdateKeyValue + { + public required string Key { get; set; } + public string? OldValue { get; set; } + public string? NewValue { get; set; } + + public IList? ExpectedValues { get; set; } + } + + public string Section { get; set; } = section; + public IniOptions? Options { get; set; } + + public UpdateSectionKeyValue(string section, IniOptions options) : this(section) => Options = options; + + public IList UpdateKeyValues { get; set; } = []; + public IList CheckKeyValues { get; set; } = []; + } + + } +} diff --git a/LANCommander.SDK.Tests/IniHandling/ConfigurationTests.cs b/LANCommander.SDK.Tests/IniHandling/ConfigurationTests.cs new file mode 100644 index 00000000..3239d34d --- /dev/null +++ b/LANCommander.SDK.Tests/IniHandling/ConfigurationTests.cs @@ -0,0 +1,240 @@ +using MadMilkman.Ini; + +namespace LANCommander.SDK.Tests.IniHandling +{ + using KeyValueInt = KeyValuePair; + using SectionKey = KeyValuePair>; + + public static class ConfigurationTests + { + public static ConfigurationTest Test_SingleSection = new ConfigurationTest + { + Ini = @"; last modified 1 April 2001 by John Doe +[owner] +name = John Doe +organization = Acme Widgets Inc.", + + RequiredSections = { "owner" }, + RequiredKeys = + { + new SectionKey("owner", ["name", "organization"]), + }, + RequiredKeyValues = + { + new ConfigurationTest.SectionKeyValue("owner") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("name", "John Doe"), + new ConfigurationTest.SectionKeyValue.KeyValue("organization", "Acme Widgets Inc."), + ], + }, + } + }; + + public static ConfigurationTest Test_TwoSections = new ConfigurationTest + { + Ini = @"[person1] +name=Mikey +age=4 + +[person2] +name=Becky +age=46", + + RequiredSections = { "person1", "person2" }, + RequiredKeys = + { + new SectionKey("person1", ["name", "age"]), + new SectionKey("person2", ["name", "age"]), + }, + RequiredKeyValues = + { + new ConfigurationTest.SectionKeyValue("person1") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("name", "Mikey"), + new ConfigurationTest.SectionKeyValue.KeyValue("age", "4"), + ], + }, + new ConfigurationTest.SectionKeyValue("person2") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("name", "Becky"), + new ConfigurationTest.SectionKeyValue.KeyValue("age", "46"), + ], + }, + } + }; + + public static ConfigurationTest Test_ArrayStatic = new ConfigurationTest + { + Ini = @"[Engine.Input] +Aliases[0]=(Command=""Button bFire | Fire"",Alias=Fire) +Aliases[1]=(Command=""Button bAltFire | AltFire"",Alias=AltFire) +Aliases[2]=(Command=""Axis aBaseY Speed=+300.0"",Alias=MoveForward) +0=SwitchWeapon 0 +1=SwitchWeapon 1 +2=SwitchWeapon 2 +3=SwitchWeapon 3 +" +, + + RequiredSections = { "Engine.Input" }, + RequiredKeys = + { + new SectionKey("Engine.Input", ["Aliases[0]", "Aliases[1]", "0", "1", "2", "3"]), + }, + RequiredKeysCount = + { + new KeyValuePair>("Engine.Input", [ + new KeyValueInt("", 7) + ]), + }, + RequiredKeyValues = + { + new ConfigurationTest.SectionKeyValue("Engine.Input") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Aliases[0]", "(Command=\"Button bFire | Fire\",Alias=Fire)"), + new ConfigurationTest.SectionKeyValue.KeyValue("0", "SwitchWeapon 0"), + ], + }, + }, + + UpdateKeyValues = + { + new ConfigurationTest.UpdateSectionKeyValue("Engine.Input") + { + UpdateKeyValues = [ + new ConfigurationTest.UpdateSectionKeyValue.UpdateKeyValue { Key = "Aliases[0]", NewValue = "(Command=\"Button bAltFire | AltFire\",Alias=AltFire)" }, + new ConfigurationTest.UpdateSectionKeyValue.UpdateKeyValue { Key = "Aliases[1]", NewValue = "(Command=\"Button bFire | Fire\",Alias=Fire)" }, + ], + CheckKeyValues = [ + new ConfigurationTest.SectionKeyValue("Engine.Input") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Aliases[0]", "(Command=\"Button bAltFire | AltFire\",Alias=AltFire)"), + ], + }, + ], + }, + } + }; + + public static ConfigurationTest Test_ArrayMultiValue = new ConfigurationTest + { + Ini = @"[Startup] +Package=Core +Package=Engine + +[User] +Name=Player +Team=1 +" +, + + RequiredSections = { "Startup" }, + RequiredKeys = + { + new SectionKey("Startup", ["Package"]), + }, + RequiredKeysCount = + { + new KeyValuePair>("Startup", [ + new KeyValueInt("Package", 2) + ]), + }, + RequiredKeyValues = + { + new ConfigurationTest.SectionKeyValue("Startup") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Package", "Core"), + new ConfigurationTest.SectionKeyValue.KeyValue("Package", "Engine"), + ], + }, + }, + + UpdateKeyValues = + { + new ConfigurationTest.UpdateSectionKeyValue("User") + { + UpdateKeyValues = [new ConfigurationTest.UpdateSectionKeyValue.UpdateKeyValue { Key = "Name", NewValue = "Admin" }], + CheckKeyValues = [ + new ConfigurationTest.SectionKeyValue("Startup") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Package", ["Core", "Engine"]), + ], + }, + new ConfigurationTest.SectionKeyValue("User") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Name", "Admin"), + new ConfigurationTest.SectionKeyValue.KeyValue("Team", "1"), + ], + }, + ], + }, + + new ConfigurationTest.UpdateSectionKeyValue("Startup") + { + Options = new IniOptions + { + KeyDuplicate = IniDuplication.Ignored, // merges, takes first value of a multi-value key + }, + UpdateKeyValues = [ + new ConfigurationTest.UpdateSectionKeyValue.UpdateKeyValue { + Key = "Package", + NewValue = "UI", + ExpectedValues = ["UI"], + }, + ], + CheckKeyValues = [ + new ConfigurationTest.SectionKeyValue("Startup") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Package", "UI"), + ], + }, + new ConfigurationTest.SectionKeyValue("User") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Name", "Player"), + new ConfigurationTest.SectionKeyValue.KeyValue("Team", "1"), + ], + }, + ], + }, + + new ConfigurationTest.UpdateSectionKeyValue("Startup") + { + Options = new IniOptions + { + KeyDuplicate = IniDuplication.Allowed, // keep multi-value keys + }, + UpdateKeyValues = [new ConfigurationTest.UpdateSectionKeyValue.UpdateKeyValue { + Key = "Package", + NewValue = "UI", + ExpectedValues = ["Core", "UI"], + }], + CheckKeyValues = [ + new ConfigurationTest.SectionKeyValue("Startup") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Package", ["Core", "UI"]), + ], + }, + new ConfigurationTest.SectionKeyValue("User") + { + KeyValues = [ + new ConfigurationTest.SectionKeyValue.KeyValue("Name", "Player"), + new ConfigurationTest.SectionKeyValue.KeyValue("Team", "1"), + ], + }, + ], + }, + } + }; + } +} diff --git a/LANCommander.SDK.Tests/IniHandling/IniHandlingTests_MadMilkman.cs b/LANCommander.SDK.Tests/IniHandling/IniHandlingTests_MadMilkman.cs new file mode 100644 index 00000000..1b52db29 --- /dev/null +++ b/LANCommander.SDK.Tests/IniHandling/IniHandlingTests_MadMilkman.cs @@ -0,0 +1,256 @@ +using System.Reflection; +using System.Text; +using LANCommander.SDK.Helpers; + +namespace LANCommander.SDK.Tests.IniHandling +{ + public class IniHandlingTests_MadMilkman + { + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void Parse(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = IniHelper.FromString(configuration.Ini); + Assert.NotNull(ini); + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSections(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = IniHelper.FromString(configuration.Ini); + + foreach (var requiredKey in configuration.RequiredSections) + { + Assert.True(ini.Sections.Contains(requiredKey)); + } + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionsCount(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = IniHelper.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredSectionsCount) + { + Assert.Equal(requiredSection.Value, ini.Sections.Count(x => string.Equals(x.Name, requiredSection.Key, StringComparison.InvariantCultureIgnoreCase))); + } + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionKeys(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = IniHelper.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredKeys) + { + var section = ini.Sections[requiredSection.Key]; + Assert.NotNull(section); + + foreach (var requiredKey in requiredSection.Value) + { + Assert.True(section.Keys.Contains(requiredKey)); + } + } + } + + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionKeysCount(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = IniHelper.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredKeysCount) + { + var section = ini.Sections[requiredSection.Key]; + Assert.NotNull(section); + + foreach (var requiredKeys in requiredSection.Value) + { + var count = (requiredKeys.Key == "") + ? section.Keys.Count + : section.Keys.Count(x => string.Equals(x.Name, requiredKeys.Key, StringComparison.InvariantCultureIgnoreCase)); + + Assert.Equal(requiredKeys.Value, count); + } + } + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionKeyValues(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = IniHelper.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredKeyValues) + { + var section = ini.Sections[requiredSection.Section]; + Assert.NotNull(section); + + foreach (var requiredKeyValue in requiredSection.KeyValues) + { + var match = section.Keys.FirstOrDefault(key => { + return string.Equals(key.Name, requiredKeyValue.Key, StringComparison.InvariantCultureIgnoreCase) + && string.Equals(key.Value, requiredKeyValue.Value, StringComparison.InvariantCulture); + }); + + Assert.NotNull(match); + } + } + } + + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void UpdateSectionKeyValues(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = IniHelper.FromString(configuration.Ini); + Assert.NotNull(ini); + + foreach (var requiredSection in configuration.UpdateKeyValues) + { + var options = requiredSection.Options ?? IniHelper.DefaultIniOptions; + ini = IniHelper.FromString(configuration.Ini, options); + var section = ini.Sections[requiredSection.Section]; + Assert.NotNull(section); + + foreach (var updateKeyValue in requiredSection.UpdateKeyValues) + { + bool found = false; + + foreach (var iniKey in section.Keys.Reverse()) + { + if (string.Equals(iniKey.Name, updateKeyValue.Key, StringComparison.InvariantCultureIgnoreCase)) + { + if (updateKeyValue.OldValue == null || string.Equals(updateKeyValue.Key, iniKey.Value, StringComparison.InvariantCultureIgnoreCase)) + { + iniKey.Value = updateKeyValue.NewValue; + Assert.Equal(iniKey.Value, updateKeyValue.NewValue); + found = true; + break; + } + } + } + + Assert.True(found); + } + + // check if values are stored properly + + foreach (var requiredKeyValue in requiredSection.UpdateKeyValues) + { + IList> searches = []; + searches.Add(new KeyValuePair(requiredKeyValue.Key, requiredKeyValue.NewValue)); + + if (requiredKeyValue.ExpectedValues != null) + { + foreach (var expected in requiredKeyValue.ExpectedValues) + { + searches.Add(new KeyValuePair(requiredKeyValue.Key, expected)); + } + } + + foreach (var search in searches) + { + var match = section.Keys.FirstOrDefault(key => + { + return string.Equals(key.Name, requiredKeyValue.Key, StringComparison.InvariantCultureIgnoreCase) + && string.Equals(key.Value, requiredKeyValue.NewValue, StringComparison.InvariantCulture); + }); + + Assert.NotNull(match); + } + } + + // compare newly generated INI + + string iniContentNew = IniHelper.ToString(ini, Encoding.Default); + var iniNew = IniHelper.FromString(iniContentNew, options); + Assert.NotNull(iniNew); + + foreach (var checkSection in requiredSection.CheckKeyValues) + { + var sectionNew = iniNew.Sections[checkSection.Section]; + Assert.NotNull(sectionNew); + + foreach (var checkKeyValue in checkSection.KeyValues) + { + IList> searches = []; + if (checkKeyValue.Values != null) + { + foreach (var expected in checkKeyValue.Values) + { + searches.Add(new KeyValuePair(checkKeyValue.Key, expected)); + } + } + else + { + searches.Add(new KeyValuePair(checkKeyValue.Key, checkKeyValue.Value)); + } + + foreach (var search in searches) + { + var match = sectionNew.Keys.FirstOrDefault(key => + { + return string.Equals(key.Name, search.Key, StringComparison.InvariantCultureIgnoreCase) + && string.Equals(key.Value, search.Value, StringComparison.InvariantCulture); + }); + + Assert.NotNull(match); + } + } + } + } + } + + public static ConfigurationTest GetConfiguration(string configurationName) + { + Type configTestsType = typeof(ConfigurationTests); + FieldInfo? field = configTestsType?.GetField(configurationName, BindingFlags.Public | BindingFlags.Static); + + if (field == null) + { + throw new Exception($"The configuration '{configurationName}' does not exist."); + } + + ConfigurationTest? configurationTest = field.GetValue(null) as ConfigurationTest; + if (configurationTest == null) + { + throw new Exception($"The configuration '{configurationName}' is not of type ConfigurationTest or is null."); + } + + return configurationTest; + } + } +} diff --git a/LANCommander.SDK.Tests/IniHandling/IniHandlingTests_PeanutButter.cs b/LANCommander.SDK.Tests/IniHandling/IniHandlingTests_PeanutButter.cs new file mode 100644 index 00000000..59e7a38c --- /dev/null +++ b/LANCommander.SDK.Tests/IniHandling/IniHandlingTests_PeanutButter.cs @@ -0,0 +1,207 @@ +using System.Reflection; +using PeanutButter.INI; + +namespace LANCommander.SDK.Tests.IniHandling +{ + public class IniHandlingTests_PeanutButter + { + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void Parse(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = INIFile.FromString(configuration.Ini); + Assert.NotNull(ini); + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSections(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = INIFile.FromString(configuration.Ini); + + foreach (var requiredKey in configuration.RequiredSections) + { + Assert.True(ini.HasSection(requiredKey)); + } + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionsCount(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = INIFile.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredSectionsCount) + { + Assert.Equal(requiredSection.Value, ini.AllSections.Count(x => string.Equals(x, requiredSection.Key, StringComparison.InvariantCultureIgnoreCase))); + } + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionKeys(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = INIFile.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredKeys) + { + var section = ini.GetSection(requiredSection.Key); + Assert.NotNull(section); + + foreach (var requiredKey in requiredSection.Value) + { + Assert.True(section.ContainsKey(requiredKey)); + } + } + } + + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionKeysCount(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = INIFile.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredKeysCount) + { + var section = ini.GetSection(requiredSection.Key); + Assert.NotNull(section); + + foreach (var requiredKeys in requiredSection.Value) + { + Assert.Equal(requiredKeys.Value, section.Keys.Count(x => string.Equals(x, requiredKeys.Key, StringComparison.InvariantCultureIgnoreCase))); + } + } + } + + [Theory] + [InlineData(nameof(ConfigurationTests.Test_SingleSection))] + [InlineData(nameof(ConfigurationTests.Test_TwoSections))] + [InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void CheckSectionKeyValues(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = INIFile.FromString(configuration.Ini); + + foreach (var requiredSection in configuration.RequiredKeyValues) + { + var section = ini.GetSection(requiredSection.Section); + Assert.NotNull(section); + + foreach (var requiredKeyValue in requiredSection.KeyValues) + { + var match = section.FirstOrDefault(key => + { + return string.Equals(key.Key, requiredKeyValue.Key, StringComparison.InvariantCultureIgnoreCase) + && string.Equals(key.Value, requiredKeyValue.Value, StringComparison.InvariantCulture); + }); + + Assert.NotEqual(match, default); + } + } + } + + [Theory] + //[InlineData(nameof(ConfigurationTests.Test_SingleSection))] + //[InlineData(nameof(ConfigurationTests.Test_TwoSections))] + //[InlineData(nameof(ConfigurationTests.Test_ArrayStatic))] + [InlineData(nameof(ConfigurationTests.Test_ArrayMultiValue))] + public void UpdateSectionKeyValues(string configurationName) + { + ConfigurationTest configuration = GetConfiguration(configurationName); + var ini = INIFile.FromString(configuration.Ini); + Assert.NotNull(ini); + + foreach (var requiredSection in configuration.UpdateKeyValues) + { + var section = ini.GetSection(requiredSection.Section); + Assert.NotNull(section); + + foreach (var updateKeyValue in requiredSection.UpdateKeyValues) + { + ini.SetValue(requiredSection.Section, updateKeyValue.Key, updateKeyValue.NewValue); + var value = ini.GetValue(requiredSection.Section, updateKeyValue.Key); + Assert.True(updateKeyValue.NewValue == null || string.Equals(updateKeyValue.NewValue, value, StringComparison.InvariantCulture)); + } + + // compare newly generated INI + string iniContentNew = ini.ToString(); + var iniNew = INIFile.FromString(iniContentNew); + Assert.NotNull(iniNew); + + foreach (var checkSection in requiredSection.CheckKeyValues) + { + var sectionNew = iniNew.GetSection(checkSection.Section); + Assert.NotNull(sectionNew); + + foreach (var checkKeyValue in checkSection.KeyValues) + { + IList> searches = []; + if (checkKeyValue.Values != null) + { + foreach (var expected in checkKeyValue.Values) + { + searches.Add(new KeyValuePair(checkKeyValue.Key, expected)); + } + } + else + { + searches.Add(new KeyValuePair(checkKeyValue.Key, checkKeyValue.Value)); + } + + foreach (var search in searches) + { + var match = section.FirstOrDefault(key => + { + return string.Equals(key.Key, search.Key, StringComparison.InvariantCultureIgnoreCase) + && string.Equals(key.Value, search.Value, StringComparison.InvariantCulture); + }); + + Assert.NotEqual(match, default); + } + } + } + } + } + + public static ConfigurationTest GetConfiguration(string configurationName) + { + Type configTestsType = typeof(ConfigurationTests); + FieldInfo? field = configTestsType?.GetField(configurationName, BindingFlags.Public | BindingFlags.Static); + + if (field == null) + { + throw new Exception($"The configuration '{configurationName}' does not exist."); + } + + ConfigurationTest? configurationTest = field.GetValue(null) as ConfigurationTest; + if (configurationTest == null) + { + throw new Exception($"The configuration '{configurationName}' is not of type ConfigurationTest or is null."); + } + + return configurationTest; + } + } +} diff --git a/LANCommander.SDK/Helpers/IniHelper.cs b/LANCommander.SDK/Helpers/IniHelper.cs new file mode 100644 index 00000000..403125c7 --- /dev/null +++ b/LANCommander.SDK/Helpers/IniHelper.cs @@ -0,0 +1,49 @@ +using System.IO; +using System.Text; +using MadMilkman.Ini; + +namespace LANCommander.SDK.Helpers +{ + public static class IniHelper + { + public static readonly IniOptions DefaultIniOptions = new() + { + }; + + public static IniFile FromString(string iniFileContent) + { + return FromString(iniFileContent, DefaultIniOptions); + } + + public static IniFile FromString(string iniFileContent, IniOptions options) + { + IniFile file = new(options); + + using (var stream = new MemoryStream(options.Encoding.GetBytes(iniFileContent))) + file.Load(stream); + + return file; + } + + public static string ToString(IniFile file, IniOptions options) + { + return ToString(file, options.Encoding); + } + + public static string ToString(IniFile file, Encoding encoding) + { + string iniFileContent; + using (var stream = new MemoryStream()) + using (var reader = new StreamReader(stream, encoding)) + { + file.Save(stream); + stream.Flush(); + stream.Seek(0, SeekOrigin.Begin); + + iniFileContent = reader.ReadToEnd(); + } + + return iniFileContent; + } + } +} diff --git a/LANCommander.SDK/LANCommander.SDK.csproj b/LANCommander.SDK/LANCommander.SDK.csproj index cec0d5d1..2e1eb300 100644 --- a/LANCommander.SDK/LANCommander.SDK.csproj +++ b/LANCommander.SDK/LANCommander.SDK.csproj @@ -19,6 +19,7 @@ + diff --git a/LANCommander.SDK/PowerShell/Cmdlets/Update-IniValue.cs b/LANCommander.SDK/PowerShell/Cmdlets/Update-IniValue.cs index 6a4d9066..ae9a0643 100644 --- a/LANCommander.SDK/PowerShell/Cmdlets/Update-IniValue.cs +++ b/LANCommander.SDK/PowerShell/Cmdlets/Update-IniValue.cs @@ -1,7 +1,11 @@ -using PeanutButter.INI; +using MadMilkman.Ini; +using PeanutButter.INI; using System; using System.IO; +using System.Linq; using System.Management.Automation; +using System.Text; +using YamlDotNet.Core.Tokens; namespace LANCommander.SDK.PowerShell.Cmdlets { @@ -10,30 +14,127 @@ namespace LANCommander.SDK.PowerShell.Cmdlets public class UpdateIniValueCmdlet : Cmdlet { [Parameter(Mandatory = true, Position = 0)] + [Alias("s")] public string Section { get; set; } [Parameter(Mandatory = true, Position = 1)] + [Alias("k")] public string Key { get; set; } [Parameter(Mandatory = true, Position = 2)] + [Alias("v")] public string Value { get; set; } [Parameter(Mandatory = true, Position = 3)] + [Alias("f")] public string FilePath { get; set; } [Parameter(Mandatory = false)] + [Alias("wrap", "quotes")] public bool WrapValueInQuotes { get; set; } = false; + [Parameter(Mandatory = false)] + [Alias("add")] + public bool UpdateOrAdd { get; set; } = true; + + [Alias("remove-only")] + [Parameter(Mandatory = false)] + public SwitchParameter OnlyRemove { get; set; } = false; + + [Parameter(Mandatory = false)] + public SwitchParameter Clear { get; set; } = false; + + [Alias("append")] + [Parameter(Mandatory = false)] + public SwitchParameter AlwaysAppend { get; set; } = false; + + [Parameter(Mandatory = false)] + [Alias("insert")] + public int? InsertIndex { get; set; } = null; + + [Alias("keepkey", "keydup")] + [Parameter(Mandatory = false)] + public bool KeepKeyDuplicates { get; set; } = true; + + [Alias("keepsec", "secdup")] + [Parameter(Mandatory = false)] + public bool KeepSectionDuplicates { get; set; } = true; + + [Alias("encoding", "enc")] + [Parameter(Mandatory = false)] + public string Codepage { get; set; } = "Latin"; + + protected Encoding GetEncoding() + { + string page = Codepage?.ToLower(); + switch (page) + { + case "uft8": return Encoding.UTF8; + case "latin": + case "latin1": + case "ISO-8859-1": + return Encoding.Latin1; + case "asci": + case "ascii": + return Encoding.ASCII; + case "unicode": + return Encoding.Unicode; + } + + return Encoding.Default; + } + protected override void ProcessRecord() { - if (File.Exists(FilePath)) - { - var ini = new INIFile(FilePath); + if (!File.Exists(FilePath)) + return; - ini.SetValue(Section, Key, Value); - ini.WrapValueInQuotes = WrapValueInQuotes; - ini.Persist(); + var iniOptions = new IniOptions() + { + Encoding = GetEncoding(), + SectionDuplicate = KeepSectionDuplicates ? IniDuplication.Allowed : IniDuplication.Ignored, + KeyDuplicate = KeepKeyDuplicates ? IniDuplication.Allowed : IniDuplication.Ignored, + }; + var ini = new IniFile(iniOptions); + ini.Load(FilePath); + + var iniSection = ini.Sections[Section]; + if (iniSection == null) + return; + + bool keyMatcher(IniKey x) => string.Equals(x.Name, Key, StringComparison.OrdinalIgnoreCase); + + if (OnlyRemove || Clear) + { + var list = iniSection.Keys.Where(keyMatcher).ToList(); + list.ForEach(x => iniSection.Keys.Remove(x)); } + + if (!OnlyRemove) + { + // assuming most of the engines interpret INI files from top to bottom using the last value of a multiple existing key, we update the last found key + var firstMatch = iniSection.Keys.LastOrDefault(keyMatcher); + if (AlwaysAppend.ToBool() || (UpdateOrAdd && firstMatch == null)) + { + if (InsertIndex.HasValue && InsertIndex.Value >= 0) + iniSection.Keys.Insert(Math.Clamp(InsertIndex.Value, 0, iniSection.Keys.Count - 1), Key, Value); + else + iniSection.Keys.Add(Key, Value); + } + else if (firstMatch != null) + { + var insertIndex = (InsertIndex.HasValue && InsertIndex.Value >= 0) ? Math.Clamp(InsertIndex.Value, 0, iniSection.Keys.Count - 1) : -1; + if (insertIndex >= 0) + { + iniSection.Keys.Remove(firstMatch); + iniSection.Keys.Insert(insertIndex, firstMatch); + } + firstMatch.Value = Value; + } + // no else, as it should be possible to only update an existing value without adding a new one + } + + ini.Save(FilePath); } } }