Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Dictionary<,> support #69

Merged
merged 1 commit into from Aug 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
197 changes: 193 additions & 4 deletions source/Handlebars.Test/BasicIntegrationTests.cs
@@ -1,5 +1,6 @@
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
Expand Down Expand Up @@ -223,6 +224,104 @@ public void BasicDictionaryEnumeratorWithKey()
Assert.AreEqual("foo: hello bar: world ", result);
}


[Test]
public void BasicPathDictionaryStringKeyNoSquareBrackets()
{
var source = "Hello, {{ names.Foo }}!";
var template = Handlebars.Compile(source);
var data = new
{
names = new Dictionary<string, string>
{
{ "Foo" , "Handlebars.Net" }
}
};
var result = template(data);
Assert.AreEqual("Hello, Handlebars.Net!", result);
}

[Test]
public void BasicPathDictionaryStringKey()
{
var source = "Hello, {{ names.[Foo] }}!";
var template = Handlebars.Compile(source);
var data = new
{
names = new Dictionary<string, string>
{
{ "Foo" , "Handlebars.Net" }
}
};
var result = template(data);
Assert.AreEqual("Hello, Handlebars.Net!", result);
}

[Test]
public void BasicPathDictionaryIntKeyNoSquareBrackets()
{
var source = "Hello, {{ names.42 }}!";
var template = Handlebars.Compile(source);
var data = new
{
names = new Dictionary<int, string>
{
{ 42 , "Handlebars.Net" }
}
};
var result = template(data);
Assert.AreEqual("Hello, Handlebars.Net!", result);
}

[Test]
public void BasicPathDictionaryLongKeyNoSquareBrackets()
{
var source = "Hello, {{ names.42 }}!";
var template = Handlebars.Compile(source);
var data = new
{
names = new Dictionary<long, string>
{
{ 42 , "Handlebars.Net" }
}
};
var result = template(data);
Assert.AreEqual("Hello, Handlebars.Net!", result);
}

[Test]
public void BasicPathDictionaryIntKey()
{
var source = "Hello, {{ names.[42] }}!";
var template = Handlebars.Compile(source);
var data = new
{
names = new Dictionary<int, string>
{
{ 42 , "Handlebars.Net" }
}
};
var result = template(data);
Assert.AreEqual("Hello, Handlebars.Net!", result);
}

[Test]
public void BasicPathDictionaryLongKey()
{
var source = "Hello, {{ names.[42] }}!";
var template = Handlebars.Compile(source);
var data = new
{
names = new Dictionary<long, string>
{
{ 42 , "Handlebars.Net" }
}
};
var result = template(data);
Assert.AreEqual("Hello, Handlebars.Net!", result);
}


[Test]
public void DynamicWithMetadataEnumerator()
{
Expand Down Expand Up @@ -592,26 +691,116 @@ public void BasicDictionary()
"<div id='userInfo'>UserName: Ondrej Language: Slovak</div>"
+ "<div id='main' style='width:120px; height:80px'>body</div>";

Assert.AreEqual(result, expectedResult);
Assert.AreEqual(expectedResult, result);
}

[Test]
public void BasicHashtable()
{
var source = "{{dictionary.[key]}}";

var template = Handlebars.Compile(source);

var result = template(new
{
dictionary = new Hashtable
{
{ "key", "Hello world!" }
}
});
var expectedResult = "Hello world!";

Assert.AreEqual(expectedResult, result);
}

[Test]
public void BasicIDictionary()
public void BasicHashtableNoSquareBrackets()
{
var source = "{{dictionary.key}}";

var template = Handlebars.Compile(source);

var result = template(new
{
dictionary = new Hashtable
{
{ "key", "Hello world!" }
}
});
var expectedResult = "Hello world!";

Assert.AreEqual(expectedResult, result);
}

[Test]
public void BasicMockIDictionary()
{
var source = "{{dictionary.[key]}}";

var template = Handlebars.Compile(source);

var result = template(new
{
dictionary = new MockDictionary()
});
var expectedResult =
"Hello world!";

Assert.AreEqual(result, expectedResult);
Assert.AreEqual(expectedResult, result);
}

[Test]
public void BasicMockIDictionaryNoSquareBrackets()
{
var source = "{{dictionary.key}}";

var template = Handlebars.Compile(source);

var result = template(new
{
dictionary = new MockDictionary()
});
var expectedResult =
"Hello world!";

Assert.AreEqual(expectedResult, result);
}

[Test]
public void BasicMockIDictionaryIntKey()
{
var source = "{{dictionary.[42]}}";

var template = Handlebars.Compile(source);

var result = template(new
{
dictionary = new MockDictionary()
});
var expectedResult =
"Hello world!";

Assert.AreEqual(expectedResult, result);
}

[Test]
public void BasicMockIDictionaryIntKeyNoSquareBrackets()
{
var source = "{{dictionary.42}}";

var template = Handlebars.Compile(source);

var result = template(new
{
dictionary = new MockDictionary()
});
var expectedResult =
"Hello world!";

Assert.AreEqual(expectedResult, result);
}


private class MockDictionary : IDictionary<string, string>
{
public void Add(string key, string value)
Expand All @@ -620,7 +809,7 @@ public void Add(string key, string value)
}
public bool ContainsKey(string key)
{
throw new NotImplementedException();
return true;
}
public bool Remove(string key)
{
Expand Down
3 changes: 3 additions & 0 deletions source/Handlebars.Test/Handlebars.Test.csproj
Expand Up @@ -80,6 +80,9 @@
<Name>Handlebars</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
Expand Down
60 changes: 54 additions & 6 deletions source/Handlebars/Compiler/Translation/Expression/PathBinder.cs
Expand Up @@ -7,6 +7,7 @@
using System.Dynamic;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Globalization;

namespace HandlebarsDotNet.Compiler
{
Expand Down Expand Up @@ -167,16 +168,51 @@ private object AccessMember(object instance, string memberName)
return new UndefinedBindingResult();
}
}
if (instance is IDictionary)


// Check if the instance is IDictionary<,>
var iDictInstance = FirstGenericDictionaryTypeInstance(instanceType);
if (iDictInstance != null)
{
return ((IDictionary)instance)[resolvedMemberName];
var genericArgs = iDictInstance.GetGenericArguments();
object key = resolvedMemberName.Trim('[', ']'); // Ensure square brackets removed.
if (genericArgs.Length > 0)
{
// Dictionary key type isn't a string, so attempt to convert.
if (genericArgs[0] != typeof(string))
{
try
{
key = Convert.ChangeType(key, genericArgs[0], CultureInfo.CurrentCulture);
}
catch (Exception)
{
// Can't convert to key type.
return new UndefinedBindingResult();
}
}
}

var m = instanceType.GetMethods();
if ((bool)instanceType.GetMethod("ContainsKey").Invoke(instance, new object[] { key }))
{
return instanceType.GetMethod("get_Item").Invoke(instance, new object[] { key });
}
else
{
// Key doesn't exist.
return new UndefinedBindingResult();
}
}
if (instanceType.GetInterfaces()
.Where(i => i.IsGenericType)
.Any(i => i.GetGenericTypeDefinition() == typeof(IDictionary<,>)))
// Check if the instance is IDictionary (ie, System.Collections.Hashtable)
if (typeof(IDictionary).IsAssignableFrom(instanceType))
{
return instanceType.GetMethod("get_Item").Invoke(instance, new object[] { resolvedMemberName });
var key = resolvedMemberName.Trim('[', ']'); // Ensure square brackets removed.
// Only string keys supported - indexer takes an object, but no nice
// way to check if the hashtable check if it should be a different type.
return ((IDictionary)instance)[key];
}

var members = instanceType.GetMember(resolvedMemberName);
if (members.Length != 1)
{
Expand All @@ -196,6 +232,18 @@ private object AccessMember(object instance, string memberName)
return new UndefinedBindingResult();
}

static Type FirstGenericDictionaryTypeInstance(Type instanceType)
{
return instanceType.GetInterfaces()
.FirstOrDefault(i =>
i.IsGenericType
&&
(
i.GetGenericTypeDefinition() == typeof(IDictionary<,>)
)
);
}

private static object GetProperty(object target, string name)
{
var site = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, name, target.GetType(), new[]{ Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0, null) }));
Expand Down