diff --git a/Source/Settings/ConfigurationFileStore.Test.cs b/Source/Settings/ConfigurationFileStore.Test.cs index 9a0c39d..e5333d5 100644 --- a/Source/Settings/ConfigurationFileStore.Test.cs +++ b/Source/Settings/ConfigurationFileStore.Test.cs @@ -244,6 +244,144 @@ namespace Nuclex.Support.Settings { Assert.That(save(configurationFile), Contains.Substring("[general]")); } + /// + /// Verifies that accessing an option that doesn't exist throws an exception + /// + [Test] + public void AccessingNonExistingOptionThrowsException() { + var configurationFile = new ConfigurationFileStore(); + + Assert.That( + () => configurationFile.Get(null, "doesn't exist"), + Throws.Exception.AssignableTo() + ); + } + + /// + /// Verifies that accessing a category that doesn't exist throws an exception + /// + [Test] + public void AccessingNonExistingCategoryThrowsException() { + var configurationFile = new ConfigurationFileStore(); + configurationFile.Set(null, "test", "123"); + + Assert.That( + () => configurationFile.Get("doesn't exist", "test"), + Throws.Exception.AssignableTo() + ); + } + + /// + /// Verifies that it's possible to enumerate a category that doesn't exist + /// + [Test] + public void NonExistingCategoryCanBeEnumerated() { + var configurationFile = new ConfigurationFileStore(); + + Assert.That(configurationFile.EnumerateOptions("doesn't exist"), Is.Empty); + } + + /// + /// Verifies that it's possible to create an option without a value + /// + [Test] + public void ValuelessOptionsCanBeCreated() { + var configurationFile = new ConfigurationFileStore(); + + configurationFile.Set(null, "test", null); + Assert.That(configurationFile.Get(null, "test"), Is.Null.Or.Empty); + } + + /// + /// Verifies that it's possible to assign an empty value to an option + /// + [Test] + public void OptionValueCanBeCleared() { + string fileContents = "test = 123 ; comment"; + ConfigurationFileStore configurationFile = load(fileContents); + + configurationFile.Set(null, "test", null); + Assert.That(configurationFile.Get(null, "test"), Is.Null.Or.Empty); + } + + /// + /// Verifies that it's possible to assign an empty value to an option + /// + [Test] + public void OptionsCanBeRemoved() { + var configurationFile = new ConfigurationFileStore(); + configurationFile.Set(null, "test", null); + + Assert.That(configurationFile.Remove(null, "test"), Is.True); + } + + /// + /// Verifies that it's possible to assign an empty value to an option + /// + [Test] + public void RemovingOptionShiftsFollowingOptionsUp() { + string fileContents = + "first = 1\r\n" + + "second = 2"; + ConfigurationFileStore configurationFile = load(fileContents); + + Assert.That(configurationFile.Remove(null, "first"), Is.True); + configurationFile.Set(null, "second", "yay! first!"); + + Assert.That(save(configurationFile), Has.No.ContainsSubstring("1")); + Assert.That(save(configurationFile), Contains.Substring("second")); + Assert.That(save(configurationFile), Contains.Substring("yay! first!")); + } + + /// + /// Verifies that it's not an error to remove an option from a non-existing category + /// + [Test] + public void CanRemoveOptionFromNonExistingCategory() { + var configurationFile = new ConfigurationFileStore(); + Assert.That(configurationFile.Remove("nothing", "first"), Is.False); + } + + /// + /// Verifies that it's not an error to remove a non-existing option + /// + [Test] + public void CanRemoveNonExistingOption() { + var configurationFile = new ConfigurationFileStore(); + Assert.That(configurationFile.Remove(null, "first"), Is.False); + } + + /// + /// Verifies that it's not an error to remove a non-existing option + /// + [ + Test, + TestCase("text = world", typeof(string)), + TestCase("short=9", typeof(int)), + TestCase("integer = 123", typeof(int)), + TestCase("integer = 123 ", typeof(int)), + TestCase("float = 123.45", typeof(float)), + TestCase("float = 123.45 ", typeof(float)), + TestCase("boolean = true", typeof(bool)), + TestCase("boolean = false", typeof(bool)), + TestCase("boolean = yes", typeof(bool)), + TestCase("boolean = no", typeof(bool)) + ] + public void OptionTypeCanBeIdentified(string assignment, Type expectedType) { + ConfigurationFileStore configurationFile = load(assignment); + + OptionInfo info; + using( + IEnumerator enumerator = configurationFile.EnumerateOptions().GetEnumerator() + ) { + Assert.That(enumerator.MoveNext(), Is.True); + info = enumerator.Current; + Assert.That(enumerator.MoveNext(), Is.False); + } + + Assert.That(info.OptionType, Is.EqualTo(expectedType)); + } + /// Loads a configuration file from a string /// Contents of the configuration file /// The configuration file loaded from the string diff --git a/Source/Settings/ConfigurationFileStore.cs b/Source/Settings/ConfigurationFileStore.cs index d78c121..cd96236 100644 --- a/Source/Settings/ConfigurationFileStore.cs +++ b/Source/Settings/ConfigurationFileStore.cs @@ -103,6 +103,9 @@ namespace Nuclex.Support.Settings { /// An enumerable list of all options in the category public IEnumerable EnumerateOptions(string category = null) { Category enumeratedCategory = getCategoryByName(category); + if(enumeratedCategory == null) { + yield break; + } foreach(Option option in this.RootCategory.OptionLookup.Values) { OptionInfo optionInfo = new OptionInfo() { @@ -147,15 +150,16 @@ namespace Nuclex.Support.Settings { /// public bool TryGet(string category, string optionName, out TValue value) { Category containingCategory = getCategoryByName(category); - - Option option; - if(containingCategory.OptionLookup.TryGetValue(optionName, out option)) { - value = (TValue)Convert.ChangeType(option.OptionValue.ToString(), typeof(TValue)); - return true; - } else { - value = default(TValue); - return false; + if(containingCategory != null) { + Option option; + if(containingCategory.OptionLookup.TryGetValue(optionName, out option)) { + value = (TValue)Convert.ChangeType(option.OptionValue.ToString(), typeof(TValue)); + return true; + } } + + value = default(TValue); + return false; } /// Saves an option in the settings store @@ -164,30 +168,22 @@ namespace Nuclex.Support.Settings { /// Name of the option that will be saved /// The value under which the option will be saved public void Set(string category, string optionName, TValue value) { - Category targetCategory; - bool optionMightExist; - - if(string.IsNullOrEmpty(category)) { - targetCategory = this.RootCategory; - optionMightExist = true; - } else if(this.categoryLookup.TryGetValue(category, out targetCategory)) { - optionMightExist = true; - } else { - targetCategory = createCategory(category); - optionMightExist = false; - } - string valueAsString = (string)Convert.ChangeType( value, typeof(string), CultureInfo.InvariantCulture ); + Category targetCategory; + if(string.IsNullOrEmpty(category)) { + targetCategory = this.RootCategory; + } else if(!this.categoryLookup.TryGetValue(category, out targetCategory)) { + targetCategory = createCategory(category); + createOption(targetCategory, optionName, valueAsString); + return; + } + Option targetOption; - if(optionMightExist) { - if(targetCategory.OptionLookup.TryGetValue(optionName, out targetOption)) { - changeOption(targetCategory, targetOption, valueAsString); - } else { - createOption(targetCategory, optionName, valueAsString); - } + if(targetCategory.OptionLookup.TryGetValue(optionName, out targetOption)) { + changeOption(targetCategory, targetOption, valueAsString); } else { createOption(targetCategory, optionName, valueAsString); } @@ -198,7 +194,26 @@ namespace Nuclex.Support.Settings { /// Name of the option that will be removed /// True if the option was found and removed public bool Remove(string category, string optionName) { - throw new NotImplementedException(); + Category sourceCategory = getCategoryByName(category); + if(sourceCategory == null) { + return false; + } + + Option option; + if(!sourceCategory.OptionLookup.TryGetValue(optionName, out option)) { + return false; + } + + sourceCategory.Lines.RemoveAt(option.LineIndex); + sourceCategory.OptionLookup.Remove(optionName); + + foreach(Option shiftedOption in sourceCategory.OptionLookup.Values) { + if(shiftedOption.LineIndex > option.LineIndex) { + --shiftedOption.LineIndex; + } + } + + return true; } /// Looks a category up by its name @@ -212,9 +227,7 @@ namespace Nuclex.Support.Settings { if(string.IsNullOrEmpty(categoryName)) { category = this.RootCategory; } else if(!this.categoryLookup.TryGetValue(categoryName, out category)) { - throw new KeyNotFoundException( - "There is no category named '" + categoryName + "' in the configuration file" - ); + return null; } return category; @@ -281,7 +294,7 @@ namespace Nuclex.Support.Settings { string line = option.OptionValue.Text; { StringBuilder builder = new StringBuilder( - line.Length - option.OptionValue.Count + newValue.Length + line.Length - option.OptionValue.Count + newValueLength ); // Stuff before the value