Browse Source

Dynamo Object Support

pull/1/head
Sebastian Stehle 9 years ago
parent
commit
7df53c706c
  1. 2
      PinkParrot.sln.DotSettings
  2. 133
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs
  3. 11
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs
  4. 32
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs
  5. 82
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs

2
PinkParrot.sln.DotSettings

@ -31,6 +31,8 @@
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue"></s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_MULTIPLICATIVE_OP/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/QUOTE_STYLE/@EntryValue">SingleQuoted</s:String>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD;
$FILENAME$&#xD;

133
src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PropertiesBagTests.cs

@ -21,6 +21,12 @@ namespace PinkParrot.Infrastructure
{
private readonly CultureInfo c = CultureInfo.InvariantCulture;
private readonly PropertiesBag bag = new PropertiesBag();
private readonly dynamic dynamicBag;
public PropertiesBagTest()
{
dynamicBag = bag;
}
[Fact]
public void Should_return_false_when_renaming_unknown_property()
@ -63,8 +69,8 @@ namespace PinkParrot.Infrastructure
bag.Set("Key2", 1);
Assert.Equal(2, bag.Count);
Assert.Equal(new[] { "Key1", "Key2" }, bag.PropertyNames.ToArray());
Assert.Equal(new[] { "Key1", "Key2" }, bag.Properties.Keys.ToArray());
Assert.Equal(new[] {"Key1", "Key2"}, bag.PropertyNames.ToArray());
Assert.Equal(new[] {"Key1", "Key2"}, bag.Properties.Keys.ToArray());
}
[Fact]
@ -93,10 +99,24 @@ namespace PinkParrot.Infrastructure
Assert.False(bag.Contains("KEY"));
}
[Fact]
public void Should_set_value_as_dynamic()
{
dynamicBag.Key = 456;
Assert.Equal(456, (int)dynamicBag.Key);
}
[Fact]
public void Should_throw_when_setting_value_with_invalid_type_dynamically()
{
Assert.Throws<InvalidOperationException>(() => dynamicBag.Key = (byte)123);
}
[Fact]
public void Should_throw_when_setting_value_with_invalid_type()
{
Assert.Throws<ArgumentException>(() => bag.Set("Key", (byte)1));
Assert.Throws<InvalidOperationException>(() => bag.Set("Key", (byte)1));
}
[Fact]
@ -168,7 +188,7 @@ namespace PinkParrot.Infrastructure
[Fact]
public void Should_convert_from_instant_string()
{
var time = SystemClock.Instance.GetCurrentInstant();
var time = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds());
bag.Set("Key", time.ToString());
@ -200,7 +220,7 @@ namespace PinkParrot.Infrastructure
{
bag.Set("Key", true);
AssertBoolean();
AssertBoolean(true);
}
[Fact]
@ -208,7 +228,7 @@ namespace PinkParrot.Infrastructure
{
bag.Set("Key", "true");
AssertBoolean();
AssertBoolean(true);
}
[Fact]
@ -216,66 +236,109 @@ namespace PinkParrot.Infrastructure
{
bag.Set("Key", 1);
AssertBoolean();
AssertBoolean(true);
}
[Fact]
public void Should_throw_when_converting_instant_to_number()
public void Should_convert_boolean_to_truthy_number_string()
{
bag.Set("Key", SystemClock.Instance.GetCurrentInstant());
bag.Set("Key", "1");
Assert.Throws<InvalidCastException>(() => bag["Key"].ToGuid(CultureInfo.InvariantCulture));
AssertBoolean(true);
}
[Fact]
public void Should_return_default_when_property_value_is_null()
public void Should_convert_boolean_to_falsy_number_string()
{
bag.Set("Key", null);
bag.Set("Key", "0");
Assert.Equal(null, bag["Key"].ToString());
AssertBoolean(false);
}
[Fact]
public void Should_throw_when_converting_instant_to_number()
{
bag.Set("Key", SystemClock.Instance.GetCurrentInstant());
Assert.Equal(0f, bag["Key"].ToSingle(CultureInfo.CurrentCulture));
Assert.Equal(0d, bag["Key"].ToDouble(CultureInfo.CurrentCulture));
Assert.Equal(0L, bag["Key"].ToInt64(CultureInfo.CurrentCulture));
Assert.Equal(0, bag["Key"].ToInt32(CultureInfo.CurrentCulture));
Assert.Throws<InvalidCastException>(() => bag["Key"].ToGuid(CultureInfo.InvariantCulture));
}
Assert.Equal(false, bag["Key"].ToBoolean(CultureInfo.CurrentCulture));
private void AssertNumber()
{
AssertInt32(123);
AssertInt64(123);
AssertSingle(123);
AssertDouble(123);
}
Assert.Equal(new Guid(), bag["Key"].ToGuid(CultureInfo.CurrentCulture));
private void AssertString(string expected)
{
Assert.Equal(expected, bag["Key"].ToString());
Assert.Equal(new Instant(), bag["Key"].ToInstant(CultureInfo.CurrentCulture));
Assert.Equal(expected, (string)dynamicBag.Key);
}
private void AssertBoolean()
private void AssertBoolean(bool expected)
{
Assert.True(bag["Key"].ToBoolean(c));
Assert.True(bag["Key"].ToNullableBoolean(c));
Assert.Equal(expected, bag["Key"].ToBoolean(c));
Assert.Equal(expected, bag["Key"].ToNullableBoolean(c));
Assert.Equal(expected, (bool)dynamicBag.Key);
Assert.Equal(expected, (bool?)dynamicBag.Key);
}
private void AssertInstant(Instant expected)
{
Assert.Equal(expected.ToUnixTimeSeconds(), bag["Key"].ToInstant(c).ToUnixTimeSeconds());
Assert.Equal(expected.ToUnixTimeSeconds(), bag["Key"].ToNullableInstant(c).Value.ToUnixTimeSeconds());
Assert.Equal(expected, bag["Key"].ToInstant(c));
Assert.Equal(expected, bag["Key"].ToNullableInstant(c).Value);
Assert.Equal(expected, (Instant)dynamicBag.Key);
Assert.Equal(expected, (Instant?)dynamicBag.Key);
}
private void AssertGuid(Guid expected)
{
Assert.Equal(expected, bag["Key"].ToGuid(c));
Assert.Equal(expected, bag["Key"].ToNullableGuid(c));
Assert.Equal(expected, (Guid)dynamicBag.Key);
Assert.Equal(expected, (Guid?)dynamicBag.Key);
}
private void AssertNumber()
private void AssertDouble(double expected)
{
Assert.Equal(expected, bag["Key"].ToDouble(c));
Assert.Equal(expected, bag["Key"].ToNullableDouble(c));
Assert.Equal(expected, (double)dynamicBag.Key);
Assert.Equal(expected, (double?)dynamicBag.Key);
}
private void AssertSingle(float expected)
{
Assert.Equal(expected, bag["Key"].ToSingle(c));
Assert.Equal(expected, bag["Key"].ToNullableSingle(c));
Assert.Equal(expected, (float)dynamicBag.Key);
Assert.Equal(expected, (float?)dynamicBag.Key);
}
private void AssertInt64(long expected)
{
Assert.Equal(expected, bag["Key"].ToInt64(c));
Assert.Equal(expected, bag["Key"].ToNullableInt64(c));
Assert.Equal(expected, (long)dynamicBag.Key);
Assert.Equal(expected, (long?)dynamicBag.Key);
}
private void AssertInt32(int expected)
{
Assert.Equal(123, bag["Key"].ToInt32(c));
Assert.Equal(123, bag["Key"].ToNullableInt32(c));
Assert.Equal(123L, bag["Key"].ToInt64(c));
Assert.Equal(123L, bag["Key"].ToNullableInt64(c));
Assert.Equal(123f, bag["Key"].ToSingle(c));
Assert.Equal(123f, bag["Key"].ToNullableSingle(c));
Assert.Equal(123d, bag["Key"].ToDouble(c));
Assert.Equal(123d, bag["Key"].ToNullableDouble(c));
Assert.Equal(expected, bag["Key"].ToInt32(c));
Assert.Equal(expected, bag["Key"].ToNullableInt32(c));
Assert.True(bag["Key"].ToBoolean(c));
Assert.Equal(expected, (int)dynamicBag.Key);
Assert.Equal(expected, (int?)dynamicBag.Key);
}
}
}

11
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs

@ -106,5 +106,16 @@ namespace PinkParrot.Infrastructure
return result;
}
public static bool TryGetValueAsObject<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key, out object value)
{
TValue result;
var isFound = dictionary.TryGetValue(key, out result);
value = result;
return isFound;
}
}
}

32
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertiesBag.cs

@ -8,10 +8,11 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
namespace PinkParrot.Infrastructure
{
public class PropertiesBag
public class PropertiesBag : DynamicObject
{
private readonly Dictionary<string, PropertyValue> internalDictionary = new Dictionary<string, PropertyValue>(StringComparer.OrdinalIgnoreCase);
@ -40,6 +41,23 @@ namespace PinkParrot.Infrastructure
}
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return internalDictionary.Keys;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return internalDictionary.TryGetValueAsObject(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
internalDictionary[binder.Name] = new PropertyValue(value);
return true;
}
public bool Contains(string propertyName)
{
Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
@ -47,20 +65,20 @@ namespace PinkParrot.Infrastructure
return internalDictionary.ContainsKey(propertyName);
}
public PropertiesBag Set(string propertyName, object value)
public bool Remove(string propertyName)
{
Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
internalDictionary[propertyName] = new PropertyValue(value);
return this;
return internalDictionary.Remove(propertyName);
}
public bool Remove(string propertyName)
public PropertiesBag Set(string propertyName, object value)
{
Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
return internalDictionary.Remove(propertyName);
internalDictionary[propertyName] = new PropertyValue(value);
return this;
}
public bool Rename(string oldPropertyName, string newPropertyName)

82
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/PropertyValue.cs

@ -8,34 +8,36 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using NodaTime;
using NodaTime.Text;
namespace PinkParrot.Infrastructure
{
public sealed class PropertyValue
public sealed class PropertyValue : DynamicObject
{
private readonly object rawValue;
private static readonly HashSet<Type> AllowedTypes = new HashSet<Type>
{
typeof(string),
typeof(bool),
typeof(bool?),
typeof(float),
typeof(float?),
typeof(double),
typeof(double?),
typeof(int),
typeof(int?),
typeof(long),
typeof(long?),
typeof(Instant),
typeof(Instant?),
typeof(Guid),
typeof(Guid?)
};
private static readonly Dictionary<Type, Func<PropertyValue, CultureInfo, object>> Parsers =
new Dictionary<Type, Func<PropertyValue, CultureInfo, object>>
{
{ typeof(string), (p, c) => p.ToString() },
{ typeof(bool), (p, c) => p.ToBoolean(c) },
{ typeof(bool?), (p, c) => p.ToNullableBoolean(c) },
{ typeof(float), (p, c) => p.ToSingle(c) },
{ typeof(float?), (p, c) => p.ToNullableSingle(c) },
{ typeof(double), (p, c) => p.ToDouble(c) },
{ typeof(double?), (p, c) => p.ToNullableDouble(c) },
{ typeof(int), (p, c) => p.ToInt32(c) },
{ typeof(int?), (p, c) => p.ToNullableInt32(c) },
{ typeof(long), (p, c) => p.ToInt64(c) },
{ typeof(long?), (p, c) => p.ToNullableInt64(c) },
{ typeof(Instant), (p, c) => p.ToInstant(c) },
{ typeof(Instant?), (p, c) => p.ToNullableInstant(c) },
{ typeof(Guid), (p, c) => p.ToGuid(c) },
{ typeof(Guid?), (p, c) => p.ToNullableGuid(c) },
};
public object RawValue
{
@ -44,14 +46,35 @@ namespace PinkParrot.Infrastructure
internal PropertyValue(object rawValue)
{
if (rawValue != null && !AllowedTypes.Contains(rawValue.GetType()))
if (rawValue != null && !Parsers.ContainsKey(rawValue.GetType()))
{
throw new ArgumentException("The type is not supported.", nameof(rawValue));
throw new InvalidOperationException($"The type '{rawValue.GetType()}' is not supported.");
}
this.rawValue = rawValue;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
result = null;
if (binder.Type == typeof(object))
{
result = rawValue;
}
Func<PropertyValue, CultureInfo, object> parser;
if (!Parsers.TryGetValue(binder.Type, out parser))
{
return false;
}
result = parser(this, CultureInfo.InvariantCulture);
return true;
}
public override string ToString()
{
return rawValue?.ToString();
@ -59,12 +82,12 @@ namespace PinkParrot.Infrastructure
public bool ToBoolean(CultureInfo culture)
{
return ToOrParseValue(culture, bool.Parse);
return ToOrParseValue(culture, ParseBoolean);
}
public bool? ToNullableBoolean(CultureInfo culture)
{
return ToNullableOrParseValue(culture, bool.Parse);
return ToNullableOrParseValue(culture, ParseBoolean);
}
public float ToSingle(CultureInfo culture)
@ -207,5 +230,18 @@ namespace PinkParrot.Infrastructure
throw new InvalidCastException(message, e);
}
}
private static bool ParseBoolean(string value)
{
switch (value)
{
case "1":
return true;
case "0":
return false;
default:
return bool.Parse(value);
}
}
}
}

Loading…
Cancel
Save