Support firing event when keys/variables used as arguments.

This commit is contained in:
Travis Parks 2013-10-30 15:39:38 -04:00
parent 6e74fa1fcc
commit 6a272230af
5 changed files with 194 additions and 23 deletions

View File

@ -349,6 +349,63 @@ Content";
CollectionAssert.AreEqual(expected, actual, "Not all placeholders were found.");
}
/// <summary>
/// We can track all of the keys that appear in a template by
/// registering with the PlaceholderFound event.
/// </summary>
[TestMethod]
public void TestCompile_FindsVariables_RecordsVariables()
{
FormatCompiler compiler = new FormatCompiler();
HashSet<string> variables = new HashSet<string>();
compiler.VariableFound += (o, e) =>
{
variables.Add(e.Name);
};
compiler.Compile(@"{{@FirstName}}{{@LastName}}");
string[] expected = new string[] { "FirstName", "LastName" };
string[] actual = variables.OrderBy(s => s).ToArray();
CollectionAssert.AreEqual(expected, actual, "Not all variables were found.");
}
/// <summary>
/// We can track all of the keys that appear in a template by
/// registering with the PlaceholderFound event.
/// </summary>
[TestMethod]
public void TestCompile_FindsPlaceholdersInIf_RecordsPlaceholders()
{
FormatCompiler compiler = new FormatCompiler();
HashSet<string> keys = new HashSet<string>();
compiler.PlaceholderFound += (o, e) =>
{
keys.Add(e.Key);
};
compiler.Compile(@"{{#if FirstName}}{{/if}}");
string[] expected = new string[] { "FirstName" };
string[] actual = keys.OrderBy(s => s).ToArray();
CollectionAssert.AreEqual(expected, actual, "Not all placeholders were found.");
}
/// <summary>
/// We can track all of the keys that appear in a template by
/// registering with the PlaceholderFound event.
/// </summary>
[TestMethod]
public void TestCompile_FindsVariablesInIf_RecordsVariables()
{
FormatCompiler compiler = new FormatCompiler();
HashSet<string> variables = new HashSet<string>();
compiler.VariableFound += (o, e) =>
{
variables.Add(e.Name);
};
compiler.Compile(@"{{#if @FirstName}}{{/if}}");
string[] expected = new string[] { "FirstName" };
string[] actual = variables.OrderBy(s => s).ToArray();
CollectionAssert.AreEqual(expected, actual, "Not all placeholders were found.");
}
/// <summary>
/// We can determine the context in which a placeholder is found by looking at the provided context array.
/// </summary>

View File

@ -48,6 +48,11 @@ namespace Mustache
/// </summary>
public event EventHandler<PlaceholderFoundEventArgs> PlaceholderFound;
/// <summary>
/// Occurs when a variable is found in the template.
/// </summary>
public event EventHandler<VariableFoundEventArgs> VariableFound;
/// <summary>
/// Registers the given tag definition with the parser.
/// </summary>
@ -140,7 +145,7 @@ namespace Mustache
private static string getKeyRegex()
{
return @"((?<key>" + RegexHelper.CompoundKey + @")(,(?<alignment>(\+|-)?[\d]+))?(:(?<format>.*?))?)";
return @"((?<key>@?" + RegexHelper.CompoundKey + @")(,(?<alignment>(\+|-)?[\d]+))?(:(?<format>.*?))?)";
}
private static string getTagRegex(TagDefinition definition)
@ -198,12 +203,29 @@ namespace Mustache
string key = match.Groups["key"].Value;
string alignment = match.Groups["alignment"].Value;
string formatting = match.Groups["format"].Value;
PlaceholderFoundEventArgs args = new PlaceholderFoundEventArgs(key, alignment, formatting, context.ToArray());
if (PlaceholderFound != null)
if (key.StartsWith("@"))
{
PlaceholderFound(this, args);
VariableFoundEventArgs args = new VariableFoundEventArgs(key.Substring(1), alignment, formatting, context.ToArray());
if (VariableFound != null)
{
VariableFound(this, args);
key = "@" + args.Name;
alignment = args.Alignment;
formatting = args.Formatting;
}
}
KeyGenerator keyGenerator = new KeyGenerator(args.Key, args.Alignment, args.Formatting);
else
{
PlaceholderFoundEventArgs args = new PlaceholderFoundEventArgs(key, alignment, formatting, context.ToArray());
if (PlaceholderFound != null)
{
PlaceholderFound(this, args);
key = args.Key;
alignment = args.Alignment;
formatting = args.Formatting;
}
}
KeyGenerator keyGenerator = new KeyGenerator(key, alignment, formatting);
generator.AddGenerator(keyGenerator);
}
else if (match.Groups["open"].Success)
@ -216,10 +238,12 @@ namespace Mustache
string message = String.Format(Resources.UnknownTag, tagName);
throw new FormatException(message);
}
generator.AddGenerator(new StaticGenerator(leading));
ArgumentCollection arguments = getArguments(nextDefinition, match, context);
if (nextDefinition.HasContent)
{
generator.AddGenerator(new StaticGenerator(leading));
ArgumentCollection arguments = getArguments(nextDefinition, match);
CompoundGenerator compoundGenerator = new CompoundGenerator(nextDefinition, arguments);
IEnumerable<TagParameter> contextParameters = nextDefinition.GetChildContextParameters();
bool hasContext = contextParameters.Any();
@ -237,8 +261,6 @@ namespace Mustache
}
else
{
generator.AddGenerator(new StaticGenerator(leading));
ArgumentCollection arguments = getArguments(nextDefinition, match);
InlineGenerator inlineGenerator = new InlineGenerator(nextDefinition, arguments);
generator.AddGenerator(inlineGenerator);
}
@ -270,9 +292,9 @@ namespace Mustache
return formatIndex;
}
private static ArgumentCollection getArguments(TagDefinition definition, Match match)
private ArgumentCollection getArguments(TagDefinition definition, Match match, List<Context> context)
{
ArgumentCollection collection = new ArgumentCollection();
// make sure we don't have too many arguments
List<Capture> captures = match.Groups["argument"].Captures.Cast<Capture>().ToList();
List<TagParameter> parameters = definition.Parameters.ToList();
if (captures.Count > parameters.Count)
@ -280,25 +302,60 @@ namespace Mustache
string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
throw new FormatException(message);
}
// provide default values for missing arguments
if (captures.Count < parameters.Count)
{
captures.AddRange(Enumerable.Repeat((Capture)null, parameters.Count - captures.Count));
}
// pair up the parameters to the given arguments
// provide default for parameters with missing arguments
// throw an error if a missing argument is for a required parameter
Dictionary<TagParameter, string> arguments = new Dictionary<TagParameter, string>();
foreach (var pair in parameters.Zip(captures, (p, c) => new { Capture = c, Parameter = p }))
{
if (pair.Capture == null)
string value = null;
if (pair.Capture != null)
{
if (pair.Parameter.IsRequired)
{
string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
throw new FormatException(message);
}
collection.AddArgument(pair.Parameter, null);
value = pair.Capture.Value;
}
else
else if (pair.Parameter.IsRequired)
{
collection.AddArgument(pair.Parameter, pair.Capture.Value);
}
string message = String.Format(Resources.WrongNumberOfArguments, definition.Name);
throw new FormatException(message);
}
arguments.Add(pair.Parameter, value);
}
// indicate that a key/variable has been encountered
// update the key/variable name
ArgumentCollection collection = new ArgumentCollection();
foreach (var pair in arguments)
{
string placeholder = pair.Value;
if (placeholder != null)
{
if (placeholder.StartsWith("@"))
{
VariableFoundEventArgs args = new VariableFoundEventArgs(placeholder.Substring(1), String.Empty, String.Empty, context.ToArray());
if (VariableFound != null)
{
VariableFound(this, args);
placeholder = "@" + args.Name;
}
}
else
{
PlaceholderFoundEventArgs args = new PlaceholderFoundEventArgs(placeholder, String.Empty, String.Empty, context.ToArray());
if (PlaceholderFound != null)
{
PlaceholderFound(this, args);
placeholder = args.Key;
}
}
}
collection.AddArgument(pair.Key, placeholder);
}
return collection;
}

View File

@ -11,6 +11,7 @@ namespace Mustache
{
private readonly string _key;
private readonly string _format;
private readonly bool _isVariable;
/// <summary>
/// Initializes a new instance of a KeyGenerator.
@ -20,7 +21,16 @@ namespace Mustache
/// <param name="formatting">The format specifier.</param>
public KeyGenerator(string key, string alignment, string formatting)
{
_key = key;
if (key.StartsWith("@"))
{
_key = key.Substring(1);
_isVariable = true;
}
else
{
_key = key;
_isVariable = false;
}
_format = getFormat(alignment, formatting);
}
@ -44,7 +54,7 @@ namespace Mustache
void IGenerator.GetText(Scope scope, TextWriter writer, Scope context)
{
object value = scope.Find(_key);
object value = _isVariable ? context.Find(_key) : scope.Find(_key);
writer.Write(_format, value);
}
}

View File

@ -0,0 +1,46 @@
using System;
using Mustache.Properties;
namespace Mustache
{
/// <summary>
/// Holds the information descibing a variable that is found in a template.
/// </summary>
public class VariableFoundEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of a VariableFoundEventArgs.
/// </summary>
/// <param name="key">The key that was found.</param>
/// <param name="alignment">The alignment that will be applied to the substitute value.</param>
/// <param name="formatting">The formatting that will be applied to the substitute value.</param>
/// <param name="context">The context where the placeholder was found.</param>
internal VariableFoundEventArgs(string name, string alignment, string formatting, Context[] context)
{
Name = name;
Alignment = alignment;
Formatting = formatting;
Context = context;
}
/// <summary>
/// Gets or sets the key that was found.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the alignment that will be applied to the substitute value.
/// </summary>
public string Alignment { get; set; }
/// <summary>
/// Gets or sets the formatting that will be applied to the substitute value.
/// </summary>
public string Formatting { get; set; }
/// <summary>
/// Gets the context where the placeholder was found.
/// </summary>
public Context[] Context { get; private set; }
}
}

View File

@ -40,6 +40,7 @@
<Compile Include="ContentTagDefinition.cs" />
<Compile Include="Context.cs" />
<Compile Include="ContextParameter.cs" />
<Compile Include="VariableFoundEventArgs.cs" />
<Compile Include="SetTagDefinition.cs" />
<Compile Include="NewlineTagDefinition.cs" />
<Compile Include="IndexTagDefinition.cs" />