Documented the intended interface for the settings store; parseBooleanLiteral() is now a private method in the ConfigurationFileStore again; added some unit tests for the registry-based settings store
git-svn-id: file:///srv/devel/repo-conversion/nusu@318 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
88105794a9
commit
0d1051dd84
|
@ -176,75 +176,6 @@ namespace Nuclex.Support.Parsing {
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Tried to parse a boolean literal</summary>
|
||||
/// <param name="value">Value that will be parsed as a boolean literal</param>
|
||||
/// <returns>
|
||||
/// True or false if the value was a boolean literal, null if it wasn't
|
||||
/// </returns>
|
||||
public static bool? ParseBooleanLiteral(string value) {
|
||||
if(value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var stringSegment = new StringSegment(value, 0, value.Length);
|
||||
return ParseBooleanLiteral(ref stringSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tried to parse a boolean literal</summary>
|
||||
/// <param name="value">Value that will be parsed as a boolean literal</param>
|
||||
/// <returns>
|
||||
/// True or false if the value was a boolean literal, null if it wasn't
|
||||
/// </returns>
|
||||
public static bool? ParseBooleanLiteral(ref StringSegment value) {
|
||||
switch(value.Count) {
|
||||
|
||||
// If the string spells 'no', it is considered a boolean
|
||||
case 2: {
|
||||
bool isSpellingNo =
|
||||
((value.Text[value.Offset + 0] == 'n') || (value.Text[value.Offset + 0] == 'N')) &&
|
||||
((value.Text[value.Offset + 1] == 'o') || (value.Text[value.Offset + 1] == 'O'));
|
||||
return isSpellingNo ? new Nullable<bool>(false) : null;
|
||||
}
|
||||
|
||||
// If the string spells 'yes', it is considered a boolean
|
||||
case 3: {
|
||||
bool isSpellingYes =
|
||||
((value.Text[value.Offset + 0] == 'y') || (value.Text[value.Offset + 0] == 'Y')) &&
|
||||
((value.Text[value.Offset + 1] == 'e') || (value.Text[value.Offset + 1] == 'E')) &&
|
||||
((value.Text[value.Offset + 2] == 's') || (value.Text[value.Offset + 2] == 'S'));
|
||||
return isSpellingYes ? new Nullable<bool>(true) : null;
|
||||
}
|
||||
|
||||
// If the string spells 'true', it is considered a boolean
|
||||
case 4: {
|
||||
bool isSpellingTrue =
|
||||
((value.Text[value.Offset + 0] == 't') || (value.Text[value.Offset + 0] == 'T')) &&
|
||||
((value.Text[value.Offset + 1] == 'r') || (value.Text[value.Offset + 1] == 'R')) &&
|
||||
((value.Text[value.Offset + 2] == 'u') || (value.Text[value.Offset + 2] == 'U')) &&
|
||||
((value.Text[value.Offset + 3] == 'e') || (value.Text[value.Offset + 3] == 'E'));
|
||||
return isSpellingTrue ? new Nullable<bool>(true) : null;
|
||||
}
|
||||
|
||||
// If the string spells 'false', it is considered a boolean
|
||||
case 5: {
|
||||
bool isSpellingFalse =
|
||||
((value.Text[value.Offset + 0] == 'f') || (value.Text[value.Offset + 0] == 'F')) &&
|
||||
((value.Text[value.Offset + 1] == 'a') || (value.Text[value.Offset + 1] == 'A')) &&
|
||||
((value.Text[value.Offset + 2] == 'l') || (value.Text[value.Offset + 2] == 'L')) &&
|
||||
((value.Text[value.Offset + 3] == 's') || (value.Text[value.Offset + 3] == 'S')) &&
|
||||
((value.Text[value.Offset + 4] == 'e') || (value.Text[value.Offset + 4] == 'E'));
|
||||
return isSpellingFalse ? new Nullable<bool>(false) : null;
|
||||
}
|
||||
|
||||
// Anything else is not considered a boolean
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Parsing
|
||||
|
|
|
@ -253,13 +253,67 @@ namespace Nuclex.Support.Settings {
|
|||
}
|
||||
|
||||
// If it parses as a boolean literal, then it must be a boolean
|
||||
if(ParserHelper.ParseBooleanLiteral(ref value) != null) {
|
||||
if(parseBooleanLiteral(ref value) != null) {
|
||||
return typeof(bool);
|
||||
}
|
||||
|
||||
return typeof(string);
|
||||
}
|
||||
|
||||
/// <summary>Tried to parse a boolean literal</summary>
|
||||
/// <param name="value">Value that will be parsed as a boolean literal</param>
|
||||
/// <returns>
|
||||
/// True or false if the value was a boolean literal, null if it wasn't
|
||||
/// </returns>
|
||||
private static bool? parseBooleanLiteral(ref StringSegment value) {
|
||||
switch(value.Count) {
|
||||
|
||||
// If the string spells 'no', it is considered a boolean
|
||||
case 2: {
|
||||
bool isSpellingNo =
|
||||
((value.Text[value.Offset + 0] == 'n') || (value.Text[value.Offset + 0] == 'N')) &&
|
||||
((value.Text[value.Offset + 1] == 'o') || (value.Text[value.Offset + 1] == 'O'));
|
||||
return isSpellingNo ? new Nullable<bool>(false) : null;
|
||||
}
|
||||
|
||||
// If the string spells 'yes', it is considered a boolean
|
||||
case 3: {
|
||||
bool isSpellingYes =
|
||||
((value.Text[value.Offset + 0] == 'y') || (value.Text[value.Offset + 0] == 'Y')) &&
|
||||
((value.Text[value.Offset + 1] == 'e') || (value.Text[value.Offset + 1] == 'E')) &&
|
||||
((value.Text[value.Offset + 2] == 's') || (value.Text[value.Offset + 2] == 'S'));
|
||||
return isSpellingYes ? new Nullable<bool>(true) : null;
|
||||
}
|
||||
|
||||
// If the string spells 'true', it is considered a boolean
|
||||
case 4: {
|
||||
bool isSpellingTrue =
|
||||
((value.Text[value.Offset + 0] == 't') || (value.Text[value.Offset + 0] == 'T')) &&
|
||||
((value.Text[value.Offset + 1] == 'r') || (value.Text[value.Offset + 1] == 'R')) &&
|
||||
((value.Text[value.Offset + 2] == 'u') || (value.Text[value.Offset + 2] == 'U')) &&
|
||||
((value.Text[value.Offset + 3] == 'e') || (value.Text[value.Offset + 3] == 'E'));
|
||||
return isSpellingTrue ? new Nullable<bool>(true) : null;
|
||||
}
|
||||
|
||||
// If the string spells 'false', it is considered a boolean
|
||||
case 5: {
|
||||
bool isSpellingFalse =
|
||||
((value.Text[value.Offset + 0] == 'f') || (value.Text[value.Offset + 0] == 'F')) &&
|
||||
((value.Text[value.Offset + 1] == 'a') || (value.Text[value.Offset + 1] == 'A')) &&
|
||||
((value.Text[value.Offset + 2] == 'l') || (value.Text[value.Offset + 2] == 'L')) &&
|
||||
((value.Text[value.Offset + 3] == 's') || (value.Text[value.Offset + 3] == 'S')) &&
|
||||
((value.Text[value.Offset + 4] == 'e') || (value.Text[value.Offset + 4] == 'E'));
|
||||
return isSpellingFalse ? new Nullable<bool>(false) : null;
|
||||
}
|
||||
|
||||
// Anything else is not considered a boolean
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Configuration
|
||||
|
|
|
@ -156,7 +156,7 @@ namespace Nuclex.Support.Settings {
|
|||
Option option;
|
||||
if(containingCategory.OptionLookup.TryGetValue(optionName, out option)) {
|
||||
if(typeof(TValue) == typeof(bool)) {
|
||||
bool? boolean = ParserHelper.ParseBooleanLiteral(ref option.OptionValue);
|
||||
bool? boolean = parseBooleanLiteral(ref option.OptionValue);
|
||||
if(boolean.HasValue) {
|
||||
value = (TValue)(object)boolean.Value;
|
||||
return true;
|
||||
|
|
|
@ -24,6 +24,27 @@ using System.Collections.Generic;
|
|||
namespace Nuclex.Support.Settings {
|
||||
|
||||
/// <summary>Interface by which settings and configuration data can be accessed</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The intended usage pattern for options is for your application to simply read and
|
||||
/// write whatever options it needs using the type it expects them to be.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When you enumerate the options appearing under a category, the settings store will
|
||||
/// try to guess the likely type of an option, but this is not always accurate. For
|
||||
/// example, assigning the text 'true' to an option in a .cfg or .ini file could mean
|
||||
/// that the option is a boolean or it could simply be a coincidence. When you read
|
||||
/// this value as a boolean, the settings store will correctly convert it to a boolean,
|
||||
/// when you read it as a string, you will get back "true" in plain text.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Which types of values a settings store can accept can also change between different
|
||||
/// settings store implementations. The windows registry supports string and byte
|
||||
/// arrays whereas configuration files have no standardized way of holding these.
|
||||
/// Any property store must support a minimal subset including booleans, integers,
|
||||
/// floating point values and strings.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface ISettingsStore {
|
||||
|
||||
/// <summary>Enumerates the categories defined in the configuration</summary>
|
||||
|
|
|
@ -23,6 +23,10 @@ License along with this library
|
|||
using System;
|
||||
|
||||
using NUnit.Framework;
|
||||
using System.IO;
|
||||
using Microsoft.Win32;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Settings {
|
||||
|
||||
|
@ -30,7 +34,153 @@ namespace Nuclex.Support.Settings {
|
|||
[TestFixture]
|
||||
internal class WindowsRegistryStoreTest {
|
||||
|
||||
|
||||
#region class TestContext
|
||||
|
||||
/// <summary>Sets up a temporary registry key for the unit test</summary>
|
||||
private class TestContext : IDisposable {
|
||||
|
||||
/// <summary>Initializes a new test context</summary>
|
||||
public TestContext() {
|
||||
this.keyName = Guid.NewGuid().ToString();
|
||||
this.registryKey = Registry.CurrentUser.CreateSubKey(this.keyName);
|
||||
this.store = new WindowsRegistryStore(this.registryKey, writable: true);
|
||||
}
|
||||
|
||||
/// <summary>Immediately frees all resources owned by the test context</summary>
|
||||
public void Dispose() {
|
||||
if(this.store != null) {
|
||||
this.store.Dispose();
|
||||
this.store = null;
|
||||
this.registryKey = null;
|
||||
} else if(this.registryKey != null) {
|
||||
this.registryKey.Dispose();
|
||||
this.registryKey = null;
|
||||
}
|
||||
if(this.keyName != null) {
|
||||
Registry.CurrentUser.DeleteSubKeyTree(this.keyName);
|
||||
this.keyName = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Store created on a temporary registry key</summary>
|
||||
public WindowsRegistryStore Store {
|
||||
get { return this.store; }
|
||||
}
|
||||
|
||||
/// <summary>Name of the temporary registry key</summary>
|
||||
private string keyName;
|
||||
/// <summary>Registry key (ownership transfered to the store)</summary>
|
||||
private RegistryKey registryKey;
|
||||
/// <summary>Store that is accessing the registry key</summary>
|
||||
private WindowsRegistryStore store;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestContext
|
||||
|
||||
/// <summary>Verifies that new instances of the registry store can be created</summary>
|
||||
[Test]
|
||||
public void CanBeCreated() {
|
||||
Assert.That(
|
||||
() => { using(var context = new TestContext()) { } }, Throws.Nothing
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that booleans can be stored in the registry</summary>
|
||||
[Test]
|
||||
public void BooleansCanBeStored() {
|
||||
using(var context = new TestContext()) {
|
||||
context.Store.Set(null, "test", true);
|
||||
Assert.That(context.Store.Get<bool>(null, "test"), Is.True);
|
||||
|
||||
context.Store.Set(null, "test", false);
|
||||
Assert.That(context.Store.Get<bool>(null, "test"), Is.False);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that integers can be stored in the registry</summary>
|
||||
[Test]
|
||||
public void IntegersCanBeStored() {
|
||||
using(var context = new TestContext()) {
|
||||
context.Store.Set(null, "test", 123);
|
||||
Assert.That(context.Store.Get<int>(null, "test"), Is.EqualTo(123));
|
||||
|
||||
context.Store.Set(null, "test", 456);
|
||||
Assert.That(context.Store.Get<int>(null, "test"), Is.EqualTo(456));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that floats can be stored in the registry</summary>
|
||||
[Test]
|
||||
public void FloatsCanBeStored() {
|
||||
float testValue = float.Parse("123.456", CultureInfo.InvariantCulture);
|
||||
|
||||
using(var context = new TestContext()) {
|
||||
context.Store.Set(null, "test", testValue);
|
||||
Assert.That(context.Store.Get<float>(null, "test"), Is.EqualTo(testValue));
|
||||
|
||||
testValue = float.Parse("654.321", CultureInfo.InvariantCulture);
|
||||
|
||||
context.Store.Set(null, "test", testValue);
|
||||
Assert.That(context.Store.Get<float>(null, "test"), Is.EqualTo(testValue));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that strings can be stored in the registry</summary>
|
||||
[Test]
|
||||
public void StringsCanBeStored() {
|
||||
using(var context = new TestContext()) {
|
||||
context.Store.Set(null, "test", "hello world");
|
||||
Assert.That(context.Store.Get<string>(null, "test"), Is.EqualTo("hello world"));
|
||||
|
||||
context.Store.Set(null, "test", "world hello");
|
||||
Assert.That(context.Store.Get<string>(null, "test"), Is.EqualTo("world hello"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the subkeys of a registry key can be enumerated</summary>
|
||||
[Test]
|
||||
public void CategoriesCanBeEnumerated() {
|
||||
string[] names = new string[] { "one", "two", "three" };
|
||||
|
||||
using(var context = new TestContext()) {
|
||||
context.Store.Set(names[0], "sol", 21);
|
||||
context.Store.Set(names[1], "sol", 42);
|
||||
context.Store.Set(names[2], "sol", 84);
|
||||
|
||||
Assert.That(context.Store.EnumerateCategories(), Is.EquivalentTo(names));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the values under a registry subkey can be enumerated</summary>
|
||||
[Test]
|
||||
public void OptionsInCategoryCanBeEnumerated() {
|
||||
string[] names = new string[] { "one", "two", "three" };
|
||||
|
||||
using(var context = new TestContext()) {
|
||||
context.Store.Set("test", names[0], 1);
|
||||
context.Store.Set("test", names[1], 2);
|
||||
context.Store.Set("test", names[2], 3);
|
||||
|
||||
var optionInfos = new List<OptionInfo>(context.Store.EnumerateOptions("test"));
|
||||
Assert.That(optionInfos.Count, Is.EqualTo(3));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the values under a registry key can be enumerated</summary>
|
||||
[Test]
|
||||
public void RootOptionsCanBeEnumerated() {
|
||||
string[] names = new string[] { "one", "two", "three" };
|
||||
|
||||
using(var context = new TestContext()) {
|
||||
context.Store.Set(null, names[0], 1);
|
||||
context.Store.Set(null, names[1], 2);
|
||||
context.Store.Set(null, names[2], 3);
|
||||
|
||||
var optionInfos = new List<OptionInfo>(context.Store.EnumerateOptions(null));
|
||||
Assert.That(optionInfos.Count, Is.EqualTo(3));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -131,8 +131,13 @@ namespace Nuclex.Support.Settings {
|
|||
if(string.IsNullOrEmpty(category)) {
|
||||
return tryGetValueFromKey(this.rootKey, optionName, out value);
|
||||
} else {
|
||||
using(RegistryKey categoryKey = this.rootKey.OpenSubKey(category, this.writable)) {
|
||||
return tryGetValueFromKey(this.rootKey, optionName, out value);
|
||||
RegistryKey categoryKey = this.rootKey.OpenSubKey(category, this.writable);
|
||||
if(categoryKey == null) {
|
||||
value = default(TValue);
|
||||
return false;
|
||||
}
|
||||
using(categoryKey) {
|
||||
return tryGetValueFromKey(categoryKey, optionName, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +148,43 @@ namespace Nuclex.Support.Settings {
|
|||
/// <param name="optionName">Name of the option that will be saved</param>
|
||||
/// <param name="value">The value under which the option will be saved</param>
|
||||
public void Set<TValue>(string category, string optionName, TValue value) {
|
||||
throw new NotImplementedException();
|
||||
if(string.IsNullOrEmpty(category)) {
|
||||
setValue(this.rootKey, optionName, value);
|
||||
} else {
|
||||
RegistryKey categoryKey = this.rootKey.OpenSubKey(category, this.writable);
|
||||
if(categoryKey == null) {
|
||||
categoryKey = this.rootKey.CreateSubKey(category);
|
||||
}
|
||||
using(categoryKey) {
|
||||
setValue(categoryKey, optionName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Writes a setting to the registry</summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="registryKey"></param>
|
||||
/// <param name="optionName"></param>
|
||||
/// <param name="value"></param>
|
||||
private void setValue<TValue>(RegistryKey registryKey, string optionName, TValue value) {
|
||||
if(typeof(TValue) == typeof(int)) {
|
||||
registryKey.SetValue(optionName, value, RegistryValueKind.DWord);
|
||||
} else if(typeof(TValue) == typeof(long)) {
|
||||
registryKey.SetValue(optionName, value, RegistryValueKind.QWord);
|
||||
} else if(typeof(TValue) == typeof(bool)) {
|
||||
registryKey.SetValue(optionName, value, RegistryValueKind.DWord);
|
||||
} else if(typeof(TValue) == typeof(string)) {
|
||||
registryKey.SetValue(optionName, value, RegistryValueKind.String);
|
||||
} else if(typeof(TValue) == typeof(string[])) {
|
||||
registryKey.SetValue(optionName, value, RegistryValueKind.MultiString);
|
||||
} else if(typeof(TValue) == typeof(byte[])) {
|
||||
registryKey.SetValue(optionName, value, RegistryValueKind.Binary);
|
||||
} else {
|
||||
string valueAsString = (string)Convert.ChangeType(
|
||||
value, typeof(string), CultureInfo.InvariantCulture
|
||||
);
|
||||
registryKey.SetValue(optionName, valueAsString, RegistryValueKind.String);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes the option with the specified name</summary>
|
||||
|
@ -167,22 +208,6 @@ namespace Nuclex.Support.Settings {
|
|||
if(valueAsObject == null) {
|
||||
value = default(TValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(typeof(TValue) == typeof(bool)) {
|
||||
string valueAsString = (string)Convert.ChangeType(
|
||||
valueAsObject, typeof(string), CultureInfo.InvariantCulture
|
||||
);
|
||||
|
||||
bool? boolean = ParserHelper.ParseBooleanLiteral(valueAsString);
|
||||
if(boolean.HasValue) {
|
||||
value = (TValue)(object)boolean.Value;
|
||||
return true;
|
||||
} else {
|
||||
throw new FormatException(
|
||||
"The value '" + valueAsString + "' can not be intepreted as a boolean"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
value = (TValue)Convert.ChangeType(
|
||||
valueAsObject, typeof(TValue), CultureInfo.InvariantCulture
|
||||
|
@ -227,11 +252,6 @@ namespace Nuclex.Support.Settings {
|
|||
}
|
||||
}
|
||||
|
||||
// If it parses as a boolean literal, then it must be a boolean
|
||||
if(ParserHelper.ParseBooleanLiteral(value) != null) {
|
||||
return typeof(bool);
|
||||
}
|
||||
|
||||
return typeof(string);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user