Support Missing Key Handlers

A request was made to allow missing keys to be handled in a custom
manner. I created an event that can be registered with the Generator
instances.

I also fixed a deployment issue causing debug versions of the library to
be packaged and released to NuGet. This should boost performance
considerably.
This commit is contained in:
Travis Parks 2013-04-18 19:26:58 -04:00
parent bfc579be22
commit ac09c8fc38
8 changed files with 1385 additions and 1277 deletions

View File

@ -1,3 +1,4 @@
nuget pack ../mustache-sharp/mustache-sharp.csproj -Prop Configuration=Release -Build msbuild ../mustache-sharp.sln /p:Configuration=Release
nuget pack ../mustache-sharp/mustache-sharp.csproj -Properties Configuration=Release
nuget push *.nupkg nuget push *.nupkg
del *.nupkg del *.nupkg

View File

@ -110,6 +110,47 @@ namespace mustache.test
generator.Render(new object()); generator.Render(new object());
} }
/// <summary>
/// If we try to print a key that doesn't exist, we can provide a
/// handler to provide a substitute.
/// </summary>
[TestMethod]
public void TestCompile_MissingKey_CallsKeyNotFoundHandler()
{
FormatCompiler compiler = new FormatCompiler();
const string format = @"Hello, {{Name}}!!!";
Generator generator = compiler.Compile(format);
generator.KeyNotFound += (obj, args) =>
{
args.Substitute = "Unknown";
args.Handled = true;
};
string actual = generator.Render(new object());
string expected = "Hello, Unknown!!!";
Assert.AreEqual(expected, actual, "The wrong message was generated.");
}
/// <summary>
/// If the key is the parent object, the search will go up the hierarchy.
/// </summary>
[TestMethod]
public void TestCompile_KeyInParent_LooksUpKeyInParent()
{
FormatCompiler compiler = new FormatCompiler();
const string format = @"{{#with Address}}{{FirstName}} from {{City}}{{/with}}";
Generator generator = compiler.Compile(format);
string actual = generator.Render(new
{
FirstName = "Bob",
Address = new
{
City = "Philadelphia",
}
});
string expected = "Bob from Philadelphia";
Assert.AreEqual(expected, actual, "The wrong message was generated.");
}
/// <summary> /// <summary>
/// If we specify an alignment with a key, the alignment should /// If we specify an alignment with a key, the alignment should
/// be used when rending the value. /// be used when rending the value.

View File

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("0.0.3.0")] [assembly: AssemblyVersion("0.0.4.0")]
[assembly: AssemblyFileVersion("0.0.3.0")] [assembly: AssemblyFileVersion("0.0.4.0")]

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -10,6 +11,7 @@ namespace mustache
public sealed class Generator public sealed class Generator
{ {
private readonly IGenerator _generator; private readonly IGenerator _generator;
private readonly List<EventHandler<MissingKeyEventArgs>> _handlers;
/// <summary> /// <summary>
/// Initializes a new instance of a Generator. /// Initializes a new instance of a Generator.
@ -18,6 +20,16 @@ namespace mustache
internal Generator(IGenerator generator) internal Generator(IGenerator generator)
{ {
_generator = generator; _generator = generator;
_handlers = new List<EventHandler<MissingKeyEventArgs>>();
}
/// <summary>
/// Occurs when a key/property is not found in the object graph.
/// </summary>
public event EventHandler<MissingKeyEventArgs> KeyNotFound
{
add { _handlers.Add(value); }
remove { _handlers.Remove(value); }
} }
/// <summary> /// <summary>
@ -48,6 +60,10 @@ namespace mustache
private string render(IFormatProvider provider, object source) private string render(IFormatProvider provider, object source)
{ {
KeyScope scope = new KeyScope(source); KeyScope scope = new KeyScope(source);
foreach (EventHandler<MissingKeyEventArgs> handler in _handlers)
{
scope.KeyNotFound += handler;
}
StringWriter writer = new StringWriter(provider); StringWriter writer = new StringWriter(provider);
_generator.GetText(scope, writer); _generator.GetText(scope, writer);
return writer.ToString(); return writer.ToString();

View File

@ -33,6 +33,11 @@ namespace mustache
_source = source; _source = source;
} }
/// <summary>
/// Occurs when a key/property is not found in the object graph.
/// </summary>
public event EventHandler<MissingKeyEventArgs> KeyNotFound;
/// <summary> /// <summary>
/// Creates a child scope that searches for keys in the given object. /// Creates a child scope that searches for keys in the given object.
/// </summary> /// </summary>
@ -41,6 +46,7 @@ namespace mustache
public KeyScope CreateChildScope(object source) public KeyScope CreateChildScope(object source)
{ {
KeyScope scope = new KeyScope(source, this); KeyScope scope = new KeyScope(source, this);
scope.KeyNotFound = KeyNotFound;
return scope; return scope;
} }
@ -77,6 +83,15 @@ namespace mustache
} }
if (_parent == null) if (_parent == null)
{ {
MissingKeyEventArgs args = new MissingKeyEventArgs(name);
if (KeyNotFound != null)
{
KeyNotFound(this, args);
}
if (args.Handled)
{
return args.Substitute;
}
string message = String.Format(CultureInfo.CurrentCulture, Resources.KeyNotFound, name); string message = String.Format(CultureInfo.CurrentCulture, Resources.KeyNotFound, name);
throw new KeyNotFoundException(message); throw new KeyNotFoundException(message);
} }

View File

@ -0,0 +1,34 @@
using System;
namespace mustache
{
/// <summary>
/// Holds the information needed to handle a missing key.
/// </summary>
public class MissingKeyEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of a MissingKeyEventArgs.
/// </summary>
/// <param name="missingKey">The key that had no match.</param>
internal MissingKeyEventArgs(string missingKey)
{
MissingKey = missingKey;
}
/// <summary>
/// Gets the key that could not be found.
/// </summary>
public string MissingKey { get; private set; }
/// <summary>
/// Gets or sets whether to use the substitute.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// Gets or sets the object to use as the substitute.
/// </summary>
public object Substitute { get; set; }
}
}

View File

@ -34,6 +34,6 @@ using System.Runtime.CompilerServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.0.3.0")] [assembly: AssemblyVersion("0.0.4.0")]
[assembly: AssemblyFileVersion("0.0.3.0")] [assembly: AssemblyFileVersion("0.0.4.0")]
[assembly: InternalsVisibleTo("mustache-sharp.test")] [assembly: InternalsVisibleTo("mustache-sharp.test")]

View File

@ -49,6 +49,7 @@
<Compile Include="InlineGenerator.cs" /> <Compile Include="InlineGenerator.cs" />
<Compile Include="KeyGenerator.cs" /> <Compile Include="KeyGenerator.cs" />
<Compile Include="MasterTagDefinition.cs" /> <Compile Include="MasterTagDefinition.cs" />
<Compile Include="MissingKeyEventArgs.cs" />
<Compile Include="NestedContext.cs" /> <Compile Include="NestedContext.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">