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/=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/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: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/CodeFormatting/JavaScriptCodeFormatting/QUOTE_STYLE/@EntryValue">SingleQuoted</s:String>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD; <s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD;
$FILENAME$&#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 CultureInfo c = CultureInfo.InvariantCulture;
private readonly PropertiesBag bag = new PropertiesBag(); private readonly PropertiesBag bag = new PropertiesBag();
private readonly dynamic dynamicBag;
public PropertiesBagTest()
{
dynamicBag = bag;
}
[Fact] [Fact]
public void Should_return_false_when_renaming_unknown_property() public void Should_return_false_when_renaming_unknown_property()
@ -63,8 +69,8 @@ namespace PinkParrot.Infrastructure
bag.Set("Key2", 1); bag.Set("Key2", 1);
Assert.Equal(2, bag.Count); Assert.Equal(2, bag.Count);
Assert.Equal(new[] { "Key1", "Key2" }, bag.PropertyNames.ToArray()); Assert.Equal(new[] {"Key1", "Key2"}, bag.PropertyNames.ToArray());
Assert.Equal(new[] { "Key1", "Key2" }, bag.Properties.Keys.ToArray()); Assert.Equal(new[] {"Key1", "Key2"}, bag.Properties.Keys.ToArray());
} }
[Fact] [Fact]
@ -93,10 +99,24 @@ namespace PinkParrot.Infrastructure
Assert.False(bag.Contains("KEY")); 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] [Fact]
public void Should_throw_when_setting_value_with_invalid_type() 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] [Fact]
@ -168,7 +188,7 @@ namespace PinkParrot.Infrastructure
[Fact] [Fact]
public void Should_convert_from_instant_string() 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()); bag.Set("Key", time.ToString());
@ -200,7 +220,7 @@ namespace PinkParrot.Infrastructure
{ {
bag.Set("Key", true); bag.Set("Key", true);
AssertBoolean(); AssertBoolean(true);
} }
[Fact] [Fact]
@ -208,7 +228,7 @@ namespace PinkParrot.Infrastructure
{ {
bag.Set("Key", "true"); bag.Set("Key", "true");
AssertBoolean(); AssertBoolean(true);
} }
[Fact] [Fact]
@ -216,66 +236,109 @@ namespace PinkParrot.Infrastructure
{ {
bag.Set("Key", 1); bag.Set("Key", 1);
AssertBoolean(); AssertBoolean(true);
} }
[Fact] [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] [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.Throws<InvalidCastException>(() => bag["Key"].ToGuid(CultureInfo.InvariantCulture));
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.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.Equal(expected, bag["Key"].ToBoolean(c));
Assert.True(bag["Key"].ToNullableBoolean(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) private void AssertInstant(Instant expected)
{ {
Assert.Equal(expected.ToUnixTimeSeconds(), bag["Key"].ToInstant(c).ToUnixTimeSeconds()); Assert.Equal(expected, bag["Key"].ToInstant(c));
Assert.Equal(expected.ToUnixTimeSeconds(), bag["Key"].ToNullableInstant(c).Value.ToUnixTimeSeconds()); 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) private void AssertGuid(Guid expected)
{ {
Assert.Equal(expected, bag["Key"].ToGuid(c)); Assert.Equal(expected, bag["Key"].ToGuid(c));
Assert.Equal(expected, bag["Key"].ToNullableGuid(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(expected, bag["Key"].ToInt32(c));
Assert.Equal(123, bag["Key"].ToNullableInt32(c)); Assert.Equal(expected, 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.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; 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
namespace PinkParrot.Infrastructure namespace PinkParrot.Infrastructure
{ {
public class PropertiesBag public class PropertiesBag : DynamicObject
{ {
private readonly Dictionary<string, PropertyValue> internalDictionary = new Dictionary<string, PropertyValue>(StringComparer.OrdinalIgnoreCase); 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) public bool Contains(string propertyName)
{ {
Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
@ -47,20 +65,20 @@ namespace PinkParrot.Infrastructure
return internalDictionary.ContainsKey(propertyName); return internalDictionary.ContainsKey(propertyName);
} }
public PropertiesBag Set(string propertyName, object value) public bool Remove(string propertyName)
{ {
Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
internalDictionary[propertyName] = new PropertyValue(value); return internalDictionary.Remove(propertyName);
return this;
} }
public bool Remove(string propertyName) public PropertiesBag Set(string propertyName, object value)
{ {
Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); Guard.NotNullOrEmpty(propertyName, nameof(propertyName));
return internalDictionary.Remove(propertyName); internalDictionary[propertyName] = new PropertyValue(value);
return this;
} }
public bool Rename(string oldPropertyName, string newPropertyName) public bool Rename(string oldPropertyName, string newPropertyName)

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

@ -8,34 +8,36 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
using System.Globalization; using System.Globalization;
using NodaTime; using NodaTime;
using NodaTime.Text; using NodaTime.Text;
namespace PinkParrot.Infrastructure namespace PinkParrot.Infrastructure
{ {
public sealed class PropertyValue public sealed class PropertyValue : DynamicObject
{ {
private readonly object rawValue; private readonly object rawValue;
private static readonly HashSet<Type> AllowedTypes = new HashSet<Type> private static readonly Dictionary<Type, Func<PropertyValue, CultureInfo, object>> Parsers =
{ new Dictionary<Type, Func<PropertyValue, CultureInfo, object>>
typeof(string), {
typeof(bool), { typeof(string), (p, c) => p.ToString() },
typeof(bool?), { typeof(bool), (p, c) => p.ToBoolean(c) },
typeof(float), { typeof(bool?), (p, c) => p.ToNullableBoolean(c) },
typeof(float?), { typeof(float), (p, c) => p.ToSingle(c) },
typeof(double), { typeof(float?), (p, c) => p.ToNullableSingle(c) },
typeof(double?), { typeof(double), (p, c) => p.ToDouble(c) },
typeof(int), { typeof(double?), (p, c) => p.ToNullableDouble(c) },
typeof(int?), { typeof(int), (p, c) => p.ToInt32(c) },
typeof(long), { typeof(int?), (p, c) => p.ToNullableInt32(c) },
typeof(long?), { typeof(long), (p, c) => p.ToInt64(c) },
typeof(Instant), { typeof(long?), (p, c) => p.ToNullableInt64(c) },
typeof(Instant?), { typeof(Instant), (p, c) => p.ToInstant(c) },
typeof(Guid), { typeof(Instant?), (p, c) => p.ToNullableInstant(c) },
typeof(Guid?) { typeof(Guid), (p, c) => p.ToGuid(c) },
}; { typeof(Guid?), (p, c) => p.ToNullableGuid(c) },
};
public object RawValue public object RawValue
{ {
@ -44,14 +46,35 @@ namespace PinkParrot.Infrastructure
internal PropertyValue(object rawValue) 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; 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() public override string ToString()
{ {
return rawValue?.ToString(); return rawValue?.ToString();
@ -59,12 +82,12 @@ namespace PinkParrot.Infrastructure
public bool ToBoolean(CultureInfo culture) public bool ToBoolean(CultureInfo culture)
{ {
return ToOrParseValue(culture, bool.Parse); return ToOrParseValue(culture, ParseBoolean);
} }
public bool? ToNullableBoolean(CultureInfo culture) public bool? ToNullableBoolean(CultureInfo culture)
{ {
return ToNullableOrParseValue(culture, bool.Parse); return ToNullableOrParseValue(culture, ParseBoolean);
} }
public float ToSingle(CultureInfo culture) public float ToSingle(CultureInfo culture)
@ -207,5 +230,18 @@ namespace PinkParrot.Infrastructure
throw new InvalidCastException(message, e); 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